@orkestrel/scaffold 0.0.63 → 0.0.65
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.
- package/README.md +29 -104
- package/dist/bin/main.js +95 -27
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +2 -2
- package/dist/host/CLAUDE.md +6 -0
- package/dist/host/agents/orchestration.md +23 -15
- package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +184 -177
- package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +314 -91
- package/dist/host/agents/skills/enterprise-bootstrap/references/color-modes.md +241 -0
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +83 -36
- package/dist/host/agents/skills/enterprise-bootstrap/references/frontend-design.md +297 -98
- package/dist/host/agents/skills/enterprise-bootstrap/references/inputs.md +25 -14
- package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +216 -128
- package/dist/host/agents/skills/enterprise-bootstrap/references/responsive-layout.md +187 -0
- package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +109 -20
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +5 -5
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +15 -15
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +43 -17
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +41 -16
- package/dist/host/claude/agents/orkestrel.md +56 -56
- package/dist/host/claude/agents/reviewer.md +13 -0
- package/dist/host/claude/rules/architecture.md +51 -45
- package/dist/host/claude/rules/documentation.md +18 -1
- package/dist/host/claude/rules/portability.md +2 -0
- package/dist/host/claude/rules/quality.md +1 -1
- package/dist/host/claude/rules/tests.md +12 -11
- package/dist/host/claude/rules/typescript.md +5 -0
- package/dist/host/claude/rules/workspace.md +25 -20
- package/dist/host/claude/rules/writing.md +4 -0
- package/dist/host/claude/settings.json +1 -1
- package/dist/host/claude/skills/enterprise-bootstrap/SKILL.md +10 -9
- package/dist/host/codex/agents/orkestrel.toml +3 -3
- package/dist/host/codex/agents/reviewer.toml +4 -2
- package/dist/host/configs/helpers.ts +311 -2
- package/dist/host/configs/policy.ts +1100 -51
- package/dist/host/dotfiles/oxlintrc.json +72 -1
- package/dist/host/guides/guide.md +749 -222
- package/dist/host/guides/scaffold.md +529 -394
- package/dist/host/manifest.json +53 -40
- package/dist/host/scripts/ollama.sh +322 -13
- package/dist/host/tests/config.test.ts +1200 -16
- package/dist/host/tests/policy.test.ts +157 -173
- package/dist/host/tests/setupPolicy.ts +522 -1007
- package/dist/src/core/index.cjs +402 -287
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +160 -128
- package/dist/src/core/index.d.ts +160 -128
- package/dist/src/core/index.js +400 -286
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +28 -21
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +38 -33
- package/dist/src/server/index.d.ts +38 -33
- package/dist/src/server/index.js +28 -21
- package/dist/src/server/index.js.map +1 -1
- package/package.json +18 -19
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- [Quick start](#quick-start)
|
|
11
11
|
- [Breakpoints & layout](#breakpoints--layout)
|
|
12
12
|
- [Color modes (light / dark / custom)](#color-modes-light--dark--custom)
|
|
13
|
-
- [Theming & design tokens](#theming--design-tokens)
|
|
13
|
+
- [Theming & design tokens](#theming--design-tokens) — [Define the working scales](#define-the-working-scales) · [Elevation and depth](#elevation-and-depth) · [Layout and type extensions](#layout-and-type-extensions)
|
|
14
14
|
- [Forms in production](#forms-in-production)
|
|
15
15
|
- [JavaScript lifecycle](#javascript-lifecycle)
|
|
16
16
|
- [Accessibility](#accessibility)
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
## Quick Start
|
|
25
25
|
|
|
26
|
-
CDN (5.3.8
|
|
26
|
+
Pinned CDN example (5.3.8). Prefer the installed compatible version; this is not an upgrade instruction:
|
|
27
27
|
|
|
28
28
|
```html
|
|
29
29
|
<!doctype html>
|
|
@@ -49,6 +49,20 @@ CDN (5.3.8 is the current — and final — 5.3.x patch before 5.4):
|
|
|
49
49
|
|
|
50
50
|
## Breakpoints & Layout
|
|
51
51
|
|
|
52
|
+
Start with the feature's content and narrow layout, then choose its container and breakpoints.
|
|
53
|
+
Take the region contract, content parity, and test matrix from [responsive-layout.md](responsive-layout.md).
|
|
54
|
+
Use fluid columns for content that needs to scale together; keep rails, forms, and reading measures
|
|
55
|
+
bounded where it does not. A full-width shell does not require full-width text or fields.
|
|
56
|
+
Take the missing role-based utilities from [Layout and type extensions](#layout-and-type-extensions).
|
|
57
|
+
|
|
58
|
+
Give a form, dialog, or login card a content-led maximum and let it shrink only when the viewport
|
|
59
|
+
is narrower: `w-100 mx-auto measure-form`, not `col-md-8 offset-md-2 col-lg-6 offset-lg-3`, whose
|
|
60
|
+
width changes at every breakpoint and is narrower on `lg` than at some `md` widths. Give a
|
|
61
|
+
sidebar a fixed rail (`shell-rail-lg-fixed flex-shrink-0`) beside a flexible `min-inline-0` main
|
|
62
|
+
region, not `col-3`, which grows on wide screens and collapses below its minimum on narrow ones.
|
|
63
|
+
Put supporting explanation beside a narrow form in a second column rather than widening its
|
|
64
|
+
fields. Percentage widths belong only where columns must scale together.
|
|
65
|
+
|
|
52
66
|
| Breakpoint | Class Infix | Dimensions |
|
|
53
67
|
| ----------- | ----------- | ---------- |
|
|
54
68
|
| Extra small | (none) | <576px |
|
|
@@ -85,77 +99,160 @@ The 5.3 color-mode system replaces the old per-component `*-dark` classes.
|
|
|
85
99
|
|
|
86
100
|
### Mechanics
|
|
87
101
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
- Deprecated by this system: `.navbar-dark`, `.dropdown-menu-dark`, `.btn-close-white`, `.carousel-dark` → put `data-bs-theme="dark"` on the component or an ancestor instead.
|
|
102
|
+
Read [color-modes.md](color-modes.md) before choosing color classes or repairing a theme failure.
|
|
103
|
+
It owns inheritance, adaptive-versus-fixed families, surface boundaries, and component exceptions.
|
|
104
|
+
Use the installed build's attribute or media-query strategy; do not assume every `--bs-*` variable
|
|
105
|
+
changes with the mode.
|
|
93
106
|
|
|
94
107
|
### Author rules
|
|
95
108
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
109
|
+
Preserve ordinary text inheritance and native component states. Prefer adaptive body/subtle
|
|
110
|
+
surfaces. Establish an explicit foreground only at an owned solid or mode boundary, or for a
|
|
111
|
+
measured role that requires it. Do not turn every subtle panel into a custom color pair.
|
|
99
112
|
|
|
100
113
|
### Theme toggle
|
|
101
114
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const stored = localStorage.getItem('theme')
|
|
106
|
-
const preferred =
|
|
107
|
-
stored ?? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
|
108
|
-
document.documentElement.setAttribute('data-bs-theme', preferred)
|
|
109
|
-
// On toggle: setAttribute + localStorage.setItem("theme", value)
|
|
110
|
-
```
|
|
115
|
+
Reuse the host controller. When implementing one, follow [Scope the mode](color-modes.md#scope-the-mode)
|
|
116
|
+
for validated preference, automatic-mode resolution, storage failure, first paint, and overlay
|
|
117
|
+
mounts. Bootstrap ships no picker; an attribute example is not a complete controller.
|
|
111
118
|
|
|
112
119
|
### Custom modes
|
|
113
120
|
|
|
114
|
-
|
|
121
|
+
Add a custom mode only when the brief requires it. Map its used body, surface, link, border,
|
|
122
|
+
validation, and component-state variables, including RGB companions. Resolve embedded component
|
|
123
|
+
images and `color-scheme` where relevant. Do not claim a complete mode from a partial token block.
|
|
124
|
+
|
|
125
|
+
In Sass, use `$enable-dark-mode`, `$color-mode-type: data` for local attribute scopes, or
|
|
126
|
+
`media-query` for system-driven mode without per-component scoping. Use `color-mode()` rather than
|
|
127
|
+
competing selectors; keep overrides in the host theme source.
|
|
128
|
+
|
|
129
|
+
## Theming & Design Tokens
|
|
130
|
+
|
|
131
|
+
### The tiered token model
|
|
132
|
+
|
|
133
|
+
Keep literal values in declared primitives, map primitives to semantic roles, and let component
|
|
134
|
+
variables consume those roles. Reuse Bootstrap's `--bs-*` semantic and component layers rather
|
|
135
|
+
than adding a parallel palette. Name semantics by purpose, not a particular shade.
|
|
136
|
+
|
|
137
|
+
Distinguish token structure from runtime behavior. Bootstrap also generates fixed values through
|
|
138
|
+
Sass; a component variable is not necessarily mode-adaptive. Map the actual consumer, including its
|
|
139
|
+
states, and resolve aliases at the scope where they must change. Take the constraints from
|
|
140
|
+
[Extend the theme](color-modes.md#extend-the-theme).
|
|
141
|
+
|
|
142
|
+
### Define the working scales
|
|
143
|
+
|
|
144
|
+
Reuse the installed theme and its scales first. Declare new values only for a role the feature
|
|
145
|
+
needs; refine one shared definition instead of accumulating per-component exceptions. Every
|
|
146
|
+
system below has a Bootstrap source, a utility, and a known gap; extend the source, never the
|
|
147
|
+
markup.
|
|
148
|
+
|
|
149
|
+
| System | Sass source | Utility | Stock steps (default root) | Gap |
|
|
150
|
+
| -------------- | ----------------------------------------------------------- | --------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------- |
|
|
151
|
+
| Font size | `$font-sizes`, `$h1…h6-font-size`, `$display-font-sizes` | `fs-1…6`, `.h1…h6`, `display-1…6` | 16 · 20 · 24 · 28 · 32 · 40 px; display 40–80 px | No `rem` step below 16 px; `.small` is `.875em` |
|
|
152
|
+
| Font weight | `$font-weight-*`, `$headings-font-weight` | `fw-light…bold` | 300 · 400 · 500 · 600 · 700; headings 500 | Headings barely heavier than body |
|
|
153
|
+
| Line height | `$line-height-*`, `$headings-line-height` | `lh-1`, `lh-sm`, `lh-base`, `lh-lg` | 1 · 1.25 · 1.5 · 2; headings 1.2 | — |
|
|
154
|
+
| Color | `$gray-100…900`, `$blue-100…900` …, `$theme-colors`, triads | `text-*`, `bg-*`, `border-*` families | 9 shades per hue; subtle/emphasis per role | Ramps are mechanical mixes ([color-modes.md](color-modes.md) → Extend the theme) |
|
|
155
|
+
| Spacing | `$spacers` | `m-*`, `p-*`, `gap-*`, `g-*` | 0 · 4 · 8 · 16 · 24 · 48 px | No 12 or 32 px; nothing above 48 px |
|
|
156
|
+
| Width | `$container-max-widths`, the grid | `w-*`, `mw-100`, `col-*` | 25 / 50 / 75 / 100 % | No content-led maximums |
|
|
157
|
+
| Shadow | `$box-shadow`, `-sm`, `-lg`, `-inset` | `shadow-sm`, `shadow`, `shadow-lg` | 3 steps | Single-layer; modal shares the dropdown step |
|
|
158
|
+
| Radius | `$border-radius*`, `$enable-rounded` | `rounded-0…5`, `-pill`, `-circle` | 0 · 4 · 6 · 8 · 16 · 32 px | — |
|
|
159
|
+
| Border width | `$border-widths` | `border-1…5` | 1–5 px | Sets every side at once |
|
|
160
|
+
| Opacity | — | `opacity-*`, `text-opacity-*`, `bg-opacity-*` | 0 · 10 · 25 · 50 · 75 · 100 | Not a text tier |
|
|
161
|
+
| Letter-spacing | — | none | — | Generate ([Layout and type extensions](#layout-and-type-extensions)) |
|
|
162
|
+
|
|
163
|
+
- **Color:** neutral, brand, and required status/categorical ramps. Pick base, light surface, and
|
|
164
|
+
dark text shades in real components, then fill the gaps. Use HSL when it helps tune related
|
|
165
|
+
shades; keep the project's existing format. Review fixed shade pairs in each theme rather than
|
|
166
|
+
generating a new `lighten`, `darken`, or `color-mix` result at each use site. Stock ramps and
|
|
167
|
+
triads are tint/shade mixes with a fixed hue; override the nine shade variables and six triad
|
|
168
|
+
variables per brand hue, and the nine greys as one temperature-matched set
|
|
169
|
+
([color-modes.md](color-modes.md) → Extend the theme).
|
|
170
|
+
- **Type:** display/body/utility roles, finite `rem` sizes, working weights, and line-height per
|
|
171
|
+
role. Roles may share a font. RFS scales sizes above 1.25 rem down below a 1200 px viewport
|
|
172
|
+
(`h1`–`h4`, `display-*`, `fs-1`–`fs-4`); body, `fs-5`, `fs-6`, `.lead`, and controls hold — do not
|
|
173
|
+
fight it with `em` heading sizes. Take 14 px and 12 px roles from generated `fs-sm`/`fs-xs`, not
|
|
174
|
+
nested `.small`. Never globally scale body text down to make a display treatment fit.
|
|
175
|
+
- **Space and size:** internal, group, panel, and section gaps; control sizes; reading/form widths;
|
|
176
|
+
rail width. Start with Bootstrap's shipped scale. Add a missing step through the utilities API
|
|
177
|
+
only where the adjacent steps cannot express the intended relationship. Button sizes already
|
|
178
|
+
scale padding faster than font (4/8 px at 14 px, 6/12 at 16, 8/16 at 20); use the three shipped
|
|
179
|
+
sizes rather than deriving one with `em` padding.
|
|
180
|
+
- **Radius and elevation:** a small consistent family, assigned to real component/layer roles.
|
|
181
|
+
Set `$border-radius` once and let components inherit it; do not hand-mix `rounded-*` per
|
|
182
|
+
element. Reuse component variables and shadow utilities ([Elevation and depth](#elevation-and-depth)).
|
|
183
|
+
No shadow is a valid surface role.
|
|
184
|
+
|
|
185
|
+
Record these roles in the existing token source or a compact design contract, not a second design
|
|
186
|
+
system. Keep literal colors and raw scale values in named primitive definitions; component rules
|
|
187
|
+
consume semantic or component tokens. [inspection.md](inspection.md) → Token discipline checks
|
|
188
|
+
that boundary; [frontend-design.md](frontend-design.md) owns the visual choices.
|
|
189
|
+
|
|
190
|
+
### Elevation and depth
|
|
191
|
+
|
|
192
|
+
Three shipped steps — `--bs-box-shadow-sm` (`0 .125rem .25rem` at .075), `--bs-box-shadow`
|
|
193
|
+
(`0 .5rem 1rem` at .15), `--bs-box-shadow-lg` (`0 1rem 3rem` at .175) — plus
|
|
194
|
+
`--bs-box-shadow-inset`. Assign by z-position: `sm` for raised cards and controls, base for
|
|
195
|
+
floating menus and a dragged item, `lg` for dialogs. Stock dropdowns, popovers, toasts, and
|
|
196
|
+
modals all sit on `--bs-box-shadow` (modal: `-sm` below 576 px), which puts a blocking dialog at
|
|
197
|
+
dropdown elevation. Lift it at rung 3, in the project stylesheet after Bootstrap's so the rule
|
|
198
|
+
wins the `sm`-up media rule:
|
|
115
199
|
|
|
116
200
|
```css
|
|
117
|
-
|
|
118
|
-
--bs-
|
|
119
|
-
--bs-body-color: #dfe4f2;
|
|
120
|
-
--bs-tertiary-bg: #131a30;
|
|
121
|
-
--bs-border-color: #26304f;
|
|
122
|
-
}
|
|
123
|
-
[data-bs-theme='midnight'] .dropdown-menu {
|
|
124
|
-
--bs-dropdown-bg: var(--bs-tertiary-bg);
|
|
201
|
+
.modal {
|
|
202
|
+
--bs-modal-box-shadow: var(--bs-box-shadow-lg);
|
|
125
203
|
}
|
|
126
204
|
```
|
|
127
205
|
|
|
128
|
-
|
|
206
|
+
Two-part shadows — a broad cast plus a tight contact shadow that fades with elevation — are a
|
|
207
|
+
token change: redefine `$box-shadow-sm`, `$box-shadow`, and `$box-shadow-lg` as two-layer values
|
|
208
|
+
and every consumer follows.
|
|
129
209
|
|
|
130
|
-
|
|
210
|
+
`$enable-shadows: true` (off by default) paints light-from-above on controls: buttons take
|
|
211
|
+
`inset 0 1px 0 rgba(#fff, .15), 0 1px 1px rgba(#000, .075)` (lit top edge, tight cast shadow),
|
|
212
|
+
inputs `inset 0 1px 2px rgba(#000, .075)` (recessed), and an active button `inset 0 3px 5px`
|
|
213
|
+
(pressed). Enable it when the direction wants tactile controls, and verify both modes; the alphas
|
|
214
|
+
are fixed white and black. Without the flag the `box-shadow` mixin emits nothing, so
|
|
215
|
+
`--bs-btn-box-shadow` and `--bs-box-shadow-inset` have no consumer and a rung-3 override does
|
|
216
|
+
nothing; the recipe is then a proposed rule.
|
|
131
217
|
|
|
132
|
-
|
|
218
|
+
Flat depth: a `bg-body` panel on `bg-body-tertiary` reads raised and `bg-body-secondary` inside
|
|
219
|
+
`bg-body` reads inset, both mode-adaptive with no shadow. A hard offset shadow is a `$box-shadow`
|
|
220
|
+
override.
|
|
133
221
|
|
|
134
|
-
|
|
222
|
+
Overlap: `position-relative translate-middle-y`, or `mt-n*` after `$enable-negative-margins`.
|
|
223
|
+
Ring overlapping images in the surface color through the border variable so the ring follows the
|
|
224
|
+
mode, where `border-white` does not:
|
|
135
225
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
226
|
+
```css
|
|
227
|
+
.ring-body {
|
|
228
|
+
--bs-border-color: var(--bs-body-bg);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
139
231
|
|
|
140
|
-
|
|
232
|
+
Then `rounded-circle border border-3 ring-body`.
|
|
141
233
|
|
|
142
234
|
### The CSS-variables-only path (no Sass build)
|
|
143
235
|
|
|
144
|
-
|
|
236
|
+
Use native components and adaptive utilities before adding overrides. For a recurring component
|
|
237
|
+
surface role, use its local variable rather than repainting the whole component. This optional
|
|
238
|
+
project-defined class changes the card background without assigning a foreground:
|
|
145
239
|
|
|
146
240
|
```css
|
|
147
|
-
|
|
148
|
-
--bs-
|
|
149
|
-
--bs-primary-rgb: 111, 66, 193; /* time need the -rgb partner updated too */
|
|
150
|
-
}
|
|
151
|
-
.btn-brand {
|
|
152
|
-
--bs-btn-bg: var(--bs-primary);
|
|
153
|
-
--bs-btn-color: #fff;
|
|
154
|
-
--bs-btn-hover-bg: color-mix(in srgb, var(--bs-primary), black 10%);
|
|
241
|
+
.card-quiet {
|
|
242
|
+
--bs-card-bg: var(--bs-tertiary-bg);
|
|
155
243
|
}
|
|
156
244
|
```
|
|
157
245
|
|
|
158
|
-
|
|
246
|
+
Use `class="card card-quiet"` only after declaring that extension in the host theme stylesheet.
|
|
247
|
+
Do not add it when `card bg-body-tertiary` already expresses the requirement. Measure the card's
|
|
248
|
+
inherited foreground and its header/footer layers in the loaded skin.
|
|
249
|
+
|
|
250
|
+
When a custom button variant is required, define rest, hover, focus, active/checked, and disabled
|
|
251
|
+
component variables as one contract. Include borders and the focus-ring RGB value; test busy
|
|
252
|
+
content without changing geometry. Do not generate a custom tinted button merely to distinguish a
|
|
253
|
+
secondary action, and do not assume reversing a subtle/emphasis pair produces valid states.
|
|
254
|
+
Changing root `--bs-primary` alone does not rebuild Sass-generated button states or utility RGB
|
|
255
|
+
consumers; follow [Extend the theme](color-modes.md#extend-the-theme).
|
|
159
256
|
|
|
160
257
|
### The Sass path (compiled builds)
|
|
161
258
|
|
|
@@ -175,7 +272,7 @@ Import order matters — override maps **before** the files that consume them:
|
|
|
175
272
|
@import 'bootstrap/scss/utilities/api'; // generates utilities — keep LAST
|
|
176
273
|
```
|
|
177
274
|
|
|
178
|
-
Feature flags worth knowing: `$enable-dark-mode`, `$enable-rounded`, `$enable-shadows
|
|
275
|
+
Feature flags worth knowing: `$enable-dark-mode`, `$enable-rounded`, `$enable-shadows` (light-from-above button/input/active shadows, [Elevation and depth](#elevation-and-depth)), `$enable-gradients` (a white fade on every `bg-*`; not a two-hue gradient), `$enable-rfs` (sizes above 1.25 rem shrink below 1200 px), `$enable-validation-icons`, `$enable-negative-margins` (`mt-n*` for overlap), `$enable-important-utilities`, `$enable-reduced-motion`. For added theme colors, extend the light/dark emphasis and subtle maps and inspect the generated utility-value maps; see [Extend the theme](color-modes.md#extend-the-theme). Do not infer a generated class from a token alone.
|
|
179
276
|
|
|
180
277
|
### Utilities API
|
|
181
278
|
|
|
@@ -191,7 +288,7 @@ $utilities: map-merge(
|
|
|
191
288
|
class: cursor,
|
|
192
289
|
values: auto pointer grab,
|
|
193
290
|
),
|
|
194
|
-
//
|
|
291
|
+
// Modify an existing utility: make width responsive.
|
|
195
292
|
'width': map-merge(
|
|
196
293
|
map-get($utilities, 'width'),
|
|
197
294
|
(
|
|
@@ -203,25 +300,125 @@ $utilities: map-merge(
|
|
|
203
300
|
@import 'bootstrap/scss/utilities/api';
|
|
204
301
|
```
|
|
205
302
|
|
|
206
|
-
Remove with `map-remove($utilities, "width")` or set the key to `null`. This is the sanctioned answer when the shipped scale is missing a step (
|
|
303
|
+
Remove with `map-remove($utilities, "width")` or set the key to `null`. This is the sanctioned answer when the shipped scale is missing a step (for example, a `vh-50` the design truly needs).
|
|
304
|
+
|
|
305
|
+
### Layout and type extensions
|
|
306
|
+
|
|
307
|
+
These are **project-generated classes**, not stock Bootstrap utilities. Use the existing project
|
|
308
|
+
roles when present. Otherwise add only the needed entries; the values below are illustrative role
|
|
309
|
+
definitions, not universal sizes.
|
|
310
|
+
|
|
311
|
+
Scale steps go in the map-override slot (after `variables-dark`, before `maps`). Keys `0`–`5`
|
|
312
|
+
keep their shipped meaning; an intermediate 12 px or 32 px step is a new key, never a decimal.
|
|
313
|
+
`$font-sizes` feeds only the `fs-*` utility in 5.3.8, and RFS leaves values at or below 1.25 rem
|
|
314
|
+
alone, so extending it is safe:
|
|
315
|
+
|
|
316
|
+
```scss
|
|
317
|
+
$spacers: map-merge(
|
|
318
|
+
$spacers,
|
|
319
|
+
(
|
|
320
|
+
6: $spacer * 4,
|
|
321
|
+
7: $spacer * 6,
|
|
322
|
+
)
|
|
323
|
+
); // 64px section gap, 96px page rhythm
|
|
324
|
+
$font-sizes: map-merge(
|
|
325
|
+
$font-sizes,
|
|
326
|
+
(
|
|
327
|
+
sm: 0.875rem,
|
|
328
|
+
xs: 0.75rem,
|
|
329
|
+
)
|
|
330
|
+
); // fs-sm 14px captions, fs-xs 12px eyebrows only
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Role utilities go after importing `utilities` and before `utilities/api`:
|
|
334
|
+
|
|
335
|
+
```scss
|
|
336
|
+
$utilities: map-merge(
|
|
337
|
+
$utilities,
|
|
338
|
+
(
|
|
339
|
+
'content-measure': (
|
|
340
|
+
property: max-inline-size,
|
|
341
|
+
class: measure,
|
|
342
|
+
values: (
|
|
343
|
+
prose: 65ch,
|
|
344
|
+
form: 36rem,
|
|
345
|
+
),
|
|
346
|
+
),
|
|
347
|
+
'shell-rail': (
|
|
348
|
+
property: inline-size,
|
|
349
|
+
class: shell-rail,
|
|
350
|
+
responsive: true,
|
|
351
|
+
values: (
|
|
352
|
+
fixed: 16rem,
|
|
353
|
+
),
|
|
354
|
+
),
|
|
355
|
+
'inline-minimum': (
|
|
356
|
+
property: min-inline-size,
|
|
357
|
+
class: min-inline,
|
|
358
|
+
values: (
|
|
359
|
+
0: 0,
|
|
360
|
+
),
|
|
361
|
+
),
|
|
362
|
+
'table-viewport': (
|
|
363
|
+
property: max-block-size,
|
|
364
|
+
class: max-block,
|
|
365
|
+
responsive: true,
|
|
366
|
+
values: (
|
|
367
|
+
table: 70vh,
|
|
368
|
+
),
|
|
369
|
+
),
|
|
370
|
+
'tabular-figures': (
|
|
371
|
+
property: font-variant-numeric,
|
|
372
|
+
class: figures,
|
|
373
|
+
values: (
|
|
374
|
+
tabular: tabular-nums,
|
|
375
|
+
),
|
|
376
|
+
),
|
|
377
|
+
'letter-spacing': (
|
|
378
|
+
property: letter-spacing,
|
|
379
|
+
class: ls,
|
|
380
|
+
values: (
|
|
381
|
+
tight: -0.02em,
|
|
382
|
+
wide: 0.05em,
|
|
383
|
+
),
|
|
384
|
+
),
|
|
385
|
+
)
|
|
386
|
+
);
|
|
387
|
+
// Generate once, after all utility-map additions:
|
|
388
|
+
@import 'bootstrap/scss/utilities/api';
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Use `w-100 measure-form` for a bounded form, `measure-prose` for a reading column,
|
|
392
|
+
`shell-rail-lg-fixed flex-shrink-0` for an inline desktop rail, `min-inline-0` for its flexible
|
|
393
|
+
sibling, `max-block-lg-table` only for a warranted wide-screen bounded table scroller, `figures-tabular` for comparable
|
|
394
|
+
quantities, `ls-tight` on `display-*` and `fs-1`, and `ls-wide` with `text-uppercase` labels (`em`
|
|
395
|
+
is correct for tracking: it follows the element's own size). Verify those selectors in the
|
|
396
|
+
compiled output before using the examples below. The font must support tabular figures. A `ch`
|
|
397
|
+
measure is a starting width, not a character-count proof.
|
|
398
|
+
|
|
399
|
+
Without a Sass build, take an existing equivalent; otherwise propose the smallest stylesheet rule
|
|
400
|
+
under [SKILL.md](../SKILL.md) → When custom CSS is justified. Never ship an unresolved utility name.
|
|
207
401
|
|
|
208
402
|
## Forms in Production
|
|
209
403
|
|
|
210
404
|
### Layout & labels
|
|
211
405
|
|
|
212
|
-
- **Top-aligned labels by default** —
|
|
406
|
+
- **Top-aligned labels by default** — keep a consistent single-column scan. Reserve side labels for a deliberate dense layout that still reads correctly at narrow widths.
|
|
213
407
|
- Visible label or `.form-floating` — never placeholder-only (disappears on input, fails accessibility).
|
|
214
|
-
- **
|
|
215
|
-
-
|
|
408
|
+
- **Name the form without duplicating its heading.** Associate the form with an existing visible title through `aria-labelledby` where useful. Keep each control's own visible label; a form name does not label its fields.
|
|
409
|
+
- Use one field column by default; add columns only for genuinely related fields. At wide widths,
|
|
410
|
+
put supporting explanation beside the form rather than stretching its fields.
|
|
411
|
+
- Keep each label, control, help text, and error in one group. Use a smaller internal gap than the
|
|
412
|
+
gap to the next field group, and recheck the relationship when errors or long labels wrap.
|
|
216
413
|
|
|
217
414
|
```html
|
|
218
415
|
<form class="row g-3">
|
|
219
|
-
<div class="col-
|
|
416
|
+
<div class="col-12">
|
|
220
417
|
<label for="inputEmail4" class="form-label">Email</label>
|
|
221
418
|
<input type="email" class="form-control" id="inputEmail4" aria-describedby="emailHelp" />
|
|
222
419
|
<div id="emailHelp" class="form-text">Work address preferred.</div>
|
|
223
420
|
</div>
|
|
224
|
-
<div class="col-
|
|
421
|
+
<div class="col-12">
|
|
225
422
|
<label for="inputPassword4" class="form-label">Password</label>
|
|
226
423
|
<input type="password" class="form-control" id="inputPassword4" />
|
|
227
424
|
</div>
|
|
@@ -234,10 +431,10 @@ Remove with `map-remove($utilities, "width")` or set the key to `null`. This is
|
|
|
234
431
|
### Validation timing (the rules that matter)
|
|
235
432
|
|
|
236
433
|
- Validate a field **on blur** — after the user leaves it — never on every keystroke, and never before the user has reached the field. Exception: live feedback that _helps_ while typing (password strength, username availability, character counts).
|
|
237
|
-
-
|
|
434
|
+
- After a field enters an error state, re-validate as the user types so they see the fix land.
|
|
238
435
|
- Always re-check everything on submit. Keep the submit button **enabled** — a disabled submit hides _what is_ wrong; a validating submit shows it.
|
|
239
436
|
- On failed submit of a long form, render an **error summary** at the top (focus it; link each item to its field) _and_ inline messages at each field — never summary-only, never inline-only.
|
|
240
|
-
- Error style = color + icon + text, stating what is wrong and how to fix it. Wire message to field with `aria-describedby`, mark the field `aria-invalid="true"`. Never report errors
|
|
437
|
+
- Error style = color + icon + text, stating what is wrong and how to fix it. Wire message to field with `aria-describedby`, mark the field `aria-invalid="true"`. Never report errors through a hover tooltip.
|
|
241
438
|
|
|
242
439
|
### Bootstrap validation mechanics
|
|
243
440
|
|
|
@@ -274,7 +471,7 @@ Client-side, the documented pattern:
|
|
|
274
471
|
</script>
|
|
275
472
|
```
|
|
276
473
|
|
|
277
|
-
**Documented limitation (enterprise-critical):** Bootstrap's client-side validation styles and `valid/invalid-tooltip`s are **not exposed to assistive technologies**. For accessible flows use the server-side pattern — apply `.is-invalid` / `.is-valid` directly (no `.was-validated` parent needed), with `.invalid-feedback` linked
|
|
474
|
+
**Documented limitation (enterprise-critical):** Bootstrap's client-side validation styles and `valid/invalid-tooltip`s are **not exposed to assistive technologies**. For accessible flows use the server-side pattern — apply `.is-invalid` / `.is-valid` directly (no `.was-validated` parent needed), with `.invalid-feedback` linked through `aria-describedby` — or rely on native browser validation.
|
|
278
475
|
|
|
279
476
|
```html
|
|
280
477
|
<input
|
|
@@ -290,7 +487,7 @@ Client-side, the documented pattern:
|
|
|
290
487
|
</div>
|
|
291
488
|
```
|
|
292
489
|
|
|
293
|
-
Details: input groups with feedback need `.has-validation` on the group (border-radius fix). `.valid-tooltip` and `.invalid-tooltip` need a `position-relative` parent. Validation colors are mode-adaptive
|
|
490
|
+
Details: input groups with feedback need `.has-validation` on the group (border-radius fix). `.valid-tooltip` and `.invalid-tooltip` need a `position-relative` parent. Validation colors are mode-adaptive through `--bs-form-valid-color`, `--bs-form-valid-border-color`, `--bs-form-invalid-color`, `--bs-form-invalid-border-color`.
|
|
294
491
|
|
|
295
492
|
### Autosave vs explicit save
|
|
296
493
|
|
|
@@ -345,7 +542,7 @@ Hold the baseline in [SKILL.md](../SKILL.md) → Accessibility baseline. Its Boo
|
|
|
345
542
|
**Measuring the bars:**
|
|
346
543
|
|
|
347
544
|
- Hold the bars from [SKILL.md](../SKILL.md) → Surfaces, color, contrast: **≥ 4.5:1** for everything information-bearing, **≥ 3:1** for textless marks and state chrome. WCAG 2.2 permits 3:1 for large text; this package does not — size grants no lower tier.
|
|
348
|
-
- Measure
|
|
545
|
+
- Measure each declared theme from the compiled cascade, never from token names. A light-theme result does not establish a dark-theme result, and a skin's values are its own.
|
|
349
546
|
- Focus rings and hover fills are UI graphics: they are in scope for the 3:1 bar.
|
|
350
547
|
- Disabled controls are exempt from the bars by the spec. That exemption covers legibility, not meaning — see [Destructive actions](#destructive-actions) for the one disabled state that still has to change color.
|
|
351
548
|
|
|
@@ -353,9 +550,12 @@ Hold the baseline in [SKILL.md](../SKILL.md) → Accessibility baseline. Its Boo
|
|
|
353
550
|
|
|
354
551
|
- collects every painted layer from the element upward to the first opaque one, then composites them top over bottom (Porter-Duff `over`) onto that opaque base;
|
|
355
552
|
- composites a translucent foreground over that result before taking the ratio, rather than reading the declared color;
|
|
356
|
-
- measures
|
|
553
|
+
- measures each declared theme in the same run, since a theme swap re-points the tokens under every layer;
|
|
357
554
|
- carries a negative control drawn from outside the population it covers — a pairing known to fail — and voids the run if that negative control passes.
|
|
358
555
|
|
|
556
|
+
Include ancestor opacity in the painted stack. For images, gradients, masks, or blend modes the
|
|
557
|
+
reader does not support, use a suitable rendered-background measurement or leave the pairing open;
|
|
558
|
+
never flatten a variable background to its average color. Name reached states beside every result.
|
|
359
559
|
Wire the reader into the suite once it has settled a question.
|
|
360
560
|
|
|
361
561
|
### WCAG 2.2 deltas that bite dense app UI
|
|
@@ -383,7 +583,7 @@ Wire the reader into the suite once it has settled a question.
|
|
|
383
583
|
Browsers handle focus on full page loads; in an SPA **you** do:
|
|
384
584
|
|
|
385
585
|
- On route change, move focus to the new view's `h1` (or the `<main>` with `tabindex="-1"`) so SR users hear where they landed.
|
|
386
|
-
- On failed submit, focus the error summary. On destructive confirm, focus the dialog
|
|
586
|
+
- On failed submit, focus the error summary. On a compact destructive confirm, focus the safe action; in a scrolling or structured dialog, focus a static heading at the start when an action would scroll its context away.
|
|
387
587
|
- After deleting a row, move focus to a sensible neighbor (next row / the table region), never let it fall to `<body>`.
|
|
388
588
|
- Anything focused programmatically under sticky chrome needs the `scroll-margin-top` offset (2.4.11 above).
|
|
389
589
|
|
|
@@ -395,7 +595,10 @@ Bootstrap wraps its transitions and animations (`.fade`, `.collapsing`, carousel
|
|
|
395
595
|
|
|
396
596
|
### App shell
|
|
397
597
|
|
|
398
|
-
**Structure:**
|
|
598
|
+
**Structure:** reuse the product's shell. For a new product, let implemented features and their
|
|
599
|
+
navigation needs decide between a sidebar and a shallow top bar; do not design the shell first.
|
|
600
|
+
Give an inline rail a content-led width and the task the remaining space. A collapsible rail can
|
|
601
|
+
reclaim width for comparison data.
|
|
399
602
|
|
|
400
603
|
The Bootstrap implementation — a responsive offcanvas that renders inline above `lg` and becomes a drawer below it, with no custom JS:
|
|
401
604
|
|
|
@@ -421,7 +624,7 @@ The Bootstrap implementation — a responsive offcanvas that renders inline abov
|
|
|
421
624
|
|
|
422
625
|
<div class="d-flex">
|
|
423
626
|
<div
|
|
424
|
-
class="offcanvas-lg offcanvas-start border-end"
|
|
627
|
+
class="offcanvas-lg offcanvas-start border-end shell-rail-lg-fixed flex-shrink-0"
|
|
425
628
|
tabindex="-1"
|
|
426
629
|
id="appSidebar"
|
|
427
630
|
aria-labelledby="appSidebarLabel"
|
|
@@ -436,7 +639,7 @@ The Bootstrap implementation — a responsive offcanvas that renders inline abov
|
|
|
436
639
|
aria-label="Close"
|
|
437
640
|
></button>
|
|
438
641
|
</div>
|
|
439
|
-
<div class="offcanvas-body d-lg-block p-lg-3"
|
|
642
|
+
<div class="offcanvas-body d-lg-block p-lg-3">
|
|
440
643
|
<nav aria-label="Primary">
|
|
441
644
|
<ul class="nav nav-pills flex-column gap-1">
|
|
442
645
|
<li class="nav-item">
|
|
@@ -448,8 +651,8 @@ The Bootstrap implementation — a responsive offcanvas that renders inline abov
|
|
|
448
651
|
</nav>
|
|
449
652
|
</div>
|
|
450
653
|
</div>
|
|
451
|
-
<main id="main" class="flex-grow-1 p-3 p-lg-4
|
|
452
|
-
<!-- min-
|
|
654
|
+
<main id="main" class="flex-grow-1 p-3 p-lg-4 min-inline-0">
|
|
655
|
+
<!-- Generated min-inline-0 lets the task shrink inside the flex row. -->
|
|
453
656
|
</main>
|
|
454
657
|
</div>
|
|
455
658
|
</body>
|
|
@@ -468,12 +671,22 @@ The Bootstrap implementation — a responsive offcanvas that renders inline abov
|
|
|
468
671
|
|
|
469
672
|
**Craft rules:**
|
|
470
673
|
|
|
471
|
-
- **Align by
|
|
674
|
+
- **Align by comparison:** quantities and currency right-aligned (`text-end`, header too), with
|
|
675
|
+
consistent units and precision. Use the generated `figures-tabular` utility or the project's
|
|
676
|
+
equivalent. Keep text start-aligned; choose date alignment by its format and comparison task.
|
|
677
|
+
- **Group related content:** combine identity and supporting detail only when they do not need
|
|
678
|
+
independent column comparison or sorting. Keep key comparison columns explicit. Quiet repeated
|
|
679
|
+
labels and row actions before increasing density.
|
|
472
680
|
- **Density:** `table-sm` for compact; offer density as a user toggle (comfortable/compact) driven by one token or wrapper class, not per-cell tweaks. Do not shrink font below readability to fake density.
|
|
473
|
-
- **Sticky header**
|
|
681
|
+
- **Sticky header** when the table meaningfully scrolls (roughly a viewport / ~15+ rows). Not built into Bootstrap — the pattern:
|
|
474
682
|
|
|
475
683
|
```html
|
|
476
|
-
<div
|
|
684
|
+
<div
|
|
685
|
+
class="table-responsive max-block-lg-table"
|
|
686
|
+
role="region"
|
|
687
|
+
aria-label="Comparison table"
|
|
688
|
+
tabindex="0"
|
|
689
|
+
>
|
|
477
690
|
<table class="table table-sm align-middle">
|
|
478
691
|
<thead class="sticky-top">
|
|
479
692
|
<tr>
|
|
@@ -485,22 +698,22 @@ The Bootstrap implementation — a responsive offcanvas that renders inline abov
|
|
|
485
698
|
</div>
|
|
486
699
|
```
|
|
487
700
|
|
|
488
|
-
|
|
701
|
+
Keep sticky header cells on an **opaque, mode-aware surface** such as `bg-body-secondary`. Stock `.table` cells use the body background; verify that a skin or override has not made them translucent. Do not substitute a `.table-*` color variant and assume it adapts. Inspect cell overlays through [Tables and overlays](color-modes.md#tables-and-overlays). Sticky chrome is the prime Focus-Not-Obscured offender: add `scroll-margin-top` on row focusables equal to the header height. Sticky first column only when row identity is lost on horizontal scroll — it costs paint and complexity.
|
|
489
702
|
|
|
490
703
|
- **Sorting:** the whole header is a button (not a bare caret), with a visible direction indicator, and `aria-sort="ascending|descending"` on the active `<th>` only:
|
|
491
704
|
|
|
492
705
|
```html
|
|
493
706
|
<th scope="col" aria-sort="ascending">
|
|
494
|
-
<button type="button" class="btn btn-link p-0 fw-semibold
|
|
707
|
+
<button type="button" class="btn btn-link p-0 fw-semibold">
|
|
495
708
|
Amount <span aria-hidden="true">↑</span>
|
|
496
709
|
</button>
|
|
497
710
|
</th>
|
|
498
711
|
```
|
|
499
712
|
|
|
500
713
|
- **Row actions:** 1–3 high-frequency actions inline; the rest behind a per-row kebab (dropdown). Hover-only reveal fails touch and keyboard — keep at least the overflow trigger always visible and ≥24px.
|
|
501
|
-
- **Selection & bulk actions:** header checkbox with indeterminate state for partial selection; per-row checkboxes with `aria-label` naming the row ("Select INV-1042"). When selection > 0, swap the toolbar's content in place for a contextual bar — "3 selected", the batch actions, and a clear-selection escape — never push the layout down (layout-shifting chrome is an anti-pattern). Announce the count
|
|
714
|
+
- **Selection & bulk actions:** header checkbox with indeterminate state for partial selection; per-row checkboxes with `aria-label` naming the row ("Select INV-1042"). When selection > 0, swap the toolbar's content in place for a contextual bar — "3 selected", the batch actions, and a clear-selection escape — never push the layout down (layout-shifting chrome is an anti-pattern). Announce the count through a polite live region.
|
|
502
715
|
- **Pagination vs scrolling:** paginate when users need position, totals, deep links, and "go to page N" — most enterprise CRUD. Virtualize (windowed rendering) for long uniform lists where scrolling is natural. True infinite scroll is for exploratory feeds only — never where users need a footer or a findable end.
|
|
503
|
-
- **Responsive,
|
|
716
|
+
- **Responsive, by task:** use a compact record list for record work, a locally scrollable semantic table for essential comparison, or priority columns with an operable detail path. Preserve identity, decision fields, and actions. Choose expansion from available container width, not `md` by habit. Keep one state model across variants; take the contract from [Keep the task intact](responsive-layout.md#keep-the-task-intact).
|
|
504
717
|
- **Table states:** loading → **skeleton rows** matching the real column count/widths (a centered spinner collapses the layout); empty → distinguish _no data yet_ (invite the first action) from _no results for these filters_ (offer "Clear filters"); error → inline retry inside the table region, header and toolbar preserved.
|
|
505
718
|
|
|
506
719
|
### Filter & search bars
|
|
@@ -508,7 +721,7 @@ Give header cells an **opaque background** (`bg-body-secondary` or a `.table-*`
|
|
|
508
721
|
- One toolbar above the table: search input first (`role="search"` on the form), then the 2–4 highest-value filters as `form-select`/segmented controls, overflow filters behind a "Filters" button (offcanvas on mobile, dropdown/collapse on desktop).
|
|
509
722
|
- **Active filters must be visible and dismissible** — chips/badges with an ✕ and a "Clear all" — users must see _why_ the list is short. A filtered-empty state repeats the escape hatch.
|
|
510
723
|
- Debounce live search; show result counts ("128 results") so feedback is immediate; filter state belongs in the URL when views are shareable.
|
|
511
|
-
-
|
|
724
|
+
- At the base, give search a full row; stack or wrap actions and filters without shrinking labels or targets. Expand with `col-12 col-md`, `col-md-auto`, or `d-grid d-sm-flex` when they fit. Reserve a horizontal scroller for a documented spatial interaction, not an ordinary toolbar. Keep active filters and the clear path outside any disclosed extras.
|
|
512
725
|
|
|
513
726
|
### Wizards & multi-step forms
|
|
514
727
|
|
|
@@ -525,19 +738,25 @@ Give header cells an **opaque background** (`bg-body-secondary` or a `.table-*`
|
|
|
525
738
|
Design **every one** for every data surface: ideal (populated), empty, loading, partial, error. A component is not done until all of them exist.
|
|
526
739
|
|
|
527
740
|
- **Skeleton vs spinner:** skeleton (`placeholder` + `placeholder-glow`) when you know the content's shape and it fills a region — tables, cards, detail panes — because it holds layout and shortens perceived wait. Spinner for short, indeterminate, or in-control waits (inside a button, a small inline fetch).
|
|
528
|
-
- **
|
|
741
|
+
- **Wait feedback:** acknowledge the action promptly, avoid flashing a loader for trivial waits, and keep the known layout stable. For longer work, show actual steps or measured progress when available; otherwise state that work continues and offer cancellation where supported. Never invent a percentage.
|
|
529
742
|
- **Optimistic vs pessimistic:** apply UI immediately and reconcile (rolling back loudly on failure) for reversible high-frequency actions — toggles, stars, reorders. Await confirmation for money, audited records, and anything a rollback would confuse.
|
|
530
|
-
- **
|
|
743
|
+
- **First-use empty:** name what belongs here and the useful create/import action. Drop tabs or
|
|
744
|
+
filters only when they genuinely have no data to operate on. An illustration may support that
|
|
745
|
+
action; it must not replace it.
|
|
746
|
+
- **Filtered-empty:** retain the active filters and result context, explain that nothing matched,
|
|
747
|
+
and offer a clear-filter path. Never hide the controls needed to undo the empty result.
|
|
748
|
+
- **Partial:** keep available data readable, identify the missing or stale part, and scope recovery
|
|
749
|
+
to it. Missing is not zero. Do not collapse the whole surface into an error when some data exists.
|
|
531
750
|
- **Every error state states what failed and how to fix it**, carries a keyboard-reachable retry in place, and preserves surrounding context — a body fetch failure must not blow away the toolbar and filters.
|
|
532
751
|
|
|
533
752
|
### Feedback discipline
|
|
534
753
|
|
|
535
|
-
| Channel | Use for
|
|
536
|
-
| ----------------------------- |
|
|
537
|
-
| **Toast** | Transient confirmation of a
|
|
538
|
-
| **Inline alert** | Feedback tied to a specific field/section/action; persists in context
|
|
539
|
-
| **Banner** (page-level alert) | Persistent page/app conditions — outage, trial expiring, permissions
|
|
540
|
-
| **Modal / alertdialog** | Blocking decisions the user must resolve now
|
|
754
|
+
| Channel | Use for | Never for |
|
|
755
|
+
| ----------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------- |
|
|
756
|
+
| **Toast** | Transient confirmation of an action that finished a moment ago; auto-dismiss; `role="status"` | Errors needing action; anything the user must read |
|
|
757
|
+
| **Inline alert** | Feedback tied to a specific field/section/action; persists in context | App-wide conditions |
|
|
758
|
+
| **Banner** (page-level alert) | Persistent page/app conditions — outage, trial expiring, permissions | Action confirmations |
|
|
759
|
+
| **Modal / alertdialog** | Blocking decisions the user must resolve now | FYIs, success messages |
|
|
541
760
|
|
|
542
761
|
Blocking errors are never toasts. Keep the acting verb consistent across the flow: the "Publish" button confirms with "Published".
|
|
543
762
|
|
|
@@ -546,16 +765,20 @@ Blocking errors are never toasts. Keep the acting verb consistent across the flo
|
|
|
546
765
|
Match friction to reversibility × blast radius:
|
|
547
766
|
|
|
548
767
|
1. **Undo** (soft-delete + toast with Undo) for reversible, low-stakes, frequent actions — least friction, best experience. Prefer making actions undoable over interrupting them.
|
|
549
|
-
2. **Confirm dialog** for irreversible-but-scoped operations. Restate the specific consequence ("This permanently deletes 3 invoices"), verb-labeled buttons ("Delete invoices" / "Cancel" — never Yes/No), destructive action visually separated from safe; `alertdialog` semantics; focus lands on the safe action.
|
|
768
|
+
2. **Confirm dialog** for irreversible-but-scoped operations. Restate the specific consequence ("This permanently deletes 3 invoices"), verb-labeled buttons ("Delete invoices" / "Cancel" — never Yes/No), destructive action visually separated from safe; `alertdialog` semantics; focus lands on the safe action for a compact confirmation, or a static top heading when focusing an action would scroll the consequences out of view.
|
|
550
769
|
3. **Type-to-confirm** (type the entity name) only for high-blast-radius irreversible operations — delete an org, drop a dataset.
|
|
551
770
|
|
|
771
|
+
Keep action rank separate from consequence. A row-level destructive action can use a measured
|
|
772
|
+
quiet treatment; emphasize the final destructive commit where the ladder makes it the decision.
|
|
773
|
+
Do not make every row's delete button compete with the page's primary action.
|
|
774
|
+
|
|
552
775
|
Do not type-gate a single-row delete; do not one-tap a tenant wipe. Confirm only where this ladder calls for it — a confirmation on every action gets clicked through.
|
|
553
776
|
|
|
554
777
|
**Neutralize a disabled destructive control.** `btn-danger` at full saturation reads as armed whatever the `disabled` attribute says, and the contrast exemption for disabled controls does not excuse it. While the action is unavailable, drop to the neutral or outline `btn-*` class (or let the disabled state mute the fill) so the color stops promising an action, and say _why_ it is unavailable in text the assistive layer reaches: `aria-describedby` pointing at the reason, with `title` only as the pointer-user convenience on top. Never use `title` alone — it never reaches a keyboard or screen-reader user, and it disappears on touch.
|
|
555
778
|
|
|
556
779
|
## RTL
|
|
557
780
|
|
|
558
|
-
- Enable per page: `<html lang="ar" dir="rtl">` + the RTL stylesheet `bootstrap.rtl.min.css` (built from the same source
|
|
781
|
+
- Enable per page: `<html lang="ar" dir="rtl">` + the RTL stylesheet `bootstrap.rtl.min.css` (built from the same source through RTLCSS). RTL support is documented as experimental.
|
|
559
782
|
- The logical properties model is why the utilities say start/end: `ms-*`/`me-*`, `ps-*`/`pe-*`, `text-start`/`text-end`, `float-start`/`float-end`, `offcanvas-start`/`end` all flip automatically. **Never write `left`/`right` positioning or physical margins in custom CSS** — use logical properties (`margin-inline-start`, `inset-inline-end`) so your custom rules flip too.
|
|
560
783
|
- Caveats: shipping LTR+RTL simultaneously costs significant extra CSS; the breadcrumb divider needs `$breadcrumb-divider-flipped`; source Sass can embed RTLCSS directives (`/* rtl: … */`) for value swaps like font stacks.
|
|
561
784
|
|
|
@@ -563,15 +786,15 @@ Do not type-gate a single-row delete; do not one-tap a tenant wipe. Confirm only
|
|
|
563
786
|
|
|
564
787
|
- Hide chrome, keep the data: `d-print-none` on nav, sidebars, toolbars, action buttons; the report/table itself stays printable.
|
|
565
788
|
- `d-print-block`/`d-print-table` can resurface content hidden on screen (a print-only header with report title/date).
|
|
566
|
-
- Print-check data screens users will export: collapse interactive affordances (sort carets, checkboxes)
|
|
789
|
+
- Print-check data screens users will export: collapse interactive affordances (sort carets, checkboxes) through `d-print-none`, and prefer `table-bordered` legibility over hover/stripe effects that may not print.
|
|
567
790
|
|
|
568
791
|
## Performance
|
|
569
792
|
|
|
570
|
-
- **
|
|
571
|
-
- **Compressed, the full build is cheap; incomplete builds are not.** Trimming
|
|
572
|
-
- **Icons:** Bootstrap Icons is a separate package — prefer inline SVG or an SVG sprite (crisp, styleable
|
|
793
|
+
- **Keep one CSS system.** Extend the installed Bootstrap theme; do not add a competing framework or a parallel palette to restyle the surface.
|
|
794
|
+
- **Compressed, the full build is cheap; incomplete builds are not.** Trimming through a Sass-subset build (import only the parts used — see [Theming](#theming--design-tokens)) is the sanctioned diet. Aggressive purge tools are the risky one: Bootstrap adds classes **at runtime** (`show`, `showing`, `fade`, `collapsing`, `modal-open`, `modal-backdrop`, `offcanvas-backdrop`, tooltip/popover generated markup) — purging without safelisting them ships UIs whose modals silently stop rendering. If you purge, safelist every JS-toggled class and test every overlay.
|
|
795
|
+
- **Icons:** Bootstrap Icons is a separate package — prefer inline SVG or an SVG sprite (crisp, styleable through `currentColor`, no font flash) over the icon font; load only the icons used.
|
|
573
796
|
- **JS:** the bundle is small, but only load it where behavior exists; per-component ESM imports (`bootstrap/js/dist/modal`) trim further in bundlers.
|
|
574
|
-
- **Fonts:**
|
|
797
|
+
- **Fonts:** reuse the existing families and load only needed weights/scripts. Add a display face only for a distinct role; use `font-display: swap` and test fallback wrapping. Keep the data face legible before and after fonts load.
|
|
575
798
|
|
|
576
799
|
## When Not to Hand-Roll
|
|
577
800
|
|
|
@@ -587,8 +810,8 @@ Bootstrap has **no** combobox/autocomplete, date picker, multi-select tags input
|
|
|
587
810
|
### Centered content
|
|
588
811
|
|
|
589
812
|
```html
|
|
590
|
-
<div class="d-flex
|
|
591
|
-
<div>Centered content</div>
|
|
813
|
+
<div class="d-flex flex-column min-vh-100 p-3">
|
|
814
|
+
<div class="my-auto">Centered content</div>
|
|
592
815
|
</div>
|
|
593
816
|
```
|
|
594
817
|
|