@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.
@@ -1,11 +1,12 @@
1
1
  # Color modes and inheritance
2
2
 
3
- > Part of the `enterprise-bootstrap` package. Use for foreground ownership, adaptive surfaces,
3
+ > Part of the `enterprise-bootstrap` skill. Use for foreground ownership, adaptive surfaces,
4
4
  > nested themes, and color-mode repairs. Take the contrast bars from [SKILL.md](../SKILL.md).
5
5
 
6
6
  ## Contents
7
7
 
8
8
  - [Choose the surface](#choose-the-surface)
9
+ - [Fixed and adaptive classes](#fixed-and-adaptive-classes)
9
10
  - [Text tiers](#text-tiers)
10
11
  - [Preserve the cascade](#preserve-the-cascade)
11
12
  - [Scope the mode](#scope-the-mode)
@@ -17,7 +18,7 @@
17
18
 
18
19
  Default to inherited text. When ordinary content owns no background, add no foreground override.
19
20
  For quiet containers and status, prefer adaptive backgrounds without an added text-color class.
20
- Treat this as the package default, not a claim that Bootstrap forbids its documented emphasis pairs.
21
+ Treat this as the skill's default, not a claim that Bootstrap forbids its documented emphasis pairs.
21
22
 
22
23
  | Context | Use | Refuse by default |
23
24
  | -------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
@@ -42,12 +43,115 @@ Measure the inherited result on its actual background. A subtle fill changes onl
42
43
  it cannot repair a fixed, translucent, or inverse foreground inherited from an ancestor. Remove
43
44
  that conflict or establish an owned surface boundary before adding a leaf override.
44
45
 
46
+ ## Fixed and adaptive classes
47
+
48
+ Every Bootstrap color class resolves through one of two kinds of variable. **Adaptive** classes
49
+ read a variable the `[data-bs-theme=dark]` block redefines — `--bs-body-color`/`-bg`,
50
+ `--bs-emphasis-color`, `--bs-secondary-color`/`-bg`, `--bs-tertiary-color`/`-bg`,
51
+ `--bs-*-text-emphasis`, `--bs-*-bg-subtle`, `--bs-*-border-subtle`, `--bs-border-color`,
52
+ `--bs-link-color` — and change with the mode. **Fixed** classes read `--bs-{theme}-rgb`,
53
+ `--bs-white-rgb`, `--bs-black-rgb`, `--bs-light-rgb`, or `--bs-dark-rgb`, which no mode
54
+ redefines, and paint the same value in light and dark.
55
+
56
+ This table classifies a class by the variable it reads, and nothing else. Deprecation is a separate
57
+ axis: `text-muted` reads the adaptive secondary color and is deprecated, so it classifies as
58
+ adaptive here and still gives way to `text-body-secondary` on its deprecation.
59
+
60
+ | Family | Adaptive | Fixed |
61
+ | ---------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
62
+ | Text | `text-body`, `text-body-emphasis`, `text-body-secondary`, `text-body-tertiary`, `text-muted` (alias), `text-*-emphasis` | `text-primary` … `text-danger`, `text-light`, `text-dark`, `text-white`, `text-black`, `text-white-50`, `text-black-50` |
63
+ | Background | `bg-body`, `bg-body-secondary`, `bg-body-tertiary`, `bg-*-subtle` | `bg-primary` … `bg-danger`, `bg-light`, `bg-dark`, `bg-white`, `bg-black`, `bg-gradient` |
64
+ | Border | `border` (default color, decorative at 1.3:1), `border-*-subtle` | `border-primary` … `border-dark`, `border-white`, `border-black` |
65
+ | Links | plain `<a>`, `btn-link`, `link-body-emphasis` | `link-primary` … `link-dark`, `link-light` |
66
+ | Own pairs | `.alert-*`, `.list-group-item-*`, `.form-control`, `.card`, `.dropdown-menu`, `.modal-content`, `.toast`, `.btn-close` | `text-bg-*`, solid `btn-*`, `btn-light`/`btn-dark`, `table-*` row variants, `progress-bar`, active nav/list/page states |
67
+ | No pair | — | `btn-outline-*`: a fixed label and border on whatever surface the page has |
68
+
69
+ **Pair like with like.** Adaptive text on an adaptive surface; fixed text on a fixed fill. Treat
70
+ every mixed pair as unproven until measured: some clear one mode and fail the other, and some fail
71
+ light and dark alike. Read the outcome from the measurement, never from the mixture.
72
+
73
+ The following readings bound stock Bootstrap 5.3.8 and nothing else. A declared theme, a skin, or an
74
+ overridden token re-points every value in the table, so measure that theme yourself rather than
75
+ carrying a stock number into it. Light | dark:
76
+
77
+ | Mixed pair | Light | Dark |
78
+ | ------------------------------------------------------- | ------------- | ------------- |
79
+ | `text-primary` / `-success` / `-danger` on `bg-body` | 4.5 | 3.4 ✗ |
80
+ | `text-secondary` on `bg-body` | 4.7 | 3.3 ✗ |
81
+ | `text-warning` / `text-info` on `bg-body` | 1.6 ✗ / 2.0 ✗ | 9.5 / 7.9 |
82
+ | `text-dark`, `text-black` on `bg-body` | 15.4 / 21.0 | 1.0 ✗ / 1.4 ✗ |
83
+ | `text-white`, `text-light` on `bg-body` | 1.0 ✗ / 1.1 ✗ | 15.4 / 14.6 |
84
+ | Inherited text on `bg-light` / `bg-white` | 14.6 / 15.4 | 1.2 ✗ / 1.3 ✗ |
85
+ | Inherited text on `bg-dark` / `bg-black` | 1.0 ✗ / 1.4 ✗ | 11.8 / 16.1 |
86
+ | Inherited text on `bg-primary` / `-danger` / `-success` | 3.4 ✗ | 3.5 ✗ |
87
+ | `btn-outline-secondary` label on `bg-body` | 4.7 | 3.3 ✗ |
88
+ | `btn-outline-primary` label on `bg-body` | 4.5 | 3.4 ✗ |
89
+ | `link-primary` (`--bs-primary-rgb`) on `bg-body` | 4.5 | 3.4 ✗ |
90
+
91
+ The adaptive pairs hold: `text-*-emphasis` on `bg-body` is 6.1–13.6 in light and dark and on
92
+ `bg-*-subtle` 7.2–10.8, with one exception — `text-dark-emphasis` is 3.7 on the dark body; use
93
+ `text-body-emphasis` for neutral emphasis. A plain link is 4.5 light and 6.4 dark because
94
+ `--bs-link-color` is redefined; `text-primary` and `link-primary` are not.
95
+
96
+ Rules:
97
+
98
+ - Default to adaptive on adaptive: inherited text on `bg-body*` or `bg-*-subtle`; status text as
99
+ `text-*-emphasis`; links as plain links or `link-body-emphasis`.
100
+ - A fixed fill needs a fixed foreground you can name: `text-bg-*` (white on primary, secondary,
101
+ success, and danger at 4.5–4.7; black on warning, info, and light; white on dark), the
102
+ component's own foreground, or a `data-bs-theme` scope that carries `text-body` on the same
103
+ element. Inherited text on a fixed fill is never a pair.
104
+ - A `data-bs-theme` scope redefines variables only. Text color is painted once, on `<body>`, from
105
+ the outer mode, and plain descendants inherit that computed color — not the variable. Inside
106
+ the scope only rules that resolve `color` through a variable re-align: `text-body*`,
107
+ `text-*-emphasis`, buttons, links, `list-group-item`, form controls, `btn-close`. A paragraph,
108
+ a `span`, a heading without a text class, and `.card` (its `--bs-card-color` is empty, so it
109
+ inherits) keep the outer mode's color. The symptom is a headline in `text-body-emphasis`
110
+ reading correctly beside a paragraph that vanishes. Restate the surface and the foreground on
111
+ the scope element: take `bg-body text-body` where the fill must follow the mode, and a fixed
112
+ `bg-*` plus `text-body` where it must not
113
+ (`<section class="bg-dark text-body" data-bs-theme="dark">`).
114
+ Check components that leave the scope: dropdown menus, and dialogs mounted at the root.
115
+ - Outline buttons carry a fixed label. Lift the neutral one once at the theme root —
116
+ `.btn-outline-secondary { --bs-btn-color: var(--bs-emphasis-color); }` — its `#6c757d` border
117
+ still clears the 3:1 boundary bar in light and dark (4.7 | 3.3). For an adaptive primary outline set
118
+ `--bs-btn-color` and `--bs-btn-border-color` to `var(--bs-link-color)`; `btn-link` already adapts.
119
+ `btn-outline-light` and `btn-outline-dark` belong only inside a fixed fill of the opposite tone.
120
+ - Shadows are fixed black alphas and nearly vanish on dark surfaces; carry depth with the
121
+ `bg-body*` steps in dark mode.
122
+ - Icon fonts and `fill="currentColor"` follow the text; a literal hex in an inline SVG or an
123
+ `<img>` is fixed. Give logos and illustrations a scoped variant or a `currentColor` path.
124
+
125
+ | Legacy or mixed | Adaptive replacement |
126
+ | ------------------------------------------------ | ------------------------------------------------------------------------- |
127
+ | `bg-light` panel | `bg-body-tertiary` |
128
+ | `bg-white` | `bg-body` |
129
+ | Neutral `bg-dark` region | `bg-dark` with `data-bs-theme="dark"`, or `bg-body` inside a dark scope |
130
+ | `text-dark`, `text-black` | `text-body-emphasis`, or inherit |
131
+ | `text-white` on an adaptive surface | inherit |
132
+ | `text-muted`, `text-black-50`, `text-white-50` | `text-body-secondary` |
133
+ | `text-primary` … `text-danger` as running text | `text-*-emphasis`, measured |
134
+ | `border-light`, `border-dark` | `border`, or `border-*-subtle` |
135
+ | `navbar-light`, `navbar-dark`, `btn-close-white` | `data-bs-theme` on the navbar or region |
136
+ | `badge bg-primary` with inherited text | `badge text-bg-primary`, or `badge bg-primary-subtle` with inherited text |
137
+ | `link-primary` … `link-dark` | plain link, or `link-body-emphasis` |
138
+
139
+ Read that table as a replacement map, not as a classification. Replace `bg-light`, `bg-white`,
140
+ `bg-dark`, `text-dark`, `text-black`, `text-white`, `text-black-50`, `text-white-50`,
141
+ `border-light`, `border-dark`, and the colored `text-*` and `link-*` families because they are
142
+ fixed. Replace `text-muted`, `navbar-light`, `navbar-dark`, and `btn-close-white` because 5.3
143
+ deprecates them — `text-muted` already adapts, so its replacement closes a deprecation rather than
144
+ a contrast defect.
145
+
45
146
  ## Text tiers
46
147
 
47
- Stock Bootstrap ships two readable text tiers, not three. `--bs-secondary-color` is the body color
48
- at 75 % alpha and `--bs-tertiary-color` at 50 % alpha, so both composite against whatever sits
49
- beneath them. Measured on the three stock body surfaces (`bg-body` / `bg-body-tertiary` /
50
- `bg-body-secondary`) in 5.3.8:
148
+ Stock Bootstrap ships the inherited body color and `text-body-secondary` as its readable tiers;
149
+ `text-body-tertiary` is not readable text. `--bs-secondary-color` is the body color at 75 % alpha
150
+ and `--bs-tertiary-color` at 50 % alpha, so each composites against whatever sits beneath it.
151
+
152
+ The following readings bound stock 5.3.8 on its own body surfaces (`bg-body` / `bg-body-tertiary` /
153
+ `bg-body-secondary`). Measure a declared theme or a skin yourself; its tokens re-point every value
154
+ here.
51
155
 
52
156
  | Tier | Light | Dark | 4.5:1 bar |
53
157
  | --------------------- | ------------------ | ----------------- | ---------------- |
@@ -57,19 +161,19 @@ beneath them. Measured on the three stock body surfaces (`bg-body` / `bg-body-te
57
161
 
58
162
  - Use `text-body-secondary` as the one shipped quiet tier. Use `text-body-tertiary` for decoration
59
163
  or disabled chrome only; it never carries a caption, timestamp, or count someone reads.
60
- - Declare a third readable tier as an opaque token — override `$body-tertiary-color` and
164
+ - Declare any further readable tier as an opaque token — override `$body-tertiary-color` and
61
165
  `$body-tertiary-color-dark`, or add a semantic token — and measure it on each surface it sits on.
62
166
  The opaque `text-secondary` (`$gray-600`) clears 4.5:1 only on pure white and does not adapt.
63
- - More than a dozen component variables consume the translucent secondary color — placeholders,
167
+ - Component variables consume the translucent secondary color — placeholders,
64
168
  `.form-text`, table captions, breadcrumb dividers and the active crumb, toast headers, figure
65
- captions, list-group action text, several disabled states — and inherit this table.
169
+ captions, list-group action text, and disabled states — and inherit this table.
66
170
  - On a colored fill, `text-body-secondary` composites the body color over the hue — grey on color —
67
171
  and `text-white-50` or `text-opacity-*` let the fill show through the glyphs. The quiet tone on a
68
172
  fill is the same hue at lower contrast: keep the owning component's foreground and quiet a line
69
173
  with weight or size, or declare a same-hue token (for the stock blue, the `$blue-200`–`$blue-300`
70
174
  region in light mode) through the component's own variable and measure it.
71
175
  - For a tinted region, use the shipped same-hue pair: `text-*-emphasis` on `bg-*-subtle` (about
72
- 10:1 for primary in the stock light theme), adapting in both modes. Keep `text-bg-*` solids for
176
+ 10:1 for primary in the stock light theme), adapting in light and dark. Keep `text-bg-*` solids for
73
177
  the primary element.
74
178
 
75
179
  When checking utility behavior, see Bootstrap's [Background](https://getbootstrap.com/docs/5.3/utilities/background/)
@@ -119,13 +223,22 @@ Use the host's mode controller. With Bootstrap's attribute strategy, set the res
119
223
  `dark` value on `<html>`; scope an intentional exception with `data-bs-theme` on its boundary.
120
224
  Do not pin components to dark merely because the page happens to be dark during development.
121
225
 
122
- On a generic nested region, establish its surface as well as its variables:
226
+ A nested scope changes variables, not painted colors: `color` is set on `<body>` by the outer
227
+ mode and inherited as a computed value, so plain text inside the scope stays the outer color
228
+ until the boundary restates it. Establish both the surface and the foreground on the scope
229
+ element — never rely on the attribute alone, and never fix the symptom by sprinkling `text-white`
230
+ on individual descendants:
123
231
 
124
232
  ```html
125
233
  <section data-bs-theme="dark" class="bg-body text-body p-3" aria-labelledby="preview-heading">
126
234
  <h2 id="preview-heading" class="h6">Preview</h2>
127
235
  <div class="bg-primary-subtle rounded p-3">This text inherits from the preview.</div>
128
236
  </section>
237
+
238
+ <!-- Fixed fill or image that must not follow the page mode: keep bg-*, still restate text-body. -->
239
+ <section data-bs-theme="dark" class="bg-dark text-body py-5" aria-labelledby="hero-heading">
240
+ …
241
+ </section>
129
242
  ```
130
243
 
131
244
  Keep the body foreground at that owned boundary; add no text-color classes to its ordinary
@@ -166,7 +279,8 @@ Do not use `.badge bg-success-subtle` alone: stock `.badge` supplies a white for
166
279
  inherited body text. Do not mistake the absence of a `text-*` class for the absence of a color
167
280
  rule. Confirm the skin's badge rule; see [Badges](https://getbootstrap.com/docs/5.3/components/badge/).
168
281
  Keep a removable tag's close button in the same mode and measure its hit area; a badge's small
169
- font must not shrink the control below the package target floor.
282
+ font must not shrink the control below the target floor in
283
+ [bootstrap-reference.md](bootstrap-reference.md) → WCAG 2.2 requirements for app UI.
170
284
 
171
285
  ### Alerts, buttons, and selection
172
286
 
@@ -214,12 +328,12 @@ Stock ramps are mechanical: `$blue-100…400` are `tint-color` mixes with white
214
328
  are `shade-color` mixes with black, and the `-text-emphasis` / `-bg-subtle` / `-border-subtle`
215
329
  triads are the same mixes. Hue stays fixed and saturation can only fall, so any brand base short
216
330
  of full saturation washes out at the light end and goes muddy at the dark end; stock blue survives
217
- only because its base is 98 % saturated. For a brand color, override the nine `$brand-100…900`
218
- variables and the six triad variables (`$brand-text-emphasis`, `-bg-subtle`, `-border-subtle`,
331
+ only because its base is 98 % saturated. For a brand color, override the `$brand-100…900` shade
332
+ variables and the triad variables (`$brand-text-emphasis`, `-bg-subtle`, `-border-subtle`,
219
333
  each with its `-dark` twin) with hand-picked values before `variables` is imported; every
220
334
  `.alert-brand`, `bg-brand-subtle`, `text-brand-emphasis`, and `table-brand` then consumes them.
221
335
  Stock greys sit at hue 210° with 7–17 % saturation — cool, matched to the stock blue. For a warm
222
- brand, override `$gray-100…900` as a complete set of nine with one hue and temperature; never drop
336
+ brand, override `$gray-100…900` as a complete set at one hue and temperature; never drop
223
337
  one warm grey into the cool set. Take the picking method from
224
338
  [frontend-design.md](frontend-design.md) → Color as a constrained system.
225
339
 
@@ -237,5 +351,6 @@ Do not infer their foregrounds from a desktop capture or from the theme attribut
237
351
 
238
352
  Run [Color-mode inheritance](inspection.md#color-mode-inheritance) and the applicable contrast,
239
353
  target, and rendered-review checks against the loaded Bootstrap build and skin. Exercise the
240
- existing mounted UI through light, dark, and back, including supported local scopes and overlays.
354
+ existing mounted UI through every declared mode and back, including supported local scopes and
355
+ overlays.
241
356
  Report actual coverage; do not claim a host application pass from documentation or a stock fixture.
@@ -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
 
@@ -158,9 +158,9 @@ Reserve the solid variants for intentional prominence or a counter with its own
158
158
 
159
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.
160
160
 
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 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.
162
162
 
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 light and dark. A fill-only utility does not remove `.badge`'s white text; see [color-modes.md](color-modes.md) → Badges and removable tags.
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.
164
164
 
165
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)).
166
166
 
@@ -180,6 +180,8 @@ The current page is `aria-current="page"` and not a link. Use breadcrumbs only f
180
180
 
181
181
  ### Buttons
182
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
+
183
185
  ```html
184
186
  <button type="button" class="btn btn-primary">Primary</button>
185
187
  <button type="button" class="btn btn-secondary">Secondary</button>
@@ -203,9 +205,9 @@ The current page is `aria-current="page"` and not a link. Use breadcrumbs only f
203
205
  <button type="button" class="btn btn-primary" data-bs-toggle="button">Toggle</button>
204
206
  ```
205
207
 
206
- 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.
207
209
 
208
- The three 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 a fourth size with `em` padding. Weight is `$font-weight-normal`; `fw-semibold` on a button is a deliberate emphasis choice, not a default.
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.
209
211
 
210
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.
211
213
 
@@ -504,6 +506,8 @@ Bootstrap's modal enforces focus, adds `role="dialog"`/`aria-modal="true"`, clos
504
506
 
505
507
  ### Navbar
506
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
+
507
511
  ```html
508
512
  <nav class="navbar navbar-expand-lg bg-body-tertiary">
509
513
  <div class="container-fluid">
@@ -704,7 +708,7 @@ when the presentation changes.
704
708
  <p aria-hidden="true">
705
709
  <span class="placeholder col-6"></span>
706
710
  <span class="placeholder w-75"></span>
707
- <span class="placeholder" style="width: 25%;"></span>
711
+ <span class="placeholder w-25"></span>
708
712
  </p>
709
713
 
710
714
  <span class="placeholder col-12 placeholder-lg">Large</span>
@@ -717,7 +721,7 @@ when the presentation changes.
717
721
  <button class="btn btn-primary disabled placeholder col-4" aria-hidden="true"></button>
718
722
  ```
719
723
 
720
- 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.
721
725
 
722
726
  ### Popover (Requires Popper.js)
723
727
 
@@ -748,6 +752,8 @@ Popovers are **opt-in**: they do nothing until initialized in JS (see [JavaScrip
748
752
 
749
753
  ### Progress
750
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
+
751
757
  5.3 markup — `role="progressbar"` and the `aria-value*` attributes go on the **outer `.progress`**, not the inner bar:
752
758
 
753
759
  ```html
@@ -873,6 +879,8 @@ Gotcha: the spied element must be a scroll container (height/overflow, or focusa
873
879
 
874
880
  ### Tables
875
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
+
876
884
  ```html
877
885
  <table class="table">
878
886
  <caption class="visually-hidden">
@@ -972,7 +980,7 @@ Toasts are **opt-in** — hidden until `.show()` is called (or shown through a t
972
980
  </button>
973
981
  ```
974
982
 
975
- 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.
976
984
 
977
985
  ## JavaScript Initialization
978
986
 
@@ -1003,14 +1011,14 @@ Bootstrap's core CSS ships **no icons**. The `.bi` SVGs in examples come from th
1003
1011
 
1004
1012
  ### Status glyph marks
1005
1013
 
1006
- 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:
1007
1015
 
1008
1016
  ```html
1009
1017
  <span class="bi bi-circle-fill fs-6 lh-1" role="img" aria-label="Healthy"></span>
1010
1018
  <span class="bi bi-circle fs-6 lh-1" role="img" aria-label="Not started"></span>
1011
1019
  ```
1012
1020
 
1013
- - **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 at **≥ 3:1** in light and dark. Take cascade exceptions from [color-modes.md](color-modes.md).
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).
1014
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.
1015
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.
1016
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.
@@ -1040,7 +1048,7 @@ The textless mark that survives both themes — dots, ticks, rings, pulses — i
1040
1048
  - Prefer visible labels or `.form-floating` — placeholder-only labels fail accessibility and disappear on input.
1041
1049
  - Pair help and errors with `aria-describedby`; use `.invalid-feedback` with `.is-invalid` and mark the field `aria-invalid="true"`.
1042
1050
  - Money/units: `.input-group` + `.input-group-text`; add `.has-validation` on groups with validation feedback.
1043
- - 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.
1044
1052
 
1045
1053
  ### Selection fills
1046
1054
 
@@ -1048,7 +1056,7 @@ Keep the component's selected foreground on ordinary labels and glyphs. Remove a
1048
1056
  semantic tint before changing its active fill; handle an independently filled badge through
1049
1057
  [color-modes.md](color-modes.md) → Alerts, buttons, and selection.
1050
1058
 
1051
- Verify that chosen and unchosen filters remain distinguishable in light and dark. Do not assume
1059
+ Verify that chosen and unchosen filters remain distinguishable in every declared theme. Do not assume
1052
1060
  that a neutral outline always inverts meaning or that an accent hue repairs it. Preserve the
1053
1061
  checked/pressed state and add a visible non-color cue when the fill alone is ambiguous.
1054
1062
 
@@ -1060,7 +1068,7 @@ to every selection widget.
1060
1068
 
1061
1069
  - Active nav items need `aria-current="page"` (or `aria-selected="true"` for tabs).
1062
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.
1063
- - 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.
1064
1072
 
1065
1073
  ### Theming
1066
1074
 
@@ -1,7 +1,8 @@
1
1
  # Frontend Design
2
2
 
3
- > Part of the `enterprise-bootstrap` package. Aesthetic, hierarchy, spacing,
4
- > typography, color, depth, imagery, process, and copy — use when setting visual direction.
3
+ > Part of the `enterprise-bootstrap` skill. Aesthetic, hierarchy, spacing,
4
+ > typography, color, depth, imagery, signature, and copy — use when setting visual direction.
5
+ > The pass order lives in [SKILL.md](../SKILL.md) → Process.
5
6
  > Operate layer: [SKILL.md](../SKILL.md).
6
7
 
7
8
  Give the surface a point of view rooted in its subject. Make the person's task clear before making
@@ -15,13 +16,17 @@ content, and its design system before choosing a direction. Use prior designs an
15
16
  hints, not templates. Draw character from the subject's materials, instruments, artifacts, and
16
17
  vernacular rather than an unrelated visual trend.
17
18
 
18
- Set personality through four levers — typeface, primary color, corner-radius family, and copy
19
- register — chosen from the audience and subject, then held on every screen. A neutral sans-serif
20
- and a small radius are deliberate neutrals, not missing decisions; a serif reads classic, a
21
- rounded sans or large radius playful, no radius formal. Keep one radius family per product: pill
22
- buttons beside square cards read as two products. Take the register from what the audience
23
- already uses, not from a competitor's interface. In an existing product these levers are set;
24
- carry them forward.
19
+ Set personality through these levers — typeface, primary color, corner-radius family, and copy
20
+ register. Choose each from the audience and subject, then hold it on every screen. In an existing
21
+ product the levers are already set: carry them forward and move none without a brief that asks for
22
+ it.
23
+
24
+ Pick the typeface and radius against the register the brief names: take a serif for a classic
25
+ register, a rounded sans or a large radius for a playful one, and no radius for a formal one. Take a
26
+ neutral sans-serif and a small radius for a neutral register as a deliberate choice, never as a
27
+ skipped one. Use one radius family per product; pill buttons beside square cards read as two
28
+ products. Take the copy register from what the audience already uses, not from a competitor's
29
+ interface.
25
30
 
26
31
  Start with the smallest useful feature: what the person needs to see, enter, decide, and do next.
27
32
  Compose that interaction with realistic content before choosing its navigation shell. Reuse a shell
@@ -36,7 +41,7 @@ Name the primary information, supporting context, and ancillary detail in each t
36
41
  that order readable without color: use placement, grouping, weight, and spacing before adding
37
42
  paint. When the primary element does not stand out, quiet its competitors before enlarging it.
38
43
 
39
- Carry hierarchy with two or three foreground tiers and two weights before reaching for size;
44
+ Carry hierarchy with a short set of foreground tiers and a weight pair before reaching for size;
40
45
  size alone produces oversized primary text and unreadable secondary text. Use a regular body
41
46
  weight (400) and one emphasis weight (600–700); weights under 400 belong only at display sizes.
42
47
  Quiet a heavy element by lowering its contrast — an icon beside a label takes the secondary tier
@@ -105,14 +110,15 @@ to make it fit.
105
110
  Assign display, body, and utility roles; they need not be different font families. Reuse the
106
111
  product's typefaces. For a new system, take a legible UI face for repeated reading and data, and add
107
112
  a display face only where its character earns the payload. A neutral sans-serif or system stack is
108
- a deliberate choice, not a failure of distinctiveness. Prefer families offered in five or more
109
- weights; avoid condensed or short-x-height faces for UI text; keep a display face at display size,
113
+ a deliberate choice, not a failure of distinctiveness. Prefer a family shipping the `300`–`700`
114
+ range so display, body, and emphasis roles draw from one family; avoid condensed or
115
+ short-x-height faces for UI text; keep a display face at display size,
110
116
  where it was drawn to work. Test the actual glyphs, numerals, weights, languages, and fallback the
111
117
  surface needs.
112
118
 
113
119
  Hand-pick a finite type scale in `rem`, with smaller jumps for UI text and larger jumps for
114
120
  display; a modular ratio yields fractional pixels and too few reading sizes. Avoid nested `em`
115
- font sizes that compound off-scale. Start with two working weights — regular and emphasis — and
121
+ font sizes that compound off-scale. Start with a regular weight and an emphasis weight, and
116
122
  add another only for a distinct role. Keep captions and metadata at the smallest readable step in
117
123
  the secondary tier, not smaller in body color. Never shrink text merely to avoid fixing a cramped
118
124
  layout.
@@ -140,10 +146,11 @@ on touch or keyboard.
140
146
 
141
147
  ### Color as a constrained system
142
148
 
143
- Define roles, not a handful of unrelated swatches. Reuse or establish a neutral ramp, one or two
144
- brand families, and only the status or categorical families the feature needs. A working neutral
145
- ramp often needs 8–10 shades; brand and status families need enough steps for text, borders, fills,
146
- and interaction states. Those are starting ranges, not quotas to fill on every task.
149
+ Define roles, not a handful of unrelated swatches. Reuse or establish a neutral ramp, the brand
150
+ families the identity needs, and only the status or categorical families the feature needs. Build
151
+ the neutral ramp across the shipped `100…900` steps, and give each brand and status family enough
152
+ steps for text, borders, fills, and interaction states. Declare a step because a role consumes it,
153
+ never to fill the ramp.
147
154
 
148
155
  Choose a family's base in a real control, its dark edge in text, and its light edge in a subtle
149
156
  surface; fill the gaps with visibly distinct steps, middle first. Define every shade up front;
@@ -167,7 +174,7 @@ the color without the weight of a dark fill, and reserves the solid pair for the
167
174
  element. Reserve explicit foreground/background pairs for intentional solid, inverse, or
168
175
  image-backed regions. Keep a secondary foreground within that region's tested contract; do not apply neutral
169
176
  grey or reduced opacity by habit. When no quieter foreground passes, separate by weight or spacing.
170
- Review hierarchy in light and dark independently. Preserve relative prominence and useful
177
+ Review hierarchy in every declared mode independently. Preserve relative prominence and useful
171
178
  separation rather than mechanically inverting shades or adding a border to every dark panel.
172
179
 
173
180
  Use color to reinforce a word, glyph, position, or pattern, never to carry meaning alone. Give
@@ -196,9 +203,10 @@ Give overlapping images a ring in the background color so they never clash. Keep
196
203
  weights in one family.
197
204
 
198
205
  Spend polish on the content already present before adding another accessory — icon bullets that mean something, a brand-colored check, a
199
- promoted quotation mark, a link underline that completes on hover. One accent border per region
200
- — top of a card, side of a callout, under a heading or the active nav item — is the cheapest
201
- "designed" cue; five accents are a pattern. Change a section's surface before decorating it; keep
206
+ promoted quotation mark, a link underline that completes on hover. Use one accent border per region
207
+ — top of a card, side of a callout, under a heading, or the active nav item. Repeating that accent
208
+ across neighboring regions turns it into a pattern and it stops reading as an accent. Change a
209
+ section's surface before decorating it; keep
202
210
  any gradient within about 30° of hue and any pattern low-contrast and away from text. An accent,
203
211
  pattern, or background treatment supports grouping, state, or the subject; never add one to
204
212
  compensate for weak hierarchy. Take the class recipes from [utilities.md](utilities.md) →
@@ -253,36 +261,23 @@ matters. Give richer components richer content without replacing their semantics
253
261
  in a menu, related non-comparable details in one table cell, native radios inside selectable cards.
254
262
  Preserve keyboard behavior, sorting, and the comparisons the task depends on.
255
263
 
256
- ## Process: brainstorm, explore, plan, critique, build, critique again
264
+ ## Hold the direction against defaults
265
+
266
+ Run the pass order in [SKILL.md](../SKILL.md) → Process. That section owns the sequence; this file
267
+ owns the visual decisions each pass makes.
257
268
 
258
269
  Calibrate against recurring defaults: cream with serif and terracotta; near-black with
259
270
  acid green or vermilion; broadsheet hairlines, square corners, and dense columns. These can fit a
260
271
  brief; they are not evidence that a direction fits this one. Follow a pinned direction exactly and
261
272
  use free axes deliberately, not as an excuse to rebrand an existing product.
262
273
 
263
- Work in small passes:
264
-
265
- 1. **Ground the feature.** State its job, real content, primary action, and existing constraints.
266
- 2. **Explore the hierarchy.** Compare compact low-fidelity arrangements of that feature. Hold
267
- color: body surfaces, inherited text, weight, and spacing only, until a grayscale capture
268
- reads; add brand and status hue last to reinforce what already reads. Settle the reading
269
- order, grouping, and narrow layout before fine styling. Discard the sketches after selecting
270
- a workable arrangement.
271
- 3. **Plan the system.** Name palette families and surface ownership; type roles, sizes, and weights; spacing
272
- and width roles; radius and elevation; the signature the brief calls for or the existing identity to preserve. Reuse existing
273
- tokens and record only the additions or changes. State the layout in prose or a small wireframe.
274
- 4. **Critique, then build.** Reject a plan that obscures the task, implies unbuilt behavior, or reads
275
- as interchangeable. Implement the smallest working flow, map shared tokens at their owning scope, and refine its states
276
- before extending the next feature. Revise the shared plan when the render disproves it.
277
- 5. **Critique the render.** Read task, hierarchy, grouping, type, contrast, state, and signature in
278
- that order. Fix the earliest failure before decorating later layers. Recheck after the fix.
279
-
280
- Keep CSS specificity deliberate. Utilities may use `!important`; inspect the winning declaration
281
- instead of stacking overrides. Remove conflicting classes before escalating selector specificity.
282
- A class selector remains a class selector regardless of its name.
283
-
284
- Keep exploratory drafts private. Show the selected direction, relevant decisions, and verification
285
- limits, not every discarded variation. Do not claim a rendered result from source inspection.
274
+ Reject a direction that obscures the task, implies unbuilt behavior, or reads as interchangeable
275
+ with any other product. Keep exploratory drafts private: show the selected direction, the decisions
276
+ behind it, and the verification limits, not every discarded variation. Never claim a rendered result
277
+ from source inspection.
278
+
279
+ Keep specificity deliberate through [SKILL.md](../SKILL.md) → The styling ladder, which owns that
280
+ rule.
286
281
 
287
282
  ## Restraint and self-critique
288
283
 
@@ -302,7 +297,7 @@ Review captures at the declared widths, themes, and states, with keyboard focus
302
297
  content. Switch modes on the mounted interface; inspect inherited text, quiet fills, and selected
303
298
  controls before polishing decoration. Compare against the named design criteria; do not invent a numeric beauty score. Use
304
299
  [inspection.md](inspection.md) for measurable claims and its rendered design review for visual
305
- ones. Record untested coverage as open. Route a requested review campaign to `orkestrel-polish-surface`.
300
+ ones. Record untested coverage as open.
306
301
 
307
302
  ## Writing in design
308
303