@orkestrel/scaffold 0.0.18 → 0.0.19
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/dist/host/AGENTS.md +4 -2
- package/dist/host/CLAUDE.md +22 -10
- package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +267 -0
- package/dist/host/agents/skills/enterprise-bootstrap/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +609 -0
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +994 -0
- package/dist/host/agents/skills/enterprise-bootstrap/references/frontend-design.md +59 -0
- package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +312 -0
- package/dist/host/agents/skills/orkestrel-align-packages/references/integration.md +4 -13
- package/dist/host/agents/skills/orkestrel-build-application/SKILL.md +78 -50
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +81 -0
- package/dist/host/agents/skills/orkestrel-debrief/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +75 -0
- package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +11 -9
- package/dist/host/agents/skills/orkestrel-harden-package/references/centralization.md +44 -59
- package/dist/host/agents/skills/orkestrel-harden-package/references/hardening.md +14 -6
- package/dist/host/agents/skills/orkestrel-polish-surface/SKILL.md +113 -0
- package/dist/host/agents/skills/orkestrel-polish-surface/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/orkestrel-polish-surface/references/capture-harness.md +82 -0
- package/dist/host/claude/agents/builder.md +2 -0
- package/dist/host/claude/agents/codex.md +33 -26
- package/dist/host/claude/agents/grok.md +7 -0
- package/dist/host/claude/agents/implementer.md +2 -1
- package/dist/host/claude/agents/orkestrel.md +20 -15
- package/dist/host/claude/agents/planner.md +2 -1
- package/dist/host/claude/agents/reviewer.md +6 -0
- package/dist/host/claude/rules/documentation.md +1 -0
- package/dist/host/claude/rules/names.md +5 -7
- package/dist/host/claude/rules/quality.md +7 -5
- package/dist/host/claude/rules/styles.md +1 -0
- package/dist/host/claude/rules/tests.md +1 -0
- package/dist/host/claude/rules/typescript.md +3 -10
- package/dist/host/claude/rules/workspace.md +2 -5
- package/dist/host/claude/skills/enterprise-bootstrap/SKILL.md +12 -0
- package/dist/host/claude/skills/orkestrel-debrief/SKILL.md +12 -0
- package/dist/host/claude/skills/orkestrel-polish-surface/SKILL.md +12 -0
- package/dist/host/codex/agents/analyst.toml +6 -3
- package/dist/host/codex/agents/builder.toml +3 -2
- package/dist/host/codex/agents/checker.toml +4 -2
- package/dist/host/codex/agents/grok.toml +3 -1
- package/dist/host/codex/agents/implementer.toml +4 -2
- package/dist/host/codex/agents/opus.toml +5 -3
- package/dist/host/codex/agents/orkestrel.toml +6 -5
- package/dist/host/codex/agents/planner.toml +6 -2
- package/dist/host/codex/agents/reviewer.toml +7 -2
- package/dist/host/codex/config.toml +11 -2
- package/dist/host/dotfiles/prettierignore +3 -0
- package/dist/host/guides/src/scaffold.md +42 -12
- package/dist/host/manifest.json +80 -9
- package/dist/src/core/index.cjs +162 -14
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +17 -6
- package/dist/src/core/index.d.ts +17 -6
- package/dist/src/core/index.js +162 -15
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +9 -3
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +2 -1
- package/dist/src/server/index.d.ts +2 -1
- package/dist/src/server/index.js +10 -4
- package/dist/src/server/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/host/agents/skills/orkestrel-build-application/references/application.md +0 -129
- package/dist/host/claude/agents/application.md +0 -30
- package/dist/host/codex/agents/application.toml +0 -25
|
@@ -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.
|