@orkestrel/scaffold 0.0.65 → 0.0.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,9 @@
3
3
  Pick an affordance from what the person is asked for, not from the name a schema gives the field.
4
4
  Where one category draws several ways, let the density and the list size decide.
5
5
 
6
+ Each **Default** and **Alternate** names the styling rung it sits on. Take the rung names and their
7
+ order from [SKILL.md](../SKILL.md) → The styling ladder.
8
+
6
9
  Read [The fixed state set](#the-fixed-state-set) before the catalog: every affordance handles that
7
10
  same set, and each category names only what it adds or changes. Take the data states a whole surface
8
11
  ships — ideal, empty, loading, partial, error — from
@@ -52,13 +55,14 @@ value is a set.
52
55
  switches without shrinking the text. Emulated viewport tests do not prove soft-keyboard behavior.
53
56
 
54
57
  - **Keep a read-only field on the same affordance the edit state uses.** Take `readonly`, or
55
- `disabled` plus a carrier, and neutralize the chrome with one transparent combination declared
56
- once by name — the combination is a class contract, so declare it and reuse it rather than
57
- retyping the utilities. Never swap to `form-control-plaintext`: it drops the horizontal padding,
58
+ `disabled` plus a carrier, and neutralize the chrome with one transparent combination the project
59
+ declares by name in its own stylesheet and token layer. The project chooses that combination; this
60
+ skill prescribes none. Reuse the declared name rather than retyping the utilities at each site.
61
+ Never swap to `form-control-plaintext`: it drops the horizontal padding,
58
62
  so the read view and the edit view reflow against each other.
59
63
  - **Give a locked select `disabled` and a hidden input beside it.** A native select cannot be
60
64
  read-only, so `disabled` stops its value submitting and the hidden input carries that value.
61
- - **Verify a chosen filter in light and dark.** Keep its native checked/pressed state and a
65
+ - **Verify a chosen filter in every declared theme.** Keep its native checked/pressed state and a
62
66
  distinguishable visual treatment; take selection rules from [components.md](components.md) →
63
67
  Selection fills. Do not prescribe an accent color as a substitute for that check.
64
68
  - **Keep native control foregrounds and states.** For quiet chip or group backgrounds, inherit
@@ -78,7 +82,9 @@ value is a set.
78
82
 
79
83
  ### One line of text
80
84
 
81
- **Default.** An `input.form-control` under its own `label.form-label`, at rung 1.
85
+ **Default.** An `input.form-control` under its own `label.form-label`, at the component rung.
86
+
87
+ The stock control border is `var(--bs-border-color)` — 1.3:1 on the light body and 1.9:1 on the dark one — so it is decorative; the label, the field's fill against the page, and the focus ring carry recognition. Where the boundary is the only cue (a field on a same-color surface), set `--bs-border-color: var(--bs-secondary-color)` on the form scope — adaptive, about 5.6:1 light and 7:1 dark — and re-measure; never a fixed `border-dark`.
82
88
 
83
89
  ```html
84
90
  <label for="account-name" class="form-label">Account name</label>
@@ -97,7 +103,7 @@ declared transparent combination rather than dropping the control.
97
103
  ### Text over many lines
98
104
 
99
105
  **Default.** A `textarea.form-control` with a `rows` attribute sized to the expected answer, at
100
- rung 1.
106
+ the component rung.
101
107
 
102
108
  ```html
103
109
  <label for="incident-notes" class="form-label">Notes</label>
@@ -114,7 +120,7 @@ count in the `.form-text`, and keep it out of a live region unless the cap is cl
114
120
 
115
121
  ### A secret
116
122
 
117
- **Default.** An `input[type=password].form-control` under a visible label, at rung 1.
123
+ **Default.** An `input[type=password].form-control` under a visible label, at the component rung.
118
124
 
119
125
  ```html
120
126
  <label for="passphrase" class="form-label">Passphrase</label>
@@ -133,8 +139,8 @@ one-time code the person must read back.
133
139
 
134
140
  ### A number
135
141
 
136
- **Default.** An `input[type=number].form-control`, at rung 1. In a column of figures add
137
- `text-end font-monospace` so the digits align, at rung 2.
142
+ **Default.** An `input[type=number].form-control`, at the component rung. In a column of figures add
143
+ `text-end font-monospace` so the digits align, at the utility rung.
138
144
 
139
145
  ```html
140
146
  <label for="unit-count" class="form-label">Units</label>
@@ -149,7 +155,7 @@ than a quantity, take the one-line-of-text category instead.
149
155
 
150
156
  ### A number in a bounded range
151
157
 
152
- **Default.** An `input.form-range`, at rung 1, and only when a minimum, a maximum, and a step are all
158
+ **Default.** An `input.form-range`, at the component rung, and only when a minimum, a maximum, and a step are all
153
159
  fixed.
154
160
 
155
161
  ```html
@@ -168,7 +174,7 @@ hand-roll: Bootstrap ships one thumb per input.
168
174
 
169
175
  ### A date
170
176
 
171
- **Default.** An `input[type=date].form-control`, at rung 1. Take the calendar, the keyboard model,
177
+ **Default.** An `input[type=date].form-control`, at the component rung. Take the calendar, the keyboard model,
172
178
  and the locale format from the platform rather than authoring any of them.
173
179
 
174
180
  ```html
@@ -184,7 +190,7 @@ help text, because a native picker takes no per-day exclusion.
184
190
 
185
191
  ### A time
186
192
 
187
- **Default.** An `input[type=time].form-control`, at rung 1, with `step` set to the granularity the
193
+ **Default.** An `input[type=time].form-control`, at the component rung, with `step` set to the granularity the
188
194
  value actually carries.
189
195
 
190
196
  ```html
@@ -200,7 +206,7 @@ select is the lighter control.
200
206
 
201
207
  ### A date and time
202
208
 
203
- **Default.** An `input[type=datetime-local].form-control`, at rung 1.
209
+ **Default.** An `input[type=datetime-local].form-control`, at the component rung.
204
210
 
205
211
  ```html
206
212
  <label for="window-opens" class="form-label">Window opens</label>
@@ -215,7 +221,7 @@ stored in; a local datetime carries none.
215
221
 
216
222
  ### A color
217
223
 
218
- **Default.** An `input.form-control-color[type=color]`, at rung 1.
224
+ **Default.** An `input.form-control-color[type=color]`, at the component rung.
219
225
 
220
226
  ```html
221
227
  <label for="brand-tint" class="form-label">Brand tint</label>
@@ -223,7 +229,8 @@ stored in; a local datetime carries none.
223
229
  ```
224
230
 
225
231
  **Alternates.** Pair the swatch with a text field when the value is copied, pasted, or read aloud
226
- between people. Keep the swatch at a 24×24px target or larger.
232
+ between people. Hold the swatch to the target floor in
233
+ [bootstrap-reference.md](bootstrap-reference.md) → WCAG 2.2 requirements for app UI.
227
234
 
228
235
  **States.** The fixed set. A color input has no empty value, so give the field a default and say what
229
236
  it is.
@@ -231,7 +238,7 @@ it is.
231
238
  ### One on/off answer
232
239
 
233
240
  **Default.** A `.form-check` holding one `input.form-check-input[type=checkbox]` and its
234
- `label.form-check-label`, at rung 1.
241
+ `label.form-check-label`, at the component rung.
235
242
 
236
243
  ```html
237
244
  <div class="form-check">
@@ -250,7 +257,7 @@ flight, and reverts visibly when it fails.
250
257
 
251
258
  ### One of a few
252
259
 
253
- **Default.** A radio group: `fieldset` and `legend` around `.form-check` rows, at rung 1.
260
+ **Default.** A radio group: `fieldset` and `legend` around `.form-check` rows, at the component rung.
254
261
 
255
262
  ```html
256
263
  <fieldset>
@@ -266,7 +273,7 @@ flight, and reverts visibly when it fails.
266
273
  </fieldset>
267
274
  ```
268
275
 
269
- **Alternates.** Take a segmented `.btn-group` of `.btn-check` radios, at rung 2, when the choice
276
+ **Alternates.** Take a segmented `.btn-group` of `.btn-check` radios, at the utility rung, when the choice
270
277
  sits in a toolbar or a filter bar and every option fits on one row without wrapping; give the group
271
278
  `role="radiogroup"` and one accessible name. A radio group and a segmented group draw the same
272
279
  question, and the list size decides between them. Verify the chosen-state contract rather than
@@ -277,7 +284,7 @@ name it in the message, and keep the error under the last row.
277
284
 
278
285
  ### One of many
279
286
 
280
- **Default.** A `select.form-select`, at rung 1.
287
+ **Default.** A `select.form-select`, at the component rung.
281
288
 
282
289
  ```html
283
290
  <label for="territory" class="form-label">Territory</label>
@@ -297,7 +304,7 @@ hidden input. A select whose options are loading is `busy`.
297
304
 
298
305
  ### One of many with an unlisted value admitted
299
306
 
300
- **Default.** An `input.form-control` bound to a `<datalist>`, at rung 1. The list suggests; the
307
+ **Default.** An `input.form-control` bound to a `<datalist>`, at the component rung. The list suggests; the
301
308
  person can still submit a value it does not hold.
302
309
 
303
310
  ```html
@@ -316,7 +323,7 @@ re-target every test and journey that finds this field by role.
316
323
 
317
324
  ### Any of a few
318
325
 
319
- **Default.** `fieldset` and `legend` around `.form-check` checkbox rows sharing one name, at rung 1.
326
+ **Default.** `fieldset` and `legend` around `.form-check` checkbox rows sharing one name, at the component rung.
320
327
 
321
328
  ```html
322
329
  <fieldset>
@@ -338,7 +345,7 @@ text and validate it on the group.
338
345
 
339
346
  ### Any of many
340
347
 
341
- **Default.** A bounded, scrollable list of `.form-check` rows inside a bordered box, at rung 2, with
348
+ **Default.** A bounded, scrollable list of `.form-check` rows inside a bordered box, at the utility rung, with
342
349
  a filter field preceding it so the person can narrow the list before choosing.
343
350
 
344
351
  ```html
@@ -362,7 +369,7 @@ one-action way to clear it.
362
369
 
363
370
  ### A value picked from a searched list
364
371
 
365
- **Default.** A combobox composed from shipped classes at rung 2, with the keyboard model
372
+ **Default.** A combobox composed from shipped classes at the utility rung, with the keyboard model
366
373
  hand-rolled against the APG combobox pattern: an `input.form-control` carrying `role="combobox"`,
367
374
  `aria-expanded`, `aria-controls`, `aria-autocomplete="list"`, and `aria-activedescendant`, over a
368
375
  `ul.dropdown-menu[role=listbox]` of `.dropdown-item` buttons.
@@ -395,7 +402,7 @@ the input stays operable throughout.
395
402
 
396
403
  ### Files
397
404
 
398
- **Default.** An `input[type=file].form-control`, at rung 1.
405
+ **Default.** An `input[type=file].form-control`, at the component rung.
399
406
 
400
407
  ```html
401
408
  <label for="statement" class="form-label">Statement</label>
@@ -413,7 +420,7 @@ toast.
413
420
 
414
421
  ### An ordered set of tags
415
422
 
416
- **Default.** Bootstrap ships no tags input. Compose one at rung 2 from a text field that commits on
423
+ **Default.** Bootstrap ships no tags input. Compose one at the utility rung from a text field that commits on
417
424
  Enter plus a row of quiet chips, each carrying a `btn-close` with its own accessible name.
418
425
  Use a utility-composed span so text inherits and the close control retains its normal font size.
419
426
 
@@ -432,7 +439,8 @@ Use a utility-composed span so text inherits and the close control retains its n
432
439
  ```
433
440
 
434
441
  Keep tag text inherited and let the close icon follow the active mode. Measure the close button
435
- against the chip and its hit area against the package target floor. Take a retained `.badge`
442
+ against the chip and its hit area against the target floor in
443
+ [bootstrap-reference.md](bootstrap-reference.md) → WCAG 2.2 requirements for app UI. Take a retained `.badge`
436
444
  through [color-modes.md](color-modes.md) → Badges and removable tags.
437
445
 
438
446
  **Alternates.** Where the tags come from a fixed vocabulary, this is the any-of-many category and the
@@ -444,10 +452,10 @@ control per chip.
444
452
 
445
453
  ### A rating
446
454
 
447
- **Default.** Bootstrap ships no rating. Draw the interactive form as a radio group at rung 2 — one
455
+ **Default.** Bootstrap ships no rating. Draw the interactive form as a radio group at the utility rung — one
448
456
  radio per value, restyled through `.btn-check` — so the keyboard model, the name, and the submitted
449
457
  value come from the platform. Star chrome over that structure is an authored class contract at
450
- rung 4.
458
+ the authored rung.
451
459
 
452
460
  ```html
453
461
  <fieldset>
@@ -467,7 +475,7 @@ per [components.md](components.md) → Status glyph marks; it is not a control.
467
475
 
468
476
  ### A step in a sequence
469
477
 
470
- **Default.** Draw a step indicator from shipped parts at rung 1 — a `nav` or `.list-group-numbered`
478
+ **Default.** Draw a step indicator from shipped parts at the component rung — a `nav` or `.list-group-numbered`
471
479
  whose current item carries `aria-current="step"`, with a `.progress` bar over a long sequence. A step
472
480
  indicator reports where the person is and holds no value, so it is not a field.
473
481
 
@@ -30,8 +30,9 @@ a pass label alone.
30
30
  - [Declared design scales](#declared-design-scales)
31
31
  - [Custom rule doing a utility's job](#custom-rule-doing-a-utilitys-job)
32
32
  - [Color-mode inheritance](#color-mode-inheritance)
33
- - [Composited contrast in both themes](#composited-contrast-in-both-themes)
33
+ - [Composited contrast in every declared theme](#composited-contrast-in-every-declared-theme)
34
34
  - [One glyph, one meaning](#one-glyph-one-meaning)
35
+ - [Fixed and adaptive pairing](#fixed-and-adaptive-pairing)
35
36
  - [Responsive task and reflow](#responsive-task-and-reflow)
36
37
  - [Responsive interaction continuity](#responsive-interaction-continuity)
37
38
  - [Rendered design review](#rendered-design-review)
@@ -48,7 +49,7 @@ a pass label alone.
48
49
  in the mounted tree separately; an unreachable stylesheet is an open dependency, not an empty one.
49
50
  - **Negative control.** Feed an undefined styling token to the reader, then append a harness SVG
50
51
  carrying that token through the same tree extractor. Both must be reported.
51
- - **Coverage.** The two controls cover set comparison and extraction, including SVG's non-string
52
+ - **Coverage.** The controls cover set comparison and extraction, including SVG's non-string
52
53
  `className`. Use `getAttribute('class')` or an equivalent safe reader. Resolution does not prove
53
54
  the rule wins the cascade, paints the intended result, or covers a state never enumerated.
54
55
 
@@ -68,14 +69,22 @@ a pass label alone.
68
69
 
69
70
  ## Style escapes
70
71
 
71
- - **Property.** Authored markup carries no `style` attribute or `<style>` element.
72
+ - **Property.** Authored markup carries no `style` attribute, and the surface's only `<style>`
73
+ element is the standalone-HTML project stylesheet in `<head>` that
74
+ [SKILL.md](../SKILL.md) → The styling ladder permits. A component-scoped `<style>` block fails.
72
75
  - **Population.** Authored templates and the freshly mounted, undriven tree. Keep source and mounted
73
76
  readings distinct so generated framework styles are not mistaken for authored declarations.
74
- - **Reading.** Report inline declarations and embedded style elements with their source or element.
75
- Record framework/runtime exemptions by producer and purpose, never a blanket component exemption.
76
- Bootstrap overlay positioning and conditional-visibility directives may write runtime styles.
77
+ - **Reading.** Report inline declarations and non-permitted style elements with their source or
78
+ element. Record each runtime exemption by producer, element, and the property it writes — never a
79
+ blanket component exemption, and never a producer that writes a property outside its purpose.
80
+ Bootstrap overlay positioning and conditional-visibility directives write runtime styles. The host
81
+ script for progress owns the `width` declaration on the elements specified in
82
+ [components.md](components.md) → Progress.
77
83
  - **Negative control.** Feed an element with an inline declaration, then append an inline-styled
78
- element and a `<style>` element to the harness tree. Every non-exempt fixture must be reported.
84
+ element and a component-scoped `<style>` element to the harness tree. Every non-exempt fixture
85
+ must be reported. Add a permitted fixture the reader must leave alone — the project stylesheet's
86
+ own `<head>` block, and a recorded runtime producer writing only its declared property — so the
87
+ reader cannot pass by rejecting every style element.
79
88
  - **Coverage.** This covers authored and mount-time escapes, not later interactions or third-party
80
89
  internals outside the declared scope. Drive later states separately when making claims about them.
81
90
 
@@ -134,7 +143,7 @@ a pass label alone.
134
143
  - **Population.** Rendered text, status marks, badges, tags, links, fields, selected controls, table
135
144
  cells, and overlays in the actual loaded build. Include supported nested modes, skin overrides,
136
145
  and portal mount points; name the boundaries that own an explicit foreground.
137
- - **Reading.** Drive the existing mounted tree from light to dark and back. Read computed text,
146
+ - **Reading.** Drive the existing mounted tree through every declared mode and back. Read computed text,
138
147
  painted backgrounds, relevant custom properties, and winning declarations after each transition.
139
148
  Confirm quiet ordinary text matches its intended inherited foreground; identify the owner when
140
149
  a component legitimately differs. Check supported system preference and reload behavior when the
@@ -144,14 +153,15 @@ a pass label alone.
144
153
  foreground/background pair. Require the reader to detect each violated contract. For projects
145
154
  using aliases, add a root-resolved foreground alias inherited into an opposite-mode scope.
146
155
  Verify each control is invalid in that build; a class name alone does not establish the defect.
147
- - **Coverage.** Pair the cascade reading with Composited contrast; correct inheritance can still
156
+ - **Coverage.** Pair the cascade reading with Composited contrast in every declared theme; correct inheritance can still
148
157
  produce insufficient contrast on a changed surface. An isolated stock fixture establishes only
149
158
  that fixture's behavior, not the host skin or application. Unreached states and mounts stay open.
150
159
 
151
- ## Composited contrast in both themes
160
+ ## Composited contrast in every declared theme
152
161
 
153
- - **Property.** Every measured pairing meets the package bar: 4.5:1 for information-bearing text,
154
- 3:1 for meaningful textless marks and state/focus chrome, in each declared theme and reached state.
162
+ - **Property.** Every measured pairing meets the bars in [SKILL.md](../SKILL.md) → Surfaces, color,
163
+ contrast, in each declared theme and reached state. That section owns the bars; read them there
164
+ rather than from a copy here.
155
165
  - **Population.** Rendered text and meaningful graphics, their actual surfaces, and all paint layers
156
166
  affecting contrast. Name exemptions for disabled controls; do not exempt readable metadata.
157
167
  - **Reading.** Composite translucent backgrounds onto the opaque base and translucent foregrounds
@@ -178,6 +188,31 @@ a pass label alone.
178
188
  - **Coverage.** This proves registry consistency, not that the markup uses the correct glyph or that
179
189
  its optical size and contrast work. Capture the states that use the marks and inspect their names.
180
190
 
191
+ ## Fixed and adaptive pairing
192
+
193
+ - **Property.** Every text, icon, and border color resolves against the surface it sits on with
194
+ tokens of the same kind: adaptive on adaptive, or fixed on a fixed fill that names its own
195
+ foreground or carries a `data-bs-theme` scope.
196
+ - **Population.** Every element with a color class, and every component with a fixed foreground
197
+ or fill (`btn-outline-*`, `table-*`, `badge`, `progress-bar`), including template branches,
198
+ portals, and inline SVG with literal fills.
199
+ - **Reading.** For each element, resolve the nearest ancestor that paints a background — class,
200
+ component, or scope — and classify both sides from
201
+ [color-modes.md](color-modes.md#fixed-and-adaptive-classes). Report mixed pairs, fixed fills
202
+ with inherited text — including a `data-bs-theme` scope on a fixed fill that does not carry
203
+ `text-body` on the same element — fixed neutrals on adaptive surfaces (`bg-light`, `bg-white`,
204
+ `text-dark`), `text-dark-emphasis` inside a dark scope, and outline buttons outside a measured
205
+ scope. Report `text-muted`, `navbar-light`, `navbar-dark`, and `btn-close-white` under a separate
206
+ deprecation finding: they are 5.3 deprecations rather than pairing defects, and `text-muted` pairs
207
+ correctly. The scan predicts a pairing failure; the contrast instrument in each declared theme
208
+ establishes it.
209
+ - **Negative control.** Append `bg-light` with inherited text, `text-white` on `bg-body`,
210
+ `text-primary` inside a `data-bs-theme="dark"` region, and a `bg-dark` region with
211
+ `data-bs-theme="dark"` and plain text but no `text-body`, in a harness; each must be reported by
212
+ the same reader.
213
+ - **Coverage.** Mechanical class analysis only; it does not see custom CSS, `currentColor`
214
+ resolution, image content, or composited opacity. Measure those in the render.
215
+
181
216
  ## Responsive task and reflow
182
217
 
183
218
  - **Property.** The declared task remains readable and operable without unintended page overflow,
@@ -230,7 +265,7 @@ requested review round or campaign, use `orkestrel-polish-surface` rather than c
230
265
  right control after wrapping. Width serves the content; rails, forms, and tables use it deliberately.
231
266
  - **Type and reflow:** line length, baseline alignment, line-height, numeric comparison, and fallback
232
267
  text work at the declared widths and enlarged text. No essential content is clipped or hidden.
233
- - **Color, depth, and imagery:** light/dark transitions preserve hierarchy without gratuitous text overrides; color has a second encoding;
268
+ - **Color, depth, and imagery:** transitions between declared themes preserve hierarchy without gratuitous text overrides; color has a second encoding;
234
269
  elevation describes layers; crops and icon sizes preserve useful detail; the frame stays quiet.
235
270
  - **States and restraint:** first-use, filtered-empty, loading, partial, and error retain a useful
236
271
  next step. Long or missing content holds up. The signature belongs to the brief; accessories do not
@@ -241,13 +276,13 @@ Pair each such claim with the relevant instrument or interaction test and its ac
241
276
 
242
277
  ## When an authored rule is already earned
243
278
 
244
- Leave rung 4 to the developer, per [SKILL.md](../SKILL.md) → When custom CSS is justified. Write a
245
- rule without asking only when every condition holds:
279
+ Leave the authored rung to the developer, per [SKILL.md](../SKILL.md) → When custom CSS is
280
+ justified. Write a rule without asking only when every condition holds:
246
281
 
247
282
  - an instrument reports a vendor failure against a stated requirement, such as a focus ring below
248
283
  3:1 or information-bearing status text below 4.5:1;
249
284
  - the rule cites the instrument, failing reading, and required bar beside it;
250
- - rungs 1–3 cannot restore the requirement, and the rule repairs that failure without unrelated polish;
285
+ - the component, utility, and extension rungs cannot restore the requirement, and the rule repairs that failure without unrelated polish;
251
286
  - the rule uses `--bs-*` paint tokens and declared scales, and the repaired result is re-measured in
252
287
  every affected theme and state.
253
288
 
@@ -1,12 +1,13 @@
1
1
  # Responsive layout
2
2
 
3
- > Part of the `enterprise-bootstrap` package. Use before composing a screen, shell, toolbar,
3
+ > Part of the `enterprise-bootstrap` skill. Use before composing a screen, shell, toolbar,
4
4
  > form, overlay, or data view. Operate layer: [SKILL.md](../SKILL.md).
5
5
 
6
6
  ## Contents
7
7
 
8
8
  - [Declare the contract](#declare-the-contract)
9
9
  - [Build the base](#build-the-base)
10
+ - [Bootstrap's responsive surface](#bootstraps-responsive-surface)
10
11
  - [Expand by available space](#expand-by-available-space)
11
12
  - [Keep the task intact](#keep-the-task-intact)
12
13
  - [Handle navigation and overlays](#handle-navigation-and-overlays)
@@ -78,6 +79,55 @@ ship breakpoint variants. Do not invent `w-md-auto`, `overflow-lg-auto`, `positi
78
79
  `min-w-0`. Responsive sticky helpers are a separate shipped family. Take missing roles through
79
80
  [Layout and type extensions](bootstrap-reference.md#layout-and-type-extensions).
80
81
 
82
+ ## Bootstrap's responsive surface
83
+
84
+ Stock 5.3.8 thresholds are `min-width` breakpoints: `sm` 576, `md` 768, `lg` 992, `xl` 1200,
85
+ `xxl` 1400 px; `xs` has no infix, so an unprefixed class is the base and `sm-*` applies from
86
+ 576 px up. `.container` caps at 540 / 720 / 960 / 1140 / 1320 px; `container-{bp}` stays fluid
87
+ until its breakpoint; `container-fluid` always. Gutter and container padding are 1.5 rem, so a
88
+ 320 px viewport leaves 296 px of content and a 390 px viewport 366 px. Offcanvas panels are
89
+ 400 px wide (`w-100` on narrow viewports); modals are 300 / 500 / 800 / 1140 px.
90
+
91
+ Families that ship breakpoint infixes, read from the utilities map: `d-*`, `flex-*`,
92
+ `justify-content-*`, `align-items-*`, `align-self-*`, `align-content-*`, `order-*`, `float-*`,
93
+ `gap-*`, `row-gap-*`, `column-gap-*`, every `m*`/`p*` spacing class, `text-{bp}-start/center/end`,
94
+ `object-fit-*`; plus the grid (`col-*`, `row-cols-*`, `g-*`, `offset-*`), `container-*`,
95
+ `sticky-{bp}-top/bottom`, and the component thresholds `navbar-expand-*`, `offcanvas-*`,
96
+ `table-responsive-*`, `modal-fullscreen-*-down`, `dropdown-menu-{bp}-end/start`,
97
+ `list-group-horizontal-*`.
98
+
99
+ Families with no infix: `w-*`, `h-*`, `mw-*`, `vh-*`, `position-*`, `top/bottom/start/end-*`,
100
+ `overflow-*`, `border-*`, `rounded-*`, `shadow-*`, `fs-*`, `fw-*`, `lh-*`, `text-nowrap`,
101
+ `text-truncate`, `text-uppercase`, `opacity-*`, `hstack`/`vstack`, `btn-group-vertical`. Write
102
+ `d-flex flex-column flex-md-row gap-3` where a stack must become a row, and generate `w-md-auto`
103
+ or `overflow-lg-visible` through the utilities API with `responsive: true` when a role needs it.
104
+ That key reaches a `$utilities` entry and nothing else, so it generates `w-*` and `overflow-*`
105
+ infixes but cannot reach a component threshold, a grid class, or a helper such as `hstack`; change
106
+ the component's own breakpoint class instead
107
+ ([bootstrap-reference.md](bootstrap-reference.md) → Utilities API).
108
+
109
+ Recipes:
110
+
111
+ - **Table scroller.** `<div class="table-responsive" role="region" aria-label="Invoices"
112
+ tabindex="0">` — the shipped class is `overflow-x: auto` only; the name and `tabindex` make it
113
+ keyboard-reachable. `table-responsive-{bp}` scrolls only below the breakpoint.
114
+ - **Menus in a scroller.** A scroller clips its `dropdown-menu`. Add
115
+ `data-bs-popper-config='{"strategy":"fixed"}'` to the toggle, or open row actions in a
116
+ root-mounted dialog.
117
+ - **Dual representations.** Render the narrow list and the wide table from one data array and one
118
+ selection set keyed by record id. Hide the inactive view with `d-none d-lg-block` /
119
+ `d-lg-none` so only one is in the accessibility tree; keep every `id` unique per view; re-sync
120
+ selection, sort, and filter state into whichever view is active. A view that resolves is not a
121
+ second store.
122
+ - **Toolbar base.** `d-grid gap-2 d-sm-flex flex-sm-wrap` stretches controls at the base and
123
+ releases them from `sm`; give search its own `col-12 col-md` row.
124
+ - **Touch.** `btn` is 38 px tall at the default size, `btn-sm` 31 px, `btn-lg` 48 px. Primary
125
+ mobile controls take `btn` or `btn-lg`, never a scaled-up icon inside `btn-sm`.
126
+ - **Pager.** Keep previous/next and the current page at every width; hide other numbers with
127
+ `d-none d-sm-block` on the `page-item` before shrinking targets.
128
+ - **Joined groups.** A `btn-group` bent over two rows loses its shared corners; below the width
129
+ where its labels fit, use a `form-select` or independent wrapping buttons.
130
+
81
131
  ## Expand by available space
82
132
 
83
133
  Measure the content container after rails, gutters, and panel padding. A wide viewport can contain
@@ -131,10 +181,10 @@ Keep record actions outside a clipped table wrapper when necessary. Popper place
131
181
  not guarantee escape from an overflow ancestor. Prefer a root-mounted dialog or an existing
132
182
  portal implementation over z-index escalation.
133
183
 
134
- Make touch targets comfortable without making text larger: prefer 44×44 CSS px for primary mobile
135
- controls; retain the package's 24×24 minimum for every applicable target. Enlarge the actual button
136
- or associated label, not merely the icon's surrounding decoration. Keep action affordances visible
137
- without hover. Measure effective label hit areas for native checkboxes and switches.
184
+ Make touch targets comfortable without making text larger; take every dimension from
185
+ [bootstrap-reference.md](bootstrap-reference.md) → WCAG 2.2 requirements for app UI, which owns the target floor and
186
+ the mobile preference. Keep action affordances visible without hover. Measure effective label hit
187
+ areas for native checkboxes and switches.
138
188
 
139
189
  ## Handle navigation and overlays
140
190
 
@@ -168,7 +218,7 @@ Run [Responsive task and reflow](inspection.md#responsive-task-and-reflow) and
168
218
  [Responsive interaction continuity](inspection.md#responsive-interaction-continuity). Check 320 and
169
219
  390 CSS px, one wide view, and `b−1`, `b`, `b+1` for each used breakpoint; deduplicate overlaps.
170
220
  Check narrow/short landscape, long unbroken identifiers, expanded copy, enlarged text, and the
171
- states actually used. Cross light/dark with the important narrow/wide states.
221
+ states actually used. Cross every declared mode with the important narrow/wide states.
172
222
 
173
223
  Require both geometry and task evidence. A page with no horizontal overflow can still hide its
174
224
  primary action, clip a menu, or push decision fields into an undiscoverable scroller. Conversely,
@@ -182,6 +232,6 @@ viewport, device-pixel-ratio change, or emulated touch a real-device or complete
182
232
  Take upstream behavior from Bootstrap's [breakpoints](https://getbootstrap.com/docs/5.3/layout/breakpoints/),
183
233
  [grid](https://getbootstrap.com/docs/5.3/layout/grid/), [flex](https://getbootstrap.com/docs/5.3/utilities/flex/),
184
234
  [offcanvas](https://getbootstrap.com/docs/5.3/components/offcanvas/), and
185
- [tables](https://getbootstrap.com/docs/5.3/content/tables/). Distinguish this package's test matrix
235
+ [tables](https://getbootstrap.com/docs/5.3/content/tables/). Distinguish this skill's test matrix
186
236
  from the requirements and two-dimensional-content exception in
187
237
  [WCAG reflow](https://www.w3.org/WAI/WCAG22/Understanding/reflow.html).
@@ -1,6 +1,6 @@
1
1
  # Bootstrap 5 Utilities Reference
2
2
 
3
- > Part of the `enterprise-bootstrap` package. Bootstrap **5.3.x** class index +
3
+ > Part of the `enterprise-bootstrap` skill. Bootstrap **5.3.x** class index +
4
4
  > composition notes. Component markup: [components.md](components.md).
5
5
  > Theming, patterns, a11y: [bootstrap-reference.md](bootstrap-reference.md).
6
6
 
@@ -24,6 +24,8 @@
24
24
  .bg-opacity-10, .bg-opacity-25, .bg-opacity-50, .bg-opacity-75, .bg-opacity-100
25
25
  ```
26
26
 
27
+ `bg-body`, `bg-body-secondary`, `bg-body-tertiary`, and `bg-*-subtle` adapt; `bg-{theme}`, `bg-light`, `bg-dark`, `bg-white`, `bg-black`, and `bg-gradient` are fixed. Inherited text on a fixed fill is never a pair — measure it in each declared mode (stock `bg-light` measures 1.2:1 in dark); a fixed fill takes `text-bg-*`, its component's foreground, or a `data-bs-theme` scope with `text-body` on the same element (the scope changes variables only; plain text inherits the outer mode's painted color).
28
+
27
29
  Prefer `bg-body`, `bg-body-secondary`, `bg-body-tertiary`, and `bg-*-subtle` for quiet surfaces; inherit text without an added foreground class. Treat original contextual `bg-*`, including `bg-light` and `bg-dark`, as non-adaptive in stock 5.3. Take ownership and exceptions from [color-modes.md](color-modes.md).
28
30
 
29
31
  ### Borders
@@ -43,7 +45,7 @@ Use adaptive border roles for quiet separation. Measure boundaries needed to ide
43
45
 
44
46
  - **`border-{1..5}` sets `border-width` on every side.** On a component that already has a border (`.card`, `.alert`) it thickens the whole box. For a one-side accent, zero first, restore one side, then widen — `card border-0 border-top border-4 border-primary` — utility source order (`border` → `border-{side}` → `border-width`) makes it hold, and the cleared sides have no border style so their width never paints.
45
47
  - Strengthen a rule that reads too faint with `border-2` on its soft color, not with a darker color; heavier width keeps the softness.
46
- - `border-{color}` is the fixed brand color in both modes; check an accent against a dark `bg-*-subtle` before shipping it.
48
+ - `border-{color}` is the fixed brand color in light and dark; check an accent against a dark `bg-*-subtle` before shipping it.
47
49
 
48
50
  ### Colors (Text)
49
51
 
@@ -57,7 +59,7 @@ Use adaptive border roles for quiet separation. Measure boundaries needed to ide
57
59
  .text-opacity-25, .text-opacity-50, .text-opacity-75, .text-opacity-100
58
60
  ```
59
61
 
60
- Default ordinary text to inheritance. Original contextual `text-*` colors do not adapt in stock 5.3; body-role and `text-*-emphasis` colors do. `text-body-secondary` (body color at .75 alpha) clears 4.5:1 on every stock body surface in both modes; `text-body-tertiary` (.5 alpha) measures 3.0–4.1:1 and is decoration or disabled only. Neither, nor `text-white-50`, is a quiet tier on a colored fill — take the same-hue token from [color-modes.md](color-modes.md) → Text tiers. Do not add emphasis text automatically to subtle fills, and do not replace a component's native foreground without inspecting its state contract.
62
+ Default ordinary text to inheritance. `text-{theme}`, `text-white`, `text-black`, `text-light`, `text-dark`, and their `-50` variants are fixed in light and dark; `text-body*`, `text-muted` (alias), and `text-*-emphasis` adapt — pair them with adaptive surfaces only ([color-modes.md](color-modes.md) → Fixed and adaptive classes). Classification and deprecation are separate axes: `text-muted` adapts and pairs correctly, and 5.3 deprecates it, so replace it with `text-body-secondary` on the deprecation rather than on a contrast reading. `text-body-secondary` (body color at .75 alpha) clears 4.5:1 on every stock body surface in light and dark; `text-body-tertiary` (.5 alpha) measures 3.0–4.1:1 there and is decoration or disabled only. Those readings bound stock 5.3.8 — re-measure under a declared theme or skin. Neither, nor `text-white-50`, is a quiet tier on a colored fill — take the same-hue token from [color-modes.md](color-modes.md) → Text tiers. Do not add emphasis text automatically to subtle fills, and do not replace a component's native foreground without inspecting its state contract.
61
63
 
62
64
  ### Display
63
65
 
@@ -165,13 +167,15 @@ Opacity is not a text tier: it reads as disabled and lets the surface show throu
165
167
  .translate-middle, .translate-middle-x, .translate-middle-y
166
168
  ```
167
169
 
170
+ Responsive infixes: `d`, `flex`, `justify-content`, `align-*`, `order`, `float`, `gap`, spacing, `text-{bp}-start/center/end`, and `object-fit` ship them; `w`, `h`, `position`, `overflow`, `border`, `rounded`, `shadow`, `fs`, `fw`, `lh`, `text-nowrap`, `text-truncate`, `hstack`/`vstack` do not ([responsive-layout.md](responsive-layout.md) → Bootstrap's responsive surface). Generate a missing infix only where a `$utilities` entry owns the property; `hstack` and `vstack` are helpers with no entry, so no key makes them responsive ([bootstrap-reference.md](bootstrap-reference.md) → Utilities API).
171
+
168
172
  ### Shadows
169
173
 
170
174
  ```css
171
175
  .shadow-none, .shadow-sm, .shadow, .shadow-lg
172
176
  ```
173
177
 
174
- Three elevation steps: `shadow-sm` (`0 .125rem .25rem` at .075) for slightly raised cards and controls, `shadow` (`0 .5rem 1rem` at .15) for floating menus and a dragged item, `shadow-lg` (`0 1rem 3rem` at .175) for dialogs. Stock dropdowns, popovers, toasts, and modals all sit on `--bs-box-shadow`; lift a modal to the top step through `--bs-modal-box-shadow` ([bootstrap-reference.md](bootstrap-reference.md) → Elevation and depth). No shadow is a valid role.
178
+ Assign the shipped elevation steps by layer role: `shadow-sm` (`0 .125rem .25rem` at .075) for slightly raised cards and controls, `shadow` (`0 .5rem 1rem` at .15) for floating menus and a dragged item, `shadow-lg` (`0 1rem 3rem` at .175) for dialogs. Stock dropdowns, popovers, toasts, and modals all sit on `--bs-box-shadow`; lift a modal to the top step through `--bs-modal-box-shadow` ([bootstrap-reference.md](bootstrap-reference.md) → Elevation and depth). No shadow is a valid role.
175
179
 
176
180
  ### Sizing
177
181
 
@@ -370,7 +374,8 @@ flex-sm-wrap` or `col-md-auto` only when the container fits. Do not default to a
370
374
  - **RTL:** use `ms-*`/`me-*`/`ps-*`/`pe-*`, `text-start`/`text-end`, and logical custom properties;
371
375
  verify the matching RTL build and the content's writing direction.
372
376
  - **Density:** drive compact/comfortable variants from shared tokens or a wrapper, not scattered
373
- per-element tweaks. Dense data retains readable text and ≥24px control targets.
377
+ per-element tweaks. Dense data retains readable text and control targets at the floor in
378
+ [bootstrap-reference.md](bootstrap-reference.md) → WCAG 2.2 requirements for app UI.
374
379
  - **Boundaries and depth:** separate with spacing first, then a surface change, then a shadow, then
375
380
  a line: `bg-body-tertiary` panels instead of bordered ones; `card border-0 shadow-sm` on a page
376
381
  surface that differs from the card; `list-group-flush`, `accordion-flush`, `table-borderless`,
@@ -379,7 +384,7 @@ flex-sm-wrap` or `col-md-auto` only when the container fits. Do not default to a
379
384
  recognition survive. Assign `shadow-sm`, `shadow`, and `shadow-lg` by layer role; do not shadow
380
385
  every panel or replace the focus indicator with depth. A `bg-body` panel on `bg-body-tertiary`
381
386
  reads raised and `bg-body-secondary` inside `bg-body` reads inset — depth with no shadow, in
382
- both modes.
387
+ light and dark.
383
388
  - **Accents and decoration:** one accent border per region (see [Borders](#borders)); the shipped
384
389
  `nav-underline` is the active-item accent. Alternate `bg-body` and `bg-body-tertiary` sections
385
390
  before decorating; a `bg-primary-subtle` band emphasizes one panel. `bg-gradient` is a