@orkestrel/scaffold 0.0.18 → 0.0.20

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.
Files changed (70) hide show
  1. package/dist/host/AGENTS.md +4 -2
  2. package/dist/host/CLAUDE.md +30 -12
  3. package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +267 -0
  4. package/dist/host/agents/skills/enterprise-bootstrap/agents/openai.yaml +4 -0
  5. package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +609 -0
  6. package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +994 -0
  7. package/dist/host/agents/skills/enterprise-bootstrap/references/frontend-design.md +59 -0
  8. package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +312 -0
  9. package/dist/host/agents/skills/orkestrel-align-packages/references/integration.md +4 -13
  10. package/dist/host/agents/skills/orkestrel-build-application/SKILL.md +78 -50
  11. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +91 -0
  12. package/dist/host/agents/skills/orkestrel-debrief/agents/openai.yaml +4 -0
  13. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +75 -0
  14. package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +70 -0
  15. package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +11 -9
  16. package/dist/host/agents/skills/orkestrel-harden-package/references/centralization.md +44 -59
  17. package/dist/host/agents/skills/orkestrel-harden-package/references/hardening.md +14 -6
  18. package/dist/host/agents/skills/orkestrel-polish-surface/SKILL.md +113 -0
  19. package/dist/host/agents/skills/orkestrel-polish-surface/agents/openai.yaml +4 -0
  20. package/dist/host/agents/skills/orkestrel-polish-surface/references/capture-harness.md +82 -0
  21. package/dist/host/claude/agents/application.md +21 -17
  22. package/dist/host/claude/agents/builder.md +2 -0
  23. package/dist/host/claude/agents/codex.md +33 -26
  24. package/dist/host/claude/agents/grok.md +7 -0
  25. package/dist/host/claude/agents/implementer.md +2 -1
  26. package/dist/host/claude/agents/orkestrel.md +20 -15
  27. package/dist/host/claude/agents/planner.md +2 -1
  28. package/dist/host/claude/agents/researcher.md +31 -0
  29. package/dist/host/claude/agents/reviewer.md +6 -0
  30. package/dist/host/claude/agents/scout.md +25 -0
  31. package/dist/host/claude/rules/documentation.md +1 -0
  32. package/dist/host/claude/rules/names.md +5 -7
  33. package/dist/host/claude/rules/quality.md +7 -5
  34. package/dist/host/claude/rules/styles.md +1 -0
  35. package/dist/host/claude/rules/tests.md +1 -0
  36. package/dist/host/claude/rules/typescript.md +3 -10
  37. package/dist/host/claude/rules/workspace.md +2 -5
  38. package/dist/host/claude/skills/enterprise-bootstrap/SKILL.md +12 -0
  39. package/dist/host/claude/skills/orkestrel-debrief/SKILL.md +12 -0
  40. package/dist/host/claude/skills/orkestrel-polish-surface/SKILL.md +12 -0
  41. package/dist/host/codex/agents/analyst.toml +6 -3
  42. package/dist/host/codex/agents/application.toml +17 -19
  43. package/dist/host/codex/agents/builder.toml +3 -2
  44. package/dist/host/codex/agents/checker.toml +4 -2
  45. package/dist/host/codex/agents/grok.toml +3 -1
  46. package/dist/host/codex/agents/implementer.toml +4 -2
  47. package/dist/host/codex/agents/opus.toml +5 -3
  48. package/dist/host/codex/agents/orkestrel.toml +6 -5
  49. package/dist/host/codex/agents/planner.toml +6 -2
  50. package/dist/host/codex/agents/researcher.toml +22 -0
  51. package/dist/host/codex/agents/reviewer.toml +7 -2
  52. package/dist/host/codex/agents/scout.toml +18 -0
  53. package/dist/host/codex/config.toml +16 -4
  54. package/dist/host/dotfiles/prettierignore +3 -0
  55. package/dist/host/guides/src/scaffold.md +42 -12
  56. package/dist/host/manifest.json +109 -3
  57. package/dist/src/core/index.cjs +162 -14
  58. package/dist/src/core/index.cjs.map +1 -1
  59. package/dist/src/core/index.d.cts +17 -6
  60. package/dist/src/core/index.d.ts +17 -6
  61. package/dist/src/core/index.js +162 -15
  62. package/dist/src/core/index.js.map +1 -1
  63. package/dist/src/server/index.cjs +9 -3
  64. package/dist/src/server/index.cjs.map +1 -1
  65. package/dist/src/server/index.d.cts +2 -1
  66. package/dist/src/server/index.d.ts +2 -1
  67. package/dist/src/server/index.js +10 -4
  68. package/dist/src/server/index.js.map +1 -1
  69. package/package.json +1 -1
  70. package/dist/host/agents/skills/orkestrel-build-application/references/application.md +0 -129
