@orkestrel/scaffold 0.0.64 → 0.0.66

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.
@@ -1,6 +1,6 @@
1
1
  # Bootstrap 5 Component Reference
2
2
 
3
- > Part of the `enterprise-bootstrap` package. Bootstrap **5.3.x** component
3
+ > Part of the `enterprise-bootstrap` skill. Bootstrap **5.3.x** component
4
4
  > markup + enterprise selection notes. Utility classes: [utilities.md](utilities.md).
5
5
  > Theming, forms deep-dive, JS lifecycle, patterns: [bootstrap-reference.md](bootstrap-reference.md).
6
6
 
@@ -22,7 +22,8 @@
22
22
 
23
23
  ### Content Components
24
24
 
25
- - Typography: `.h1`–`.h6`, `.display-1`–`.display-6`, `.lead`, `.small`
25
+ - Typography: `.h1`–`.h6`, `.display-1`–`.display-6`, `.lead`, `.small` (sizes, weights, and the `.small` `em` trap: [utilities.md](utilities.md) → Text)
26
+ - Lists and quotes: `.list-unstyled`, `.list-inline`, `.blockquote`, `.blockquote-footer` — icon bullets and promoted quotes per [utilities.md](utilities.md) → Composition habits
26
27
  - Images: `.img-fluid`, `.img-thumbnail`, `.figure`