@@ -0,0 +1,994 @@
1
+ # Bootstrap 5 Component Reference
2
+
3
+ > Part of the `enterprise-bootstrap` package. Bootstrap **5.3.x** component
4
+ > markup + enterprise selection notes. Utility classes: [utilities.md](utilities.md).
5
+ > Theming, forms deep-dive, JS lifecycle, patterns: [bootstrap-reference.md](bootstrap-reference.md).
6
+
7
+ ## Contents
8
+
9
+ - [Component list](#complete-component-list) — layout, content, form components
10
+ - Markup: [Accordion](#accordion) · [Alerts](#alerts) · [Badge](#badge) · [Breadcrumb](#breadcrumb) · [Buttons](#buttons) · [Button group](#button-group) · [Card](#card) · [Carousel](#carousel) · [Close button](#close-button) · [Collapse](#collapse) · [Dropdown](#dropdown) · [List group](#list-group) · [Modal](#modal) · [Navbar](#navbar) · [Navs & tabs](#navs--tabs) · [Offcanvas](#offcanvas) · [Pagination](#pagination) · [Placeholder](#placeholder-skeletons) · [Popover](#popover-requires-popperjs) · [Progress](#progress) · [Scrollspy](#scrollspy) · [Spinners](#spinners) · [Tables](#tables) · [Toasts](#toasts) · [Tooltip](#tooltip-requires-popperjs)
11
+ - [JavaScript initialization](#javascript-initialization) — which components need JS, which auto-init
12
+ - [Icons](#icons) — icon sourcing, status glyph marks
13
+ - [Enterprise notes](#enterprise-notes-components) — choosing components, forms, selection fills, navigation, theming
14
+
15
+ ## Complete Component List
16
+
17
+ ### Layout Components
18
+
19
+ - Containers: `.container`, `.container-fluid`, `.container-{breakpoint}`
20
+ - Grid: `.row`, `.col`, `.col-{1-12}`, `.col-{breakpoint}-{1-12}`
21
+ - CSS Grid (opt-in): `.grid`, `.g-col-{1-12}`
22
+
23
+ ### Content Components
24
+
25
+ - Typography: `.h1`–`.h6`, `.display-1`–`.display-6`, `.lead`, `.small`
26
+ - Images: `.img-fluid`, `.img-thumbnail`, `.figure`
27
+ - Tables: `.table` + variants — see [Tables](#tables)
28
+ - Figures: `.figure`, `.figure-img`, `.figure-caption`
29
+
30
+ ### Form Components
31
+
32
+ - Control: `.form-control`, `.form-control-lg/sm`, `.form-select`
33
+ - Check/Radio: `.form-check`, `.form-check-input`, `.form-check-label`
34
+ - Switch: `.form-switch`
35
+ - Range: `.form-range`
36
+ - Floating: `.form-floating`
37
+ - Input Group: `.input-group`, `.input-group-text`, `.has-validation`
38
+ - Validation: `.was-validated`, `.is-valid/invalid`, `.valid/invalid-feedback/tooltip`
39
+
40
+ Full form patterns and validation JS: [bootstrap-reference.md](bootstrap-reference.md) → Forms in production.
41
+
42
+ ## Component Markup
43
+
44
+ ### Accordion
45
+
46
+ ```html
47
+ <div class="accordion" id="accordionExample">
48
+ <div class="accordion-item">
49
+ <h2 class="accordion-header">
50
+ <button
51
+ class="accordion-button"
52
+ type="button"
53
+ data-bs-toggle="collapse"
54
+ data-bs-target="#collapseOne"
55
+ aria-expanded="true"
56
+ aria-controls="collapseOne"
57
+ >
58
+ Item #1
59
+ </button>
60
+ </h2>
61
+ <div
62
+ id="collapseOne"
63
+ class="accordion-collapse collapse show"
64
+ data-bs-parent="#accordionExample"
65
+ >
66
+ <div class="accordion-body">Body content</div>
67
+ </div>
68
+ </div>
69
+ <div class="accordion-item">
70
+ <h2 class="accordion-header">
71
+ <button
72
+ class="accordion-button collapsed"
73
+ type="button"
74
+ data-bs-toggle="collapse"
75
+ data-bs-target="#collapseTwo"
76
+ aria-expanded="false"
77
+ aria-controls="collapseTwo"
78
+ >
79
+ Item #2
80
+ </button>
81
+ </h2>
82
+ <div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
83
+ <div class="accordion-body">Body content</div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ ```
88
+
89
+ Variants: `.accordion-flush` (edge-to-edge, no outer borders); omit `data-bs-parent` to allow multiple items open.
90
+
91
+ ### Alerts
92
+
93
+ ```html
94
+ <div class="alert alert-primary" role="alert">Primary alert</div>
95
+ <div class="alert alert-success" role="alert">Success alert</div>
96
+ <div class="alert alert-danger" role="alert">Danger alert</div>
97
+ <div class="alert alert-warning" role="alert">Warning alert</div>
98
+
99
+ <div class="alert alert-primary d-flex align-items-center" role="alert">
100
+ <svg class="bi flex-shrink-0 me-2" role="img" aria-label="Info:">...</svg>
101
+ <div>Alert with icon</div>
102
+ </div>
103
+
104
+ <div class="alert alert-success alert-dismissible fade show" role="alert">
105
+ <h4 class="alert-heading">Well done!</h4>
106
+ <p>Content here.</p>
107
+ <hr />
108
+ <p class="mb-0">Additional info.</p>
109
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
110
+ </div>
111
+ ```
112
+
113
+ `role="alert"` announces immediately when the element is injected into the DOM — right for errors and warnings. For calm status messages injected dynamically, prefer a polite live region (`role="status"`). Anything that _looks_ like an alert carries the alert role: styling and semantics disagree the moment a notice wears `.alert` chrome with no role, and an accessibility snapshot is what catches it. When to use alert vs toast vs banner: [bootstrap-reference.md](bootstrap-reference.md) → Feedback discipline.
114
+
115
+ ### Badge
116
+
117
+ ```html
118
+ <span class="badge text-bg-primary">Primary</span>
119
+ <span class="badge text-bg-secondary">Secondary</span>
120
+ <span class="badge text-bg-success">Success</span>
121
+ <span class="badge text-bg-danger">Danger</span>
122
+ <span class="badge text-bg-warning">Warning</span>
123
+ <span class="badge text-bg-info">Info</span>
124
+ <span class="badge text-bg-light">Light</span>
125
+ <span class="badge text-bg-dark">Dark</span>
126
+
127
+ <span class="badge rounded-pill text-bg-primary">Pill badge</span>
128
+
129
+ <!-- Notification counter positioned on a control -->
130
+ <button type="button" class="btn btn-primary position-relative">
131
+ Inbox
132
+ <span
133
+ class="position-absolute top-0 start-100 translate-middle badge rounded-pill text-bg-danger"
134
+ >
135
+ 9<span class="visually-hidden">unread messages</span>
136
+ </span>
137
+ </button>
138
+ ```
139
+
140
+ Use `text-bg-*` (auto-contrasting text) rather than `bg-*` alone. A badge is never the only carrier of meaning — pair color with text or a visually-hidden label.
141
+
142
+ **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.
143
+
144
+ **A badge's fill is never assumed.** Stock `.badge` carries no background of its own, but compatible skins may give it one, so an "unfilled" badge can arrive painted and land at a contrast the design never intended. State the fill explicitly — `bg-*-subtle` for a muted badge, `bg-transparent` when the surface behind it must show through — and measure the result in both themes against the cascade the page actually loads.
145
+
146
+ A badge reporting an in-flight request is a live region: `role="status"` on the badge (or on the small wrapper that holds it) announces the settled state politely without stealing focus. Reserve `role="alert"` for alert-styled notices ([Alerts](#alerts)).
147
+
148
+ ### Breadcrumb
149
+
150
+ ```html
151
+ <nav style="--bs-breadcrumb-divider: '>';" aria-label="Breadcrumb">
152
+ <ol class="breadcrumb">
153
+ <li class="breadcrumb-item"><a href="#">Home</a></li>
154
+ <li class="breadcrumb-item"><a href="#">Library</a></li>
155
+ <li class="breadcrumb-item active" aria-current="page">Data</li>
156
+ </ol>
157
+ </nav>
158
+ ```
159
+
160
+ The current page is `aria-current="page"` and not a link. Use breadcrumbs only for genuinely hierarchical models — in flat or tabbed apps they are noise.
161
+
162
+ ### Buttons
163
+
164
+ ```html
165
+ <button type="button" class="btn btn-primary">Primary</button>
166
+ <button type="button" class="btn btn-secondary">Secondary</button>
167
+ <button type="button" class="btn btn-success">Success</button>
168
+ <button type="button" class="btn btn-danger">Danger</button>
169
+ <button type="button" class="btn btn-warning">Warning</button>
170
+ <button type="button" class="btn btn-info">Info</button>
171
+ <button type="button" class="btn btn-light">Light</button>
172
+ <button type="button" class="btn btn-dark">Dark</button>
173
+ <button type="button" class="btn btn-link">Link</button>
174
+
175
+ <button type="button" class="btn btn-outline-primary">Outline</button>
176
+ <button type="button" class="btn btn-outline-secondary">Outline Secondary</button>
177
+
178
+ <button type="button" class="btn btn-primary btn-lg">Large</button>
179
+ <button type="button" class="btn btn-primary btn-sm">Small</button>
180
+
181
+ <button type="button" class="btn btn-primary" disabled>Disabled</button>
182
+ <a class="btn btn-primary disabled" role="button" aria-disabled="true">Disabled Link</a>
183
+
184
+ <button type="button" class="btn btn-primary" data-bs-toggle="button">Toggle</button>
185
+ ```
186
+
187
+ 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.
188
+
189
+ ### Button Group
190
+
191
+ ```html
192
+ <div class="btn-group" role="group" aria-label="Basic example">
193
+ <button type="button" class="btn btn-primary">Left</button>
194
+ <button type="button" class="btn btn-primary">Middle</button>
195
+ <button type="button" class="btn btn-primary">Right</button>
196
+ </div>
197
+
198
+ <div class="btn-toolbar" role="toolbar" aria-label="Toolbar">
199
+ <div class="btn-group me-2" role="group">...</div>
200
+ <div class="btn-group me-2" role="group">...</div>
201
+ </div>
202
+
203
+ <div class="btn-group-vertical">
204
+ <button type="button" class="btn btn-primary">Top</button>
205
+ <button type="button" class="btn btn-primary">Middle</button>
206
+ <button type="button" class="btn btn-primary">Bottom</button>
207
+ </div>
208
+ ```
209
+
210
+ ### Card
211
+
212
+ ```html
213
+ <div class="card">
214
+ <div class="card-header">Header</div>
215
+ <img src="..." class="card-img-top" alt="..." />
216
+ <div class="card-body">
217
+ <h5 class="card-title">Title</h5>
218
+ <h6 class="card-subtitle mb-2 text-body-secondary">Subtitle</h6>
219
+ <p class="card-text">Text content.</p>
220
+ <a href="#" class="card-link">Link</a>
221
+ <a href="#" class="btn btn-primary">Button</a>
222
+ </div>
223
+ <ul class="list-group list-group-flush">
224
+ <li class="list-group-item">Item</li>
225
+ </ul>
226
+ <div class="card-footer text-body-secondary">Footer</div>
227
+ </div>
228
+
229
+ <div class="card text-bg-primary">Colored card</div>
230
+ <div class="card border-primary">Bordered card</div>
231
+ <div class="card-group">Card group</div>
232
+ <div class="row row-cols-1 row-cols-md-3 g-4">Card grid (with h-100 on cards)</div>
233
+ ```
234
+
235
+ ### Carousel
236
+
237
+ ```html
238
+ <div id="carouselExample" class="carousel slide" data-bs-ride="carousel">
239
+ <div class="carousel-indicators">
240
+ <button
241
+ type="button"
242
+ data-bs-target="#carouselExample"
243
+ data-bs-slide-to="0"
244
+ class="active"
245
+ aria-current="true"
246
+ ></button>
247
+ <button type="button" data-bs-target="#carouselExample" data-bs-slide-to="1"></button>
248
+ </div>
249
+ <div class="carousel-inner">
250
+ <div class="carousel-item active">
251
+ <img src="..." class="d-block w-100" alt="..." />
252
+ <div class="carousel-caption d-none d-md-block">
253
+ <h5>Caption</h5>
254
+ <p>Description</p>
255
+ </div>
256
+ </div>
257
+ <div class="carousel-item">
258
+ <img src="..." class="d-block w-100" alt="..." />
259
+ </div>
260
+ </div>
261
+ <button
262
+ class="carousel-control-prev"
263
+ type="button"
264
+ data-bs-target="#carouselExample"
265
+ data-bs-slide="prev"
266
+ >
267
+ <span class="carousel-control-prev-icon" aria-hidden="true"></span>
268
+ <span class="visually-hidden">Previous</span>
269
+ </button>
270
+ <button
271
+ class="carousel-control-next"
272
+ type="button"
273
+ data-bs-target="#carouselExample"
274
+ data-bs-slide="next"
275
+ >
276
+ <span class="carousel-control-next-icon" aria-hidden="true"></span>
277
+ <span class="visually-hidden">Next</span>
278
+ </button>
279
+ </div>
280
+ ```
281
+
282
+ `.carousel-dark` is deprecated — use `data-bs-theme="dark"` on the carousel instead. Auto-advancing carousels rarely belong in product UI.
283
+
284
+ ### Close Button
285
+
286
+ ```html
287
+ <button type="button" class="btn-close" aria-label="Close"></button>
288
+ <button type="button" class="btn-close" disabled aria-label="Close"></button>
289
+
290
+ <!-- On dark surfaces: .btn-close-white is DEPRECATED — scope the theme instead -->
291
+ <button type="button" class="btn-close" data-bs-theme="dark" aria-label="Close"></button>
292
+ ```
293
+
294
+ ### Collapse
295
+
296
+ ```html
297
+ <p>
298
+ <button
299
+ class="btn btn-primary"
300
+ type="button"
301
+ data-bs-toggle="collapse"
302
+ data-bs-target="#collapseExample"
303
+ aria-expanded="false"
304
+ aria-controls="collapseExample"
305
+ >
306
+ Toggle
307
+ </button>
308
+ </p>
309
+ <div class="collapse" id="collapseExample">
310
+ <div class="card card-body">Content here.</div>
311
+ </div>
312
+ ```
313
+
314
+ Multiple targets: give each panel `.multi-collapse` and point separate triggers at each id (or one trigger at a shared selector). The trigger button IS the disclosure pattern: `aria-expanded` + `aria-controls`, Enter/Space toggles — nothing more needed.
315
+
316
+ ### Dropdown
317
+
318
+ ```html
319
+ <div class="dropdown">
320
+ <button
321
+ class="btn btn-secondary dropdown-toggle"
322
+ type="button"
323
+ data-bs-toggle="dropdown"
324
+ aria-expanded="false"
325
+ >
326
+ Dropdown button
327
+ </button>
328
+ <ul class="dropdown-menu">
329
+ <li><a class="dropdown-item" href="#">Action</a></li>
330
+ <li><a class="dropdown-item" href="#">Another action</a></li>
331
+ <li><hr class="dropdown-divider" /></li>
332
+ <li><a class="dropdown-item" href="#">Something else</a></li>
333
+ </ul>
334
+ </div>
335
+
336
+ <!-- Directions: wrap in .btn-group with .dropup / .dropend / .dropstart -->
337
+
338
+ <ul class="dropdown-menu dropdown-menu-end">
339
+ Right-aligned
340
+ </ul>
341
+ <ul class="dropdown-menu">
342
+ <li><h6 class="dropdown-header">Header</h6></li>
343
+ <li><span class="dropdown-item-text">Text</span></li>
344
+ <li><a class="dropdown-item active" aria-current="true" href="#">Active</a></li>
345
+ <li><a class="dropdown-item disabled" aria-disabled="true">Disabled</a></li>
346
+ </ul>
347
+
348
+ <div class="dropdown-menu p-4">
349
+ <form>Form content</form>
350
+ </div>
351
+ ```
352
+
353
+ `.dropdown-menu-dark` is deprecated — use `data-bs-theme="dark"` on the menu or an ancestor. Dropdowns have full keyboard support (arrows, Esc) built in. A dropdown is a **command menu** — for choosing a form value use `.form-select`, never a styled dropdown pretending to be an input.
354
+
355
+ ### List Group
356
+
357
+ ```html
358
+ <ul class="list-group">
359
+ <li class="list-group-item">Item</li>
360
+ <li class="list-group-item active" aria-current="true">Active</li>
361
+ <li class="list-group-item disabled" aria-disabled="true">Disabled</li>
362
+ <li class="list-group-item list-group-item-primary">Primary</li>
363
+ </ul>
364
+
365
+ <div class="list-group">
366
+ <a href="#" class="list-group-item list-group-item-action active" aria-current="true">
367
+ <div class="d-flex w-100 justify-content-between">
368
+ <h5 class="mb-1">Heading</h5>
369
+ <small>3 days ago</small>
370
+ </div>
371
+ <p class="mb-1">Content.</p>
372
+ <small>Footer text.</small>
373
+ </a>
374
+ </div>
375
+
376
+ <ul class="list-group list-group-horizontal">
377
+ <li class="list-group-item">Horizontal</li>
378
+ </ul>
379
+ <ul class="list-group list-group-numbered">
380
+ <li class="list-group-item">Numbered</li>
381
+ </ul>
382
+ <ul class="list-group list-group-flush">
383
+ <li class="list-group-item">Flush (edge-to-edge)</li>
384
+ </ul>
385
+
386
+ <!-- Checkboxes / radios in a list group (core pattern — form-check inside items) -->
387
+ <ul class="list-group">
388
+ <li class="list-group-item">
389
+ <input class="form-check-input me-1" type="checkbox" id="lgCheck1" value="" />
390
+ <label class="form-check-label" for="lgCheck1">First checkbox</label>
391
+ </li>
392
+ <li class="list-group-item">
393
+ <input class="form-check-input me-1" type="radio" name="lgRadio" id="lgRadio1" value="" />
394
+ <label class="form-check-label" for="lgRadio1">First radio</label>
395
+ </li>
396
+ </ul>
397
+ ```
398
+
399
+ (The `list-group-checkable` / `list-group-item-check` classes seen in Bootstrap's _examples gallery_ are custom CSS, not core — don't ship them without their styles.)
400
+
401
+ ### Modal
402
+
403
+ ```html
404
+ <div
405
+ class="modal fade"
406
+ id="exampleModal"
407
+ tabindex="-1"
408
+ aria-labelledby="exampleModalLabel"
409
+ aria-hidden="true"
410
+ >
411
+ <div class="modal-dialog">
412
+ <div class="modal-content">
413
+ <div class="modal-header">
414
+ <h1 class="modal-title fs-5" id="exampleModalLabel">Title</h1>
415
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
416
+ </div>
417
+ <div class="modal-body">Body</div>
418
+ <div class="modal-footer">
419
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
420
+ <button type="button" class="btn btn-primary">Save changes</button>
421
+ </div>
422
+ </div>
423
+ </div>
424
+ </div>
425
+
426
+ <div class="modal-dialog modal-dialog-scrollable">Scrollable body</div>
427
+ <div class="modal-dialog modal-dialog-centered">Centered vertically</div>
428
+ <div class="modal-dialog modal-sm">Small</div>
429
+ <div class="modal-dialog modal-lg">Large</div>
430
+ <div class="modal-dialog modal-xl">Extra large</div>
431
+ <div class="modal-dialog modal-fullscreen">Fullscreen</div>
432
+ <div class="modal-dialog modal-fullscreen-sm-down">Fullscreen below sm</div>
433
+
434
+ <div class="modal" data-bs-backdrop="static" data-bs-keyboard="false">
435
+ Static backdrop (blocks click-outside / Esc dismiss)
436
+ </div>
437
+ ```
438
+
439
+ Bootstrap's modal enforces focus, adds `role="dialog"`/`aria-modal="true"`, closes on Esc, and returns focus to the trigger. One modal at a time — nesting is unsupported; if a flow needs a second layer, redesign it. In SPAs, `dispose()` the instance on unmount ([bootstrap-reference.md](bootstrap-reference.md) → JavaScript lifecycle).
440
+
441
+ ### Navbar
442
+
443
+ ```html
444
+ <nav class="navbar navbar-expand-lg bg-body-tertiary">
445
+ <div class="container-fluid">
446
+ <a class="navbar-brand" href="#">Brand</a>
447
+ <button
448
+ class="navbar-toggler"
449
+ type="button"
450
+ data-bs-toggle="collapse"
451
+ data-bs-target="#navbarNav"
452
+ aria-controls="navbarNav"
453
+ aria-expanded="false"
454
+ aria-label="Toggle navigation"
455
+ >
456
+ <span class="navbar-toggler-icon"></span>
457
+ </button>
458
+ <div class="collapse navbar-collapse" id="navbarNav">
459
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
460
+ <li class="nav-item">
461
+ <a class="nav-link active" aria-current="page" href="#">Home</a>
462
+ </li>
463
+ <li class="nav-item"><a class="nav-link" href="#">Features</a></li>
464
+ <li class="nav-item">
465
+ <a class="nav-link disabled" aria-disabled="true">Disabled</a>
466
+ </li>
467
+ </ul>
468
+ <form class="d-flex" role="search">
469
+ <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" />
470
+ <button class="btn btn-outline-success" type="submit">Search</button>
471
+ </form>
472
+ </div>
473
+ </div>
474
+ </nav>
475
+
476
+ <nav class="navbar bg-body-tertiary fixed-top">Fixed top</nav>
477
+ <nav class="navbar bg-body-tertiary sticky-top">Sticky top</nav>
478
+
479
+ <!-- Dark navbar: .navbar-dark is DEPRECATED — scope the theme instead -->
480
+ <nav class="navbar bg-primary" data-bs-theme="dark">Dark-themed navbar</nav>
481
+ ```
482
+
483
+ ### Navs & Tabs
484
+
485
+ ```html
486
+ <ul class="nav">
487
+ <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Active</a></li>
488
+ <li class="nav-item"><a class="nav-link" href="#">Link</a></li>
489
+ <li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Disabled</a></li>
490
+ </ul>
491
+
492
+ <ul class="nav nav-tabs">
493
+
494
+ </ul>
495
+ <ul class="nav nav-pills">
496
+
497
+ </ul>
498
+ <ul class="nav nav-underline">
499
+
500
+ </ul>
501
+ <!-- 5.3: understated bottom-border variant -->
502
+ <ul class="nav nav-pills nav-fill">
503
+
504
+ </ul>
505
+ <ul class="nav nav-pills nav-justified">
506
+
507
+ </ul>
508
+ <nav class="nav nav-tabs flex-column flex-sm-row">Responsive nav</nav>
509
+ ```
510
+
511
+ Real switchable tab panels (JS-driven — buttons, not scroll anchors):
512
+
513
+ ```html
514
+ <ul class="nav nav-tabs" id="myTab" role="tablist">
515
+ <li class="nav-item" role="presentation">
516
+ <button
517
+ class="nav-link active"
518
+ id="home-tab"
519
+ data-bs-toggle="tab"
520
+ data-bs-target="#home-pane"
521
+ type="button"
522
+ role="tab"
523
+ aria-controls="home-pane"
524
+ aria-selected="true"
525
+ >
526
+ Home
527
+ </button>
528
+ </li>
529
+ <li class="nav-item" role="presentation">
530
+ <button
531
+ class="nav-link"
532
+ id="profile-tab"
533
+ data-bs-toggle="tab"
534
+ data-bs-target="#profile-pane"
535
+ type="button"
536
+ role="tab"
537
+ aria-controls="profile-pane"
538
+ aria-selected="false"
539
+ >
540
+ Profile
541
+ </button>
542
+ </li>
543
+ </ul>
544
+ <div class="tab-content">
545
+ <div
546
+ class="tab-pane fade show active"
547
+ id="home-pane"
548
+ role="tabpanel"
549
+ aria-labelledby="home-tab"
550
+ tabindex="0"
551
+ >
552
+ Home content
553
+ </div>
554
+ <div
555
+ class="tab-pane fade"
556
+ id="profile-pane"
557
+ role="tabpanel"
558
+ aria-labelledby="profile-tab"
559
+ tabindex="0"
560
+ >
561
+ Profile content
562
+ </div>
563
+ </div>
564
+ ```
565
+
566
+ ### Offcanvas
567
+
568
+ ```html
569
+ <button
570
+ class="btn btn-primary"
571
+ type="button"
572
+ data-bs-toggle="offcanvas"
573
+ data-bs-target="#offcanvasExample"
574
+ >
575
+ Launch
576
+ </button>
577
+
578
+ <div
579
+ class="offcanvas offcanvas-start"
580
+ tabindex="-1"
581
+ id="offcanvasExample"
582
+ aria-labelledby="offcanvasExampleLabel"
583
+ >
584
+ <div class="offcanvas-header">
585
+ <h5 class="offcanvas-title" id="offcanvasExampleLabel">Offcanvas</h5>
586
+ <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
587
+ </div>
588
+ <div class="offcanvas-body">Body content</div>
589
+ </div>
590
+
591
+ <div class="offcanvas offcanvas-top">Top</div>
592
+ <div class="offcanvas offcanvas-bottom">Bottom</div>
593
+ <div class="offcanvas offcanvas-start">Left/start</div>
594
+ <div class="offcanvas offcanvas-end">Right/end</div>
595
+
596
+ <div class="offcanvas offcanvas-start" data-bs-scroll="true" data-bs-backdrop="false">
597
+ No backdrop, body still scrolls
598
+ </div>
599
+ ```
600
+
601
+ **Responsive offcanvas** — the canonical sidebar-that-becomes-a-drawer: replace `.offcanvas` with `.offcanvas-{sm|md|lg|xl|xxl}`. Content renders **inline above** that breakpoint and as an **offcanvas below** it. Close buttons inside a responsive offcanvas need an explicit `data-bs-target`. Always set `aria-labelledby` (it's conceptually a dialog; `role="dialog"` is added by JS). Width/height via `--bs-offcanvas-width` (400px) / `--bs-offcanvas-height` (30vh). Full app-shell pattern: [bootstrap-reference.md](bootstrap-reference.md) → App shell.
602
+
603
+ ### Pagination
604
+
605
+ ```html
606
+ <nav aria-label="Search results pages">
607
+ <ul class="pagination">
608
+ <li class="page-item"><a class="page-link" href="#">Previous</a></li>
609
+ <li class="page-item"><a class="page-link" href="#">1</a></li>
610
+ <li class="page-item active" aria-current="page"><a class="page-link" href="#">2</a></li>
611
+ <li class="page-item"><a class="page-link" href="#">3</a></li>
612
+ <li class="page-item"><a class="page-link" href="#">Next</a></li>
613
+ </ul>
614
+ </nav>
615
+
616
+ <ul class="pagination pagination-lg">
617
+ Large
618
+ </ul>
619
+ <ul class="pagination pagination-sm">
620
+ Small
621
+ </ul>
622
+ <ul class="pagination justify-content-center">
623
+ Centered
624
+ </ul>
625
+ ```
626
+
627
+ ### Placeholder (skeletons)
628
+
629
+ ```html
630
+ <p aria-hidden="true">
631
+ <span class="placeholder col-6"></span>
632
+ <span class="placeholder w-75"></span>
633
+ <span class="placeholder" style="width: 25%;"></span>
634
+ </p>
635
+
636
+ <span class="placeholder col-12 placeholder-lg">Large</span>
637
+ <span class="placeholder col-12 placeholder-sm">Small</span>
638
+ <span class="placeholder col-12 placeholder-xs">Extra small</span>
639
+
640
+ <p class="placeholder-glow"><span class="placeholder col-12"></span></p>
641
+ <p class="placeholder-wave"><span class="placeholder col-12"></span></p>
642
+
643
+ <button class="btn btn-primary disabled placeholder col-4" aria-hidden="true"></button>
644
+ ```
645
+
646
+ 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 five states.
647
+
648
+ ### Popover (Requires Popper.js)
649
+
650
+ ```html
651
+ <button
652
+ type="button"
653
+ class="btn btn-lg btn-danger"
654
+ data-bs-toggle="popover"
655
+ data-bs-title="Popover title"
656
+ data-bs-content="And here's some amazing content."
657
+ >
658
+ Click to toggle popover
659
+ </button>
660
+
661
+ <button
662
+ type="button"
663
+ class="btn btn-secondary"
664
+ data-bs-container="body"
665
+ data-bs-toggle="popover"
666
+ data-bs-placement="top"
667
+ data-bs-content="Top popover"
668
+ >
669
+ Popover on top
670
+ </button>
671
+ ```
672
+
673
+ Popovers are **opt-in**: they do nothing until initialized in JS (see [JavaScript initialization](#javascript-initialization)). Only attach them to focusable elements; wrap disabled elements in a `<span tabindex="0">`. `data-bs-html="true"` with untrusted content is an XSS vector.
674
+
675
+ ### Progress
676
+
677
+ 5.3 markup — `role="progressbar"` and the `aria-value*` attributes go on the **outer `.progress`**, not the inner bar:
678
+
679
+ ```html
680
+ <div
681
+ class="progress"
682
+ role="progressbar"
683
+ aria-label="Basic example"
684
+ aria-valuenow="25"
685
+ aria-valuemin="0"
686
+ aria-valuemax="100"
687
+ >
688
+ <div class="progress-bar" style="width: 25%">25%</div>
689
+ </div>
690
+
691
+ <div
692
+ class="progress"
693
+ role="progressbar"
694
+ aria-label="Success"
695
+ aria-valuenow="25"
696
+ aria-valuemin="0"
697
+ aria-valuemax="100"
698
+ >
699
+ <div class="progress-bar bg-success" style="width: 25%"></div>
700
+ </div>
701
+
702
+ <div
703
+ class="progress"
704
+ role="progressbar"
705
+ aria-label="Striped"
706
+ aria-valuenow="10"
707
+ aria-valuemin="0"
708
+ aria-valuemax="100"
709
+ >
710
+ <div class="progress-bar progress-bar-striped" style="width: 10%"></div>
711
+ </div>
712
+
713
+ <div
714
+ class="progress"
715
+ role="progressbar"
716
+ aria-label="Animated"
717
+ aria-valuenow="75"
718
+ aria-valuemin="0"
719
+ aria-valuemax="100"
720
+ >
721
+ <div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 75%"></div>
722
+ </div>
723
+
724
+ <div class="progress-stacked">
725
+ <div
726
+ class="progress"
727
+ role="progressbar"
728
+ aria-label="Segment one"
729
+ aria-valuenow="30"
730
+ aria-valuemin="0"
731
+ aria-valuemax="100"
732
+ style="width: 30%"
733
+ >
734
+ <div class="progress-bar"></div>
735
+ </div>
736
+ <div
737
+ class="progress"
738
+ role="progressbar"
739
+ aria-label="Segment two"
740
+ aria-valuenow="20"
741
+ aria-valuemin="0"
742
+ aria-valuemax="100"
743
+ style="width: 20%"
744
+ >
745
+ <div class="progress-bar bg-success"></div>
746
+ </div>
747
+ </div>
748
+ ```
749
+
750
+ ### Scrollspy
751
+
752
+ ```html
753
+ <nav id="navbar-example2" class="navbar bg-body-tertiary px-3 mb-3">
754
+ <a class="navbar-brand" href="#">Navbar</a>
755
+ <ul class="nav nav-pills">
756
+ <li class="nav-item">
757
+ <a class="nav-link" href="#scrollspyHeading1">First</a>
758
+ </li>
759
+ <li class="nav-item">
760
+ <a class="nav-link" href="#scrollspyHeading2">Second</a>
761
+ </li>
762
+ </ul>
763
+ </nav>
764
+ <div
765
+ data-bs-spy="scroll"
766
+ data-bs-target="#navbar-example2"
767
+ data-bs-root-margin="0px 0px -40%"
768
+ data-bs-threshold="0.1"
769
+ tabindex="0"
770
+ >
771
+ <h4 id="scrollspyHeading1">First heading</h4>
772
+ <p>Content...</p>
773
+ <h4 id="scrollspyHeading2">Second heading</h4>
774
+ <p>Content...</p>
775
+ </div>
776
+ ```
777
+
778
+ Gotcha: the spied element must be a scroll container (height/overflow, or focusable via `tabindex="0"`), and heading IDs must match the nav `href`s exactly. Scrollspy highlights position in one long page — it is not a substitute for real tabs.
779
+
780
+ ### Spinners
781
+
782
+ ```html
783
+ <div class="spinner-border text-primary" role="status">
784
+ <span class="visually-hidden">Loading...</span>
785
+ </div>
786
+
787
+ <div class="spinner-grow text-primary" role="status">
788
+ <span class="visually-hidden">Loading...</span>
789
+ </div>
790
+
791
+ <div class="spinner-border spinner-border-sm" role="status"></div>
792
+ <div class="spinner-grow spinner-grow-sm" role="status"></div>
793
+
794
+ <button class="btn btn-primary" type="button" disabled>
795
+ <span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
796
+ <span role="status">Loading...</span>
797
+ </button>
798
+ ```
799
+
800
+ ### Tables
801
+
802
+ ```html
803
+ <table class="table">
804
+ <caption class="visually-hidden">
805
+ Monthly invoices with status and totals
806
+ </caption>
807
+ <thead>
808
+ <tr>
809
+ <th scope="col">Invoice</th>
810
+ <th scope="col">Status</th>
811
+ <th scope="col" class="text-end">Amount</th>
812
+ </tr>
813
+ </thead>
814
+ <tbody>
815
+ <tr>
816
+ <th scope="row">INV-1042</th>
817
+ <td><span class="badge text-bg-success">Paid</span></td>
818
+ <td class="text-end">$1,280.00</td>
819
+ </tr>
820
+ </tbody>
821
+ </table>
822
+ ```
823
+
824
+ Modifiers (combine freely):
825
+
826
+ ```css
827
+ .table-sm /* half padding — dense screens */
828
+ .table-striped, .table-striped-columns
829
+ .table-hover /* row highlight — scanning aid */
830
+ .table-bordered, .table-borderless
831
+ .table-active /* highlight a row/cell */
832
+ .table-group-divider /* thicker rule between <tbody> groups */
833
+ .caption-top /* caption above the table */
834
+ .table-primary … .table-dark /* variants, on table/tr/td */
835
+ .align-middle /* vertical alignment, on table/tr/td */
836
+ ```
837
+
838
+ - **Responsive:** wrap in `.table-responsive{-sm|-md|-lg|-xl|-xxl}` for horizontal scroll. Caveat: the wrapper clips overflowing content — dropdown menus inside a responsive table get cut off.
839
+ - **Dark tables:** `data-bs-theme="dark"` on the `<table>` (the `.table-dark` variant approach is superseded).
840
+ - **Theming:** variants set CSS variables, not fixed colors — `--bs-table-bg`, `--bs-table-color`, `--bs-table-striped-bg`, `--bs-table-hover-bg`, `--bs-table-active-bg`, `--bs-table-border-color`. `--bs-table-bg` is transparent by default so striping/hover layer through.
841
+ - **Sticky headers are NOT built in.** Bootstrap ships no sticky-header feature; the pattern needs a few lines of custom CSS. That, plus selection columns, `aria-sort` sorting, bulk-action bars, and responsive strategies: [bootstrap-reference.md](bootstrap-reference.md) → Dense data tables.
842
+
843
+ ### Toasts
844
+
845
+ ```html
846
+ <div class="toast" role="status" aria-live="polite" aria-atomic="true">
847
+ <div class="toast-header">
848
+ <strong class="me-auto">Deployment</strong>
849
+ <small>11 mins ago</small>
850
+ <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
851
+ </div>
852
+ <div class="toast-body">Changes published.</div>
853
+ </div>
854
+
855
+ <div class="toast align-items-center text-bg-primary border-0" role="status" aria-live="polite">
856
+ <div class="d-flex">
857
+ <div class="toast-body">Color variant</div>
858
+ <button
859
+ type="button"
860
+ class="btn-close me-2 m-auto"
861
+ data-bs-theme="dark"
862
+ data-bs-dismiss="toast"
863
+ aria-label="Close"
864
+ ></button>
865
+ </div>
866
+ </div>
867
+
868
+ <!-- One fixed container stacks all toasts (flex column) -->
869
+ <div class="toast-container position-fixed top-0 end-0 p-3">
870
+ <div class="toast" role="status" aria-live="polite" aria-atomic="true">…</div>
871
+ <div class="toast" role="status" aria-live="polite" aria-atomic="true">…</div>
872
+ </div>
873
+ ```
874
+
875
+ Toasts are **opt-in** — hidden until `.show()` is called (or shown via a trigger). Keep the container in the DOM before showing so the live region announces. Use `role="status"`/`aria-live="polite"` for confirmations; reserve `role="alert"`/`assertive` for urgent messages. Errors requiring action are never toasts — see [bootstrap-reference.md](bootstrap-reference.md) → Feedback discipline.
876
+
877
+ ### Tooltip (Requires Popper.js)
878
+
879
+ ```html
880
+ <button
881
+ type="button"
882
+ class="btn btn-secondary"
883
+ data-bs-toggle="tooltip"
884
+ data-bs-placement="top"
885
+ data-bs-title="Tooltip on top"
886
+ >
887
+ Tooltip on top
888
+ </button>
889
+ <!-- data-bs-placement: top | right | bottom | left (auto-flipped in RTL) -->
890
+
891
+ <button
892
+ type="button"
893
+ class="btn btn-secondary"
894
+ data-bs-toggle="tooltip"
895
+ data-bs-html="true"
896
+ data-bs-title="<em>Tooltip</em> <u>with</u> <b>HTML</b>"
897
+ >
898
+ Tooltip with HTML
899
+ </button>
900
+ ```
901
+
902
+ 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 via tooltip. `data-bs-html` with untrusted content is an XSS vector.
903
+
904
+ ## JavaScript Initialization
905
+
906
+ Data-attribute components (modal, collapse, dropdown, offcanvas, tab, alert dismiss) work from markup alone. **Tooltips and popovers do not** — they must be constructed; **toasts** stay hidden until shown:
907
+
908
+ ```js
909
+ // Required for every tooltip/popover on the page
910
+ const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
911
+ const tooltipList = [...tooltipTriggerList].map((el) => new bootstrap.Tooltip(el))
912
+
913
+ const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
914
+ const popoverList = [...popoverTriggerList].map((el) => new bootstrap.Popover(el))
915
+
916
+ // Programmatic control — prefer getOrCreateInstance over `new` when the
917
+ // element may already be initialized (e.g. by a data attribute)
918
+ const myModal = bootstrap.Modal.getOrCreateInstance('#myModal')
919
+ myModal.show()
920
+
921
+ const myToast = bootstrap.Toast.getOrCreateInstance('#myToast')
922
+ myToast.show()
923
+ ```
924
+
925
+ Constructors accept elements or CSS selector strings. Full lifecycle — `getInstance`, `dispose()` on unmount, event pairs (`show.bs.*` / `shown.bs.*`), async behavior, and why SPAs should prefer framework wrappers: [bootstrap-reference.md](bootstrap-reference.md) → JavaScript lifecycle.
926
+
927
+ ## Icons
928
+
929
+ Bootstrap's core CSS ships **no icons**. The `.bi` SVGs in examples come from the companion [Bootstrap Icons](https://icons.getbootstrap.com/) library (`bootstrap-icons` package) — a separate install, used as inline SVG / SVG sprite (preferred) or icon font (`<i class="bi bi-check"></i>`). Decorative icons get `aria-hidden="true"`; meaningful icons get `role="img"` + `aria-label`. Pairs with the `.icon-link` helper ([utilities.md](utilities.md) → Helpers).
930
+
931
+ ### Status glyph marks
932
+
933
+ 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:
934
+
935
+ ```html
936
+ <span
937
+ class="bi bi-circle-fill fs-6 lh-1 text-success-emphasis"
938
+ role="img"
939
+ aria-label="Healthy"
940
+ ></span>
941
+ <span class="bi bi-circle fs-6 lh-1 text-body-secondary" role="img" aria-label="Not started"></span>
942
+ ```
943
+
944
+ - **Color from the emphasis tokens.** `text-*-emphasis` is the mode-adaptive tier built for marks on subtle surfaces; the plain `text-*` colors are tuned for light and thin out in dark. Measure every mark at **≥ 3:1** against the surface it sits on, **in both themes**, against the compiled cascade — a skin's token values are its own.
945
+ - **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.
946
+ - **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.
947
+ - 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.
948
+
949
+ ## Enterprise notes (components)
950
+
951
+ ### Choosing components
952
+
953
+ | Need | Prefer | Avoid |
954
+ | ---------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------- |
955
+ | Primary page actions | `btn` / `btn-group` / `btn-toolbar` | Random links styled as buttons inconsistently |
956
+ | Full-width stacks (sidebars, empty-state CTAs) | `d-grid gap-2` + `btn` | Absolute positioning |
957
+ | Switching in-page views | `nav-tabs` / `nav-pills` / `nav-underline` + tab panes or equivalent state | Anchor links that only scroll a long page |
958
+ | App sidebar navigation | Responsive `offcanvas-{bp}` + `nav` | Hand-rolled drawer JS |
959
+ | Structured groups | `card` with `card-header` / `card-body` / `card-footer` | Ad-hoc bordered `div`s with mismatched padding |
960
+ | Ordered instructions / schemas | `list-group` / `list-group-numbered` | Unstyled paragraphs pretending to be steps |
961
+ | Long documentation in-product | `accordion` or scrollable `modal` | One infinite tinted card stack |
962
+ | Dense data | `table` + `table-responsive` (+ `table-sm` when appropriate) | Non-semantic grids of text |
963
+ | Choosing a form value | `form-select` (or native input) | A `dropdown` menu posing as an input |
964
+ | Confirmations / focused tasks | `modal` with header, body, footer actions | Nested modals |
965
+ | Secondary filters on small screens | `offcanvas` | Permanent wide sidebars that crush content |
966
+ | Transient success feedback | `toast` | `alert()`; toasts for errors |
967
+ | Loading a known layout | `placeholder` skeleton | Layout-collapsing centered spinner |
968
+
969
+ ### Forms
970
+
971
+ - Prefer visible labels or `.form-floating` — placeholder-only labels fail accessibility and disappear on input.
972
+ - Pair help and errors with `aria-describedby`; use `.invalid-feedback` with `.is-invalid` and mark the field `aria-invalid="true"`.
973
+ - Money/units: `.input-group` + `.input-group-text`; add `.has-validation` on groups with validation feedback.
974
+ - 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).
975
+
976
+ ### Selection fills
977
+
978
+ A selected row, pill, or filter chip repaints everything inside it — marks included. Two traps, both invisible until the selected state is captured in both themes:
979
+
980
+ - **A mark on an active fill of the same family disappears.** `.active` on a `list-group-item`, `nav-pill`, or `page-item` sets the item's own color, and a `text-bg-primary`-family mark inside it inherits or loses to that fill — the mark is there in the markup and gone on screen. `text-body-emphasis` (or an outline glyph that keeps its own token) survives the fill; verify by capturing the selected row, not by reading the class list.
981
+ - **`btn-check` filter labels invert in dark.** A `btn-outline-secondary` label reads as "chosen" in light and as "muted" in dark, because the checked fill and the surface swap relative weight. Give chosen filters an accent variant (a real theme color) rather than the neutral outline, so "chosen" reads the same way in both modes.
982
+
983
+ Exactly one item in a selection carries `aria-current` — the visual fill and the announced state must be the same item.
984
+
985
+ ### Navigation & overlays
986
+
987
+ - Active nav items need `aria-current="page"` (or `aria-selected="true"` for tabs).
988
+ - Modals and offcanvas: set `aria-labelledby`; Bootstrap traps focus and restores it on close — don't fight it; `dispose()` instances when the host unmounts in SPAs.
989
+ - Icon-only controls always need an accessible name (`aria-label` or visually-hidden text) and a ≥24px target.
990
+
991
+ ### Theming
992
+
993
+ - Components consume CSS variables — favor `text-bg-*`, `*-subtle`, and `data-bs-theme` over one-off colors; the deprecated `*-dark` component variants (`navbar-dark`, `dropdown-menu-dark`, `btn-close-white`, `carousel-dark`) all map to `data-bs-theme="dark"`.
994
+ - To restyle a component, override its `--bs-{component}-*` variables in your own scope instead of writing high-specificity rules — see [bootstrap-reference.md](bootstrap-reference.md) → Theming & design tokens.