27
28
  - Tables: `.table` plus its `.table-*` tone classes — see [Tables](#tables)
28
29
  - Figures: `.figure`, `.figure-img`, `.figure-caption`
@@ -41,6 +42,10 @@ Full form patterns and validation JS: [bootstrap-reference.md](bootstrap-referen
41
42
 
42
43
  ## Component Markup
43
44
 
45
+ Preserve the component's native foreground and state rules. For quiet custom surfaces, inherit text
46
+ and use adaptive fills; take exceptions from [color-modes.md](color-modes.md). Do not copy a solid
47
+ variant merely because it appears in this catalog.
48
+
44
49
  ### Accordion
45
50
 
46
51
  ```html
@@ -91,17 +96,17 @@ Modifier classes: `.accordion-flush` (edge-to-edge, no outer borders); omit `dat
91
96
  ### Alerts
92
97
 
93
98
  ```html
94
- <div class="alert alert-primary" role="alert">Primary alert</div>
95
- <div class="alert alert-success" role="alert">Success alert</div>
99
+ <div class="alert alert-primary" role="status">Sync complete</div>
100
+ <div class="alert alert-success" role="status">Changes saved</div>
96
101
  <div class="alert alert-danger" role="alert">Danger alert</div>
97
102
  <div class="alert alert-warning" role="alert">Warning alert</div>
98
103
 
99
- <div class="alert alert-primary d-flex align-items-center" role="alert">
104
+ <div class="alert alert-primary d-flex align-items-center" role="status">
100
105
  <svg class="bi flex-shrink-0 me-2" role="img" aria-label="Info:">...</svg>
101
106
  <div>Alert with icon</div>
102
107
  </div>
103
108
 
104
- <div class="alert alert-success alert-dismissible fade show" role="alert">
109
+ <div class="alert alert-success alert-dismissible fade show" role="status">
105
110
  <h4 class="alert-heading">Well done!</h4>
106
111
  <p>Content here.</p>
107
112
  <hr />
@@ -110,12 +115,24 @@ Modifier classes: `.accordion-flush` (edge-to-edge, no outer borders); omit `dat
110
115
  </div>
111
116
  ```
112
117
 
113
- `role="alert"` announces immediately when the element is injected into the DOM — right for errors and warnings. For calm status messages injected dynamically, prefer a polite live region (`role="status"`). Anything that _looks_ like an alert carries the alert role: styling and semantics disagree the moment a notice wears `.alert` chrome with no role, and an accessibility snapshot is what catches it. When to use alert vs toast vs banner: [bootstrap-reference.md](bootstrap-reference.md) → Feedback discipline.
118
+ Choose the announcement by urgency: `role="status"` for routine asynchronous results, `role="alert"` for urgent failures. Do not make every warning assertive or infer urgency from `.alert` styling. A static advisory does not require a live region merely because it has alert chrome. Take channel selection from [bootstrap-reference.md](bootstrap-reference.md) → Feedback discipline.
119
+
120
+ Keep the native `.alert-*` foreground, subtle background, and `.alert-link` states. Do not add blanket `text-reset` or text-color utilities to alerts. Measure their children on the actual fill; see [color-modes.md](color-modes.md) → Alerts, buttons, and selection.
114
121
 
115
- An alert is a subtle fill — apply the subtle-fill degradation rule to everything inside it ([SKILL.md](../SKILL.md) → Surfaces, color, contrast).
122
+ A side accent is an accessory, one per region: `alert alert-warning border-0 border-start border-4 border-warning`. Zero the alert's own border first — `border-4` alone widens all four sides — and check the fixed `border-warning` against the dark `bg-warning-subtle` ([utilities.md](utilities.md) → Borders).
116
123
 
117
124
  ### Badge
118
125
 
126
+ Start quiet status with inherited text on a subtle fill. Use a plain span to avoid the badge's
127
+ built-in white foreground; when retaining `.badge`, use `text-reset` to restore inheritance:
128
+
129
+ ```html
130
+ <span class="d-inline-flex rounded-pill bg-success-subtle px-2 py-1 small fw-semibold">Paid</span>
131
+ <span class="badge bg-success-subtle text-reset">Paid</span>
132
+ ```
133
+
134
+ Reserve the solid variants for intentional prominence or a counter with its own paired surface:
135
+
119
136
  ```html
120
137
  <span class="badge text-bg-primary">Primary</span>
121
138
  <span class="badge text-bg-secondary">Secondary</span>
@@ -139,13 +156,13 @@ An alert is a subtle fill — apply the subtle-fill degradation rule to everythi
139
156
  </button>
140
157
  ```
141
158
 
142
- Use `text-bg-*` (auto-contrasting text) rather than `bg-*` alone. A badge is never the only carrier of meaning — pair color with text or a visually-hidden label.
159
+ For an intentional solid badge, use a measured `text-bg-*` pair rather than a solid background alone. Its foreground is selected at Sass build time, not recalculated on a runtime theme change. Never make color the only carrier of meaning; keep visible text or an accessible label.
143
160
 
144
- **A badge is never a textless mark.** Stock Bootstrap ships `.badge:empty { display: none }`, so an empty `<span class="badge">` used as a status dot renders nothing at all — the surface silently loses the state it claimed to show, and source review never sees it. A textless status mark is an **icon glyph** (see [Icons](#icons) → Status glyph marks), not a stripped badge.
161
+ **A badge is never a textless mark.** Stock Bootstrap ships `.badge:empty { display: none }`, so an empty `<span class="badge">` used as a status dot renders nothing. Draw a textless status mark as an **icon glyph** (see [Icons](#icons) → Status glyph marks), never as a stripped badge, and confirm the mark in a capture — source review cannot see the missing paint.
145
162
 
146
- **A badge's fill is never assumed.** Stock `.badge` carries no background of its own, but compatible skins may give it one, so an "unfilled" badge can arrive painted and land at a contrast the design never intended. State the fill explicitly — `bg-*-subtle` for a muted badge, `bg-transparent` when the surface behind it must show through — and measure the result in both themes against the cascade the page actually loads.
163
+ State a badge's intended fill and inspect the skin. For a quiet badge use `bg-*-subtle text-reset`; for an unfilled badge use `bg-transparent text-reset`. Verify the inherited foreground on that actual surface in every declared theme. A fill-only utility does not remove `.badge`'s white text; see [color-modes.md](color-modes.md) → Badges and removable tags.
147
164
 
148
- A badge reporting an in-flight request is a live region: `role="status"` on the badge (or on the small wrapper that holds it) announces the settled state politely without stealing focus. Reserve `role="alert"` for alert-styled notices ([Alerts](#alerts)).
165
+ A badge reporting an in-flight request is a live region: `role="status"` on the badge (or on the small wrapper that holds it) announces the settled state politely without stealing focus. Reserve `role="alert"` for urgent results ([Alerts](#alerts)).
149
166
 
150
167
  ### Breadcrumb
151
168
 
@@ -163,6 +180,8 @@ The current page is `aria-current="page"` and not a link. Use breadcrumbs only f
163
180
 
164
181
  ### Buttons
165
182
 
183
+ Solid variants own a fixed foreground/background pair and read the same in light and dark (primary and danger at 4.5:1, on the line). Outline variants paint a fixed label on the adaptive page surface: `btn-outline-secondary` is 4.7:1 on the light body and 3.3:1 on the dark one, `btn-outline-primary` 4.5 | 3.4. Lift the neutral once at the theme root — `.btn-outline-secondary { --bs-btn-color: var(--bs-emphasis-color); }` — and give an adaptive primary outline `--bs-btn-color`/`--bs-btn-border-color: var(--bs-link-color)`; `btn-link` adapts already. `btn-light`/`btn-dark` and `btn-outline-light`/`-dark` belong inside fixed fills of the opposite tone ([color-modes.md](color-modes.md) → Fixed and adaptive classes).
184
+
166
185
  ```html
167
186
  <button type="button" class="btn btn-primary">Primary</button>
168
187
  <button type="button" class="btn btn-secondary">Secondary</button>
@@ -186,12 +205,18 @@ The current page is `aria-current="page"` and not a link. Use breadcrumbs only f
186
205
  <button type="button" class="btn btn-primary" data-bs-toggle="button">Toggle</button>
187
206
  ```
188
207
 
189
- Icon-only buttons need `aria-label` and a ≥24×24 px target (WCAG 2.2) — `btn-sm` icon clusters in toolbars are the common violation; pad rather than shrink.
208
+ Icon-only buttons need `aria-label` and a target meeting the floor in [bootstrap-reference.md](bootstrap-reference.md) → WCAG 2.2 requirements for app UI — `btn-sm` icon clusters in toolbars are the common violation; pad rather than shrink.
209
+
210
+ The shipped sizes scale padding faster than font — `btn-sm` 4/8 px at 14 px, `btn` 6/12 at 16 px, `btn-lg` 8/16 at 20 px — so a large button reads as larger, not zoomed. Use them as shipped; do not derive another size with `em` padding. Weight is `$font-weight-normal`; `fw-semibold` on a button is a deliberate emphasis choice, not a default.
190
211
 
191
- Choose the `btn-*` class by contrast rather than by taste ([SKILL.md](../SKILL.md) → Hierarchy & actions).
212
+ Choose action rank, then a variant whose rest, hover, focus, active/checked, and disabled treatment works on its actual surface ([SKILL.md](../SKILL.md) → Hierarchy & actions). Do not override native button states with background or text utilities.
192
213
 
193
214
  ### Button Group
194
215
 
216
+ Keep joined groups on one line only while their labels and targets fit. For narrow filters or
217
+ review controls, use a select or independently spaced wrapping buttons; `flex-wrap` alone does
218
+ not make joined corners and shared borders into a coherent multiline group.
219
+
195
220
  ```html
196
221
  <div class="btn-group" role="group" aria-label="Basic example">
197
222
  <button type="button" class="btn btn-primary">Left</button>
@@ -219,7 +244,7 @@ Choose the `btn-*` class by contrast rather than by taste ([SKILL.md](../SKILL.m
219
244
  <img src="..." class="card-img-top" alt="..." />
220
245
  <div class="card-body">
221
246
  <h5 class="card-title">Title</h5>
222
- <h6 class="card-subtitle mb-2 text-body-secondary">Subtitle</h6>
247
+ <h6 class="card-subtitle mb-2">Subtitle</h6>
223
248
  <p class="card-text">Text content.</p>
224
249
  <a href="#" class="card-link">Link</a>
225
250
  <a href="#" class="btn btn-primary">Button</a>
@@ -227,15 +252,23 @@ Choose the `btn-*` class by contrast rather than by taste ([SKILL.md](../SKILL.m
227
252
  <ul class="list-group list-group-flush">
228
253
  <li class="list-group-item">Item</li>
229
254
  </ul>
230
- <div class="card-footer text-body-secondary">Footer</div>
255
+ <div class="card-footer">Footer</div>
231
256
  </div>
232
257
 
233
- <div class="card text-bg-primary">Colored card</div>
234
- <div class="card border-primary">Bordered card</div>
258
+ <div class="card bg-primary-subtle">Quiet tinted card</div>
259
+ <div class="card border-primary-subtle">Quiet bordered card</div>
260
+ <div class="card border-0 shadow-sm">
261
+ Borderless raised card — page surface must differ from the card's
262
+ </div>
263
+ <div class="card border-0 border-top border-4 border-primary">
264
+ Top accent — border-0 first, or border-4 widens every side
265
+ </div>
235
266
  <div class="card-group">Card group</div>
236
267
  <div class="row row-cols-1 row-cols-md-3 g-4">Card grid (with h-100 on cards)</div>
237
268
  ```
238
269
 
270
+ Use `card` only where a group earns containment; try spacing and a surface change first. Card headers and footers are a 3 % tint of the body color, so `border-0` on `card-header` often reads cleaner than the shipped rule. One accent per region ([utilities.md](utilities.md) → Borders).
271
+
239
272
  ### Carousel
240
273
 
241
274
  ```html
@@ -356,6 +389,8 @@ Multiple targets: give each panel `.multi-collapse` and point separate triggers
356
389
 
357
390
  `.dropdown-menu-dark` is deprecated — use `data-bs-theme="dark"` on the menu or an ancestor. Dropdowns have full keyboard support (arrows, Esc) built in. A dropdown is a **command menu** — for choosing a form value use `.form-select`, never a styled dropdown pretending to be an input.
358
391
 
392
+ A menu is a floating surface, not only a list of links: give it `dropdown-header` sections, a `dropdown-divider`, an icon, and a `small text-body-secondary` line under a `fw-semibold` label inside each `dropdown-item`, or a `row` of columns in a `p-3` menu, while keeping `dropdown-item` semantics on every choice.
393
+
359
394
  ### List Group
360
395
 
361
396
  ```html
@@ -404,6 +439,10 @@ Multiple targets: give each panel `.multi-collapse` and point separate triggers
404
439
 
405
440
  ### Modal
406
441
 
442
+ Keep width bounded and all actions vertically reachable on short viewports and enlarged text.
443
+ Use `modal-fullscreen-*-down` only with Bootstrap modal markup; a native `<dialog>` needs its own
444
+ measured sizing contract. Keep row-action dialogs outside table overflow ancestors.
445
+
407
446
  **Build a blocking dialog on the native `<dialog>`.** `showModal()` brings focus containment, Esc, an inert background, and top-layer stacking from the platform — nothing to construct, nothing to dispose when the view unmounts, and no JS instance for a virtual-DOM framework to fight with over the same nodes. Leave the element itself unpainted and put Bootstrap chrome inside it:
408
447
 
409
448
  ```html
@@ -467,6 +506,8 @@ Bootstrap's modal enforces focus, adds `role="dialog"`/`aria-modal="true"`, clos
467
506
 
468
507
  ### Navbar
469
508
 
509
+ Stock 5.3 navbars are adaptive on `bg-body-tertiary`. `navbar-light` and `navbar-dark` are deprecated: a dark brand bar is `navbar bg-dark` with `data-bs-theme="dark"` plus `text-body` on the same element — the scope re-aligns the toggler icon, links, and form controls, which resolve their own color variables, while plain text inherits the outer mode's painted color — never `bg-dark` alone with inherited text. Match `navbar-expand-{bp}` to where the full destination set fits; the toggler must remain a named `navbar-toggler` with `aria-controls` and `aria-expanded`.
510
+
470
511
  ```html
471
512
  <nav class="navbar navbar-expand-lg bg-body-tertiary">
472
513
  <div class="container-fluid">
@@ -504,7 +545,9 @@ Bootstrap's modal enforces focus, adds `role="dialog"`/`aria-modal="true"`, clos
504
545
  <nav class="navbar bg-body-tertiary sticky-top">Sticky top</nav>
505
546
 
506
547
  <!-- Dark navbar: .navbar-dark is DEPRECATED — scope the theme instead -->
507
- <nav class="navbar bg-primary" data-bs-theme="dark">Dark-themed navbar</nav>
548
+ <nav class="navbar bg-body-tertiary" data-bs-theme="dark">
549
+ <a class="navbar-brand" href="#">Dark-themed navbar</a>
550
+ </nav>
508
551
  ```
509
552
 
510
553
  ### Navs & Tabs
@@ -592,6 +635,10 @@ Real switchable tab panels (JS-driven — buttons, not scroll anchors):
592
635
 
593
636
  ### Offcanvas
594
637
 
638
+ Take narrow/inline thresholds, trigger parity, and open-resize-close tests from
639
+ [responsive-layout.md](responsive-layout.md#handle-navigation-and-overlays). Match trigger and panel
640
+ breakpoints; do not leave a hidden focus trap or scroll lock after expansion.
641
+
595
642
  ```html
596
643
  <button
597
644
  class="btn btn-primary"
@@ -629,6 +676,10 @@ Real switchable tab panels (JS-driven — buttons, not scroll anchors):
629
676
 
630
677
  ### Pagination
631
678
 
679
+ On narrow screens, retain the current page and previous/next controls; reduce numbered links
680
+ before shrinking targets. Keep pagination outside the table scroller and preserve page state
681
+ when the presentation changes.
682
+
632
683
  ```html
633
684
  <nav aria-label="Search results pages">
634
685
  <ul class="pagination">
@@ -657,7 +708,7 @@ Real switchable tab panels (JS-driven — buttons, not scroll anchors):
657
708
  <p aria-hidden="true">
658
709
  <span class="placeholder col-6"></span>
659
710
  <span class="placeholder w-75"></span>
660
- <span class="placeholder" style="width: 25%;"></span>
711
+ <span class="placeholder w-25"></span>
661
712
  </p>
662
713
 
663
714
  <span class="placeholder col-12 placeholder-lg">Large</span>
@@ -670,7 +721,7 @@ Real switchable tab panels (JS-driven — buttons, not scroll anchors):
670
721
  <button class="btn btn-primary disabled placeholder col-4" aria-hidden="true"></button>
671
722
  ```
672
723
 
673
- Always wrap skeletons in `aria-hidden="true"` — they are visual scaffolding, not content. Skeleton-vs-spinner decision rules: [bootstrap-reference.md](bootstrap-reference.md) → The data states.
724
+ Always wrap skeletons in `aria-hidden="true"` — they are visual scaffolding, not content. Size a placeholder with `col-*` or a shipped width utility; a skeleton width is a layout decision, not a runtime value, so it never takes a `style` attribute. Skeleton-vs-spinner decision rules: [bootstrap-reference.md](bootstrap-reference.md) → The data states.
674
725
 
675
726
  ### Popover (Requires Popper.js)
676
727
 
@@ -701,6 +752,8 @@ Popovers are **opt-in**: they do nothing until initialized in JS (see [JavaScrip
701
752
 
702
753
  ### Progress
703
754
 
755
+ For dynamic progress, have the host script set `width` on `.progress-bar` in a standalone `.progress`, or on each `.progress` segment in `.progress-stacked`. Derive the painted width and the matching `aria-valuenow` from the same progress value. Record that script by name and purpose under [inspection.md](inspection.md) → Style escapes. Exempt only those elements' producer-written `width` declarations. Keep all other widths in shipped utilities or the project stylesheet.
756
+
704
757
  5.3 markup — `role="progressbar"` and the `aria-value*` attributes go on the **outer `.progress`**, not the inner bar:
705
758
 
706
759
  ```html
@@ -807,11 +860,11 @@ Gotcha: the spied element must be a scroll container (height/overflow, or focusa
807
860
  ### Spinners
808
861
 
809
862
  ```html
810
- <div class="spinner-border text-primary" role="status">
863
+ <div class="spinner-border" role="status">
811
864
  <span class="visually-hidden">Loading...</span>
812
865
  </div>
813
866
 
814
- <div class="spinner-grow text-primary" role="status">
867
+ <div class="spinner-grow" role="status">
815
868
  <span class="visually-hidden">Loading...</span>
816
869
  </div>
817
870
 
@@ -826,6 +879,8 @@ Gotcha: the spied element must be a scroll container (height/overflow, or focusa
826
879
 
827
880
  ### Tables
828
881
 
882
+ `table-*` row variants are fixed pairs (`--bs-table-bg` tint with `--bs-table-color: #000`) and read as light rows inside a dark table; prefer a `bg-*-subtle` status mark in a cell. `table-responsive` is `overflow-x: auto` only — give the wrapper `role="region"`, an `aria-label`, and `tabindex="0"` so keyboard users can reach its far edge, and add `data-bs-popper-config='{"strategy":"fixed"}'` to any dropdown toggle inside it ([responsive-layout.md](responsive-layout.md) → Bootstrap's responsive surface).
883
+
829
884
  ```html
830
885
  <table class="table">
831
886
  <caption class="visually-hidden">
@@ -841,14 +896,14 @@ Gotcha: the spied element must be a scroll container (height/overflow, or focusa
841
896
  <tbody>
842
897
  <tr>
843
898
  <th scope="row">INV-1042</th>
844
- <td><span class="badge text-bg-success">Paid</span></td>
899
+ <td><span class="badge bg-success-subtle text-reset">Paid</span></td>
845
900
  <td class="text-end">$1,280.00</td>
846
901
  </tr>
847
902
  </tbody>
848
903
  </table>
849
904
  ```
850
905
 
851
- Modifiers (combine freely):
906
+ Combine structural modifiers as needed; choose color variants separately:
852
907
 
853
908
  ```css
854
909
  .table-sm /* half padding — dense screens */
@@ -863,8 +918,8 @@ Modifiers (combine freely):
863
918
  ```
864
919
 
865
920
  - **Responsive:** wrap in `.table-responsive{-sm|-md|-lg|-xl|-xxl}` for horizontal scroll. Caveat: the wrapper clips overflowing content — dropdown menus inside a responsive table get cut off.
866
- - **Dark tables:** `data-bs-theme="dark"` on the `<table>` (the `.table-dark` class approach is superseded).
867
- - **Theming:** the `.table-*` tone classes set CSS variables, not fixed colors — `--bs-table-bg`, `--bs-table-color`, `--bs-table-striped-bg`, `--bs-table-hover-bg`, `--bs-table-active-bg`, `--bs-table-border-color`. `--bs-table-bg` is transparent by default so striping/hover layer through.
921
+ - **Color modes:** let the uncolored `.table` follow the page. Use `data-bs-theme="dark"` only for an intentional local mode, not as a permanent setting on a table that must follow the toggle.
922
+ - **Theming:** treat `.table-*` color variants as non-adaptive in stock 5.3; their CSS variables contain Sass-generated colors. The base table background uses the body background; the transparent default belongs to `--bs-table-accent-bg`. Inspect painted cells and their state overlays; see [color-modes.md](color-modes.md) → Tables and overlays.
868
923
  - **Sticky headers are NOT built in.** Bootstrap ships no sticky-header feature; the pattern needs a few lines of custom CSS. That, plus selection columns, `aria-sort` sorting, bulk-action bars, and responsive strategies: [bootstrap-reference.md](bootstrap-reference.md) → Dense data tables.
869
924
 
870
925
  ### Toasts
@@ -879,13 +934,12 @@ Modifiers (combine freely):
879
934
  <div class="toast-body">Changes published.</div>
880
935
  </div>
881
936
 
882
- <div class="toast align-items-center text-bg-primary border-0" role="status" aria-live="polite">
937
+ <div class="toast align-items-center bg-primary-subtle border-0" role="status" aria-live="polite">
883
938
  <div class="d-flex">
884
939
  <div class="toast-body">Color tone</div>
885
940
  <button
886
941
  type="button"
887
942
  class="btn-close me-2 m-auto"
888
- data-bs-theme="dark"
889
943
  data-bs-dismiss="toast"
890
944
  aria-label="Close"
891
945
  ></button>
@@ -926,7 +980,7 @@ Toasts are **opt-in** — hidden until `.show()` is called (or shown through a t
926
980
  </button>
927
981
  ```
928
982
 
929
- Tooltips are **opt-in** (JS init required, below). Only attach to focusable elements so keyboard users can trigger them; never put essential information _only_ in a tooltip, and never report form errors through a tooltip. `data-bs-html` with untrusted content is an XSS vector.
983
+ Tooltips are **opt-in** (JS init required; see [JavaScript initialization](#javascript-initialization)). Only attach to focusable elements so keyboard users can trigger them; never put essential information _only_ in a tooltip, and never report form errors through a tooltip. `data-bs-html` with untrusted content is an XSS vector.
930
984
 
931
985
  ## JavaScript Initialization
932
986
 
@@ -957,18 +1011,14 @@ Bootstrap's core CSS ships **no icons**. The `.bi` SVGs in examples come from th
957
1011
 
958
1012
  ### Status glyph marks
959
1013
 
960
- The textless mark that survives both themes — dots, ticks, rings, pulses — is a glyph, not a badge ([Badge](#badge)). Inline SVG or icon font, the composition rules are the same:
1014
+ For a textless status mark — a dot, tick, ring, or pulse — use a glyph rather than a badge ([Badge](#badge)). Apply the following composition rules to inline SVG and icon fonts:
961
1015
 
962
1016
  ```html
963
- <span
964
- class="bi bi-circle-fill fs-6 lh-1 text-success-emphasis"
965
- role="img"
966
- aria-label="Healthy"
967
- ></span>
968
- <span class="bi bi-circle fs-6 lh-1 text-body-secondary" role="img" aria-label="Not started"></span>
1017
+ <span class="bi bi-circle-fill fs-6 lh-1" role="img" aria-label="Healthy"></span>
1018
+ <span class="bi bi-circle fs-6 lh-1" role="img" aria-label="Not started"></span>
969
1019
  ```
970
1020
 
971
- - **Color from the emphasis tokens.** `text-*-emphasis` is the mode-adaptive tier built for marks on subtle surfaces; the plain `text-*` colors are tuned for light and thin out in dark. On a filled surface — `.active`, `.bg-primary`, `text-bg-*` — drop the tone class instead and let the fill's contrast color take the glyph ([Selection fills](#selection-fills)). Measure every mark at **≥ 3:1** against the surface it sits on, **in both themes**, against the compiled cascade — a skin's token values are its own.
1021
+ - **Inherit the owning foreground.** Keep ordinary glyphs on body or component text, including selected fills. Add `text-*-emphasis` only for a deliberate semantic tint on a known, measured surface; never apply it to every mark on a subtle fill. Measure meaningful marks against the bar in [SKILL.md](../SKILL.md) → Surfaces, color, contrast, in every declared theme and reached state. Take cascade exceptions from [color-modes.md](color-modes.md).
972
1022
  - **Filled and hollow say different things** — done vs pending, live vs idle — so pair glyphs that share one advance width (a filled/hollow pair from the same icon family). Mixed widths make a column of marks jitter row to row.
973
1023
  - **Size with `fs-*` _and_ `lh-1`.** A glyph inherits the row's line-height, so an `fs-*` bump without `lh-1` grows the line box and pushes the row taller than its neighbors.
974
1024
  - Give the mark an accessible name (`role="img"` + `aria-label`, or a `.visually-hidden` word next to an `aria-hidden` glyph) — a mark whose only meaning is its color and shape is color-only status.
@@ -998,24 +1048,29 @@ The textless mark that survives both themes — dots, ticks, rings, pulses — i
998
1048
  - Prefer visible labels or `.form-floating` — placeholder-only labels fail accessibility and disappear on input.
999
1049
  - Pair help and errors with `aria-describedby`; use `.invalid-feedback` with `.is-invalid` and mark the field `aria-invalid="true"`.
1000
1050
  - Money/units: `.input-group` + `.input-group-text`; add `.has-validation` on groups with validation feedback.
1001
- - Show progress with `spinner-border spinner-border-sm` inside the submit button while waiting; keep submit enabled and validate on submit rather than disabling it ([bootstrap-reference.md](bootstrap-reference.md) → Forms in production).
1051
+ - Keep submit enabled while fields are invalid and validate on submit. Mark the button busy while a submit is in flight — `spinner-border spinner-border-sm` inside it, `aria-busy="true"` on it — and refuse a second submit; take the full rule from [bootstrap-reference.md](bootstrap-reference.md) → Forms in production.
1002
1052
 
1003
1053
  ### Selection fills
1004
1054
 
1005
- A selected row, pill, or filter chip repaints everything inside it — marks included. These traps stay invisible until the selected state is captured in both themes:
1055
+ Keep the component's selected foreground on ordinary labels and glyphs. Remove a competing
1056
+ semantic tint before changing its active fill; handle an independently filled badge through
1057
+ [color-modes.md](color-modes.md) → Alerts, buttons, and selection.
1006
1058
 
1007
- - **A mark on an active fill of the same family disappears.** `.active` on a `list-group-item`, `nav-pill`, or `page-item` sets the item's own color, and a `text-bg-primary`-family mark inside it inherits or loses to that fill — present in the markup, gone on screen. Carry no tone class inside the fill ([SKILL.md](../SKILL.md) → Surfaces, color, contrast). Verify by capturing the selected row, not by reading the class list.
1008
- - **`btn-check` filter labels invert in dark.** A `btn-outline-secondary` label reads as "chosen" in light and as "muted" in dark, because the checked fill and the surface swap relative weight. Give chosen filters an accent tone class (a real theme color) rather than the neutral outline, so "chosen" reads the same way in both modes.
1059
+ Verify that chosen and unchosen filters remain distinguishable in every declared theme. Do not assume
1060
+ that a neutral outline always inverts meaning or that an accent hue repairs it. Preserve the
1061
+ checked/pressed state and add a visible non-color cue when the fill alone is ambiguous.
1009
1062
 
1010
- Exactly one item in a selection carries `aria-current` — the visual fill and the announced state must be the same item.
1063
+ Use `aria-current` for current navigation, `aria-selected` for tabs, and native checked state for
1064
+ checkboxes/radios. Match the visual state to the applicable pattern; do not apply `aria-current`
1065
+ to every selection widget.
1011
1066
 
1012
1067
  ### Navigation & overlays
1013
1068
 
1014
1069
  - Active nav items need `aria-current="page"` (or `aria-selected="true"` for tabs).
1015
1070
  - Modals and offcanvas: set `aria-labelledby`; Bootstrap traps focus and restores it on close — do not fight it; `dispose()` instances when the host unmounts in SPAs.
1016
- - Icon-only controls always need an accessible name (`aria-label` or visually-hidden text) and a ≥24px target.
1071
+ - Icon-only controls always need an accessible name (`aria-label` or visually-hidden text) and a target meeting the floor in [bootstrap-reference.md](bootstrap-reference.md) → WCAG 2.2 requirements for app UI.
1017
1072
 
1018
1073
  ### Theming
1019
1074
 
1020
- - Components consume CSS variables — favor `text-bg-*`, `*-subtle`, and `data-bs-theme` over one-off colors; the deprecated `*-dark` component classes (`navbar-dark`, `dropdown-menu-dark`, `btn-close-white`, `carousel-dark`) all map to `data-bs-theme="dark"`.
1075
+ - Preserve native component colors and prefer adaptive quiet surfaces. Take ownership, solid exceptions, nested modes, and deprecated-class replacements from [color-modes.md](color-modes.md). Do not assume every component variable adapts.
1021
1076
  - To restyle a component, override its `--bs-{component}-*` variables in your own scope instead of writing high-specificity rules — see [bootstrap-reference.md](bootstrap-reference.md) → Theming & design tokens.