@orkestrel/scaffold 0.0.59 → 0.0.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -10
- package/dist/bin/main.js +632 -320
- package/dist/bin/main.js.map +1 -1
- package/dist/host/CLAUDE.md +5 -1
- package/dist/host/agents/orchestration.md +44 -19
- package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +91 -82
- package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +5 -5
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +15 -15
- package/dist/host/agents/skills/enterprise-bootstrap/references/inputs.md +501 -0
- package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +167 -0
- package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +10 -9
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +21 -11
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +20 -2
- package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +1 -1
- package/dist/host/agents/skills/orkestrel-harden-package/references/hardening.md +1 -1
- package/dist/host/agents/skills/orkestrel-harden-package/references/research.md +1 -1
- package/dist/host/agents/skills/orkestrel-polish-surface/SKILL.md +4 -1
- package/dist/host/agents/skills/orkestrel-polish-surface/references/capture-harness.md +71 -50
- package/dist/host/agents/skills/orkestrel-prove-journey/SKILL.md +93 -29
- package/dist/host/agents/skills/orkestrel-prove-journey/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-prove-journey/references/captures.md +62 -38
- package/dist/host/agents/skills/orkestrel-prove-journey/references/decide.md +68 -0
- package/dist/host/agents/skills/orkestrel-prove-journey/references/layer.md +107 -79
- package/dist/host/agents/skills/orkestrel-prove-journey/references/statechart.md +84 -0
- package/dist/host/agents/skills/orkestrel-prove-journey/references/styles.md +87 -0
- package/dist/host/agents/templates/brief.md +16 -7
- package/dist/host/agents/transports/claude.md +4 -2
- package/dist/host/agents/transports/codex.md +4 -1
- package/dist/host/claude/agents/analyst.md +3 -1
- package/dist/host/claude/agents/application.md +1 -1
- package/dist/host/claude/agents/builder.md +3 -3
- package/dist/host/claude/agents/checker.md +5 -0
- package/dist/host/claude/agents/grok.md +15 -5
- package/dist/host/claude/agents/implementer.md +1 -1
- package/dist/host/claude/agents/orkestrel.md +12 -11
- package/dist/host/claude/agents/planner.md +10 -0
- package/dist/host/claude/agents/reviewer.md +14 -8
- package/dist/host/claude/agents/sol.md +3 -1
- package/dist/host/claude/agents/verifier.md +2 -4
- package/dist/host/claude/rules/architecture.md +7 -5
- package/dist/host/claude/rules/documentation.md +1 -0
- package/dist/host/claude/rules/names.md +23 -5
- package/dist/host/claude/rules/patterns.md +1 -0
- package/dist/host/claude/rules/quality.md +1 -1
- package/dist/host/claude/rules/tests.md +3 -3
- package/dist/host/claude/rules/typescript.md +4 -1
- package/dist/host/claude/rules/writing.md +2 -2
- package/dist/host/claude/skills/orkestrel-prove-journey/SKILL.md +1 -1
- package/dist/host/codex/agents/builder.toml +6 -6
- package/dist/host/codex/agents/checker.toml +2 -1
- package/dist/host/codex/agents/grok.toml +12 -5
- package/dist/host/codex/agents/implementer.toml +2 -2
- package/dist/host/codex/agents/opus.toml +6 -1
- package/dist/host/codex/agents/planner.toml +11 -6
- package/dist/host/codex/agents/reviewer.toml +8 -6
- package/dist/host/guides/scaffold.md +39 -14
- package/dist/host/manifest.json +81 -51
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/src/core/index.cjs +424 -282
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +361 -220
- package/dist/src/core/index.d.ts +361 -220
- package/dist/src/core/index.js +421 -283
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +208 -170
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +276 -152
- package/dist/src/server/index.d.ts +276 -152
- package/dist/src/server/index.js +200 -172
- package/dist/src/server/index.js.map +1 -1
- package/package.json +8 -7
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
- Typography: `.h1`–`.h6`, `.display-1`–`.display-6`, `.lead`, `.small`
|
|
26
26
|
- Images: `.img-fluid`, `.img-thumbnail`, `.figure`
|
|
27
|
-
- Tables: `.table`
|
|
27
|
+
- Tables: `.table` plus its `.table-*` tone classes — see [Tables](#tables)
|
|
28
28
|
- Figures: `.figure`, `.figure-img`, `.figure-caption`
|
|
29
29
|
|
|
30
30
|
### Form Components
|
|
@@ -86,7 +86,7 @@ Full form patterns and validation JS: [bootstrap-reference.md](bootstrap-referen
|
|
|
86
86
|
</div>
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
Modifier classes: `.accordion-flush` (edge-to-edge, no outer borders); omit `data-bs-parent` to allow multiple items open.
|
|
90
90
|
|
|
91
91
|
### Alerts
|
|
92
92
|
|
|
@@ -188,7 +188,7 @@ The current page is `aria-current="page"` and not a link. Use breadcrumbs only f
|
|
|
188
188
|
|
|
189
189
|
Icon-only buttons need `aria-label` and a ≥24×24 px target (WCAG 2.2) — `btn-sm` icon clusters in toolbars are the common violation; pad rather than shrink.
|
|
190
190
|
|
|
191
|
-
Choose the
|
|
191
|
+
Choose the `btn-*` class by contrast rather than by taste ([SKILL.md](../SKILL.md) → Hierarchy & actions).
|
|
192
192
|
|
|
193
193
|
### Button Group
|
|
194
194
|
|
|
@@ -525,7 +525,7 @@ Bootstrap's modal enforces focus, adds `role="dialog"`/`aria-modal="true"`, clos
|
|
|
525
525
|
<ul class="nav nav-underline">
|
|
526
526
|
…
|
|
527
527
|
</ul>
|
|
528
|
-
<!-- 5.3: understated bottom-border
|
|
528
|
+
<!-- 5.3: understated bottom-border style -->
|
|
529
529
|
<ul class="nav nav-pills nav-fill">
|
|
530
530
|
…
|
|
531
531
|
</ul>
|
|
@@ -625,7 +625,7 @@ Real switchable tab panels (JS-driven — buttons, not scroll anchors):
|
|
|
625
625
|
</div>
|
|
626
626
|
```
|
|
627
627
|
|
|
628
|
-
**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 is conceptually a dialog; `role="dialog"` is added by JS). Width/height
|
|
628
|
+
**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 is conceptually a dialog; `role="dialog"` is added by JS). Width/height through `--bs-offcanvas-width` (400px) / `--bs-offcanvas-height` (30vh). Full app-shell pattern: [bootstrap-reference.md](bootstrap-reference.md) → App shell.
|
|
629
629
|
|
|
630
630
|
### Pagination
|
|
631
631
|
|
|
@@ -802,7 +802,7 @@ Popovers are **opt-in**: they do nothing until initialized in JS (see [JavaScrip
|
|
|
802
802
|
</div>
|
|
803
803
|
```
|
|
804
804
|
|
|
805
|
-
Gotcha: the spied element must be a scroll container (height/overflow, or focusable
|
|
805
|
+
Gotcha: the spied element must be a scroll container (height/overflow, or focusable through `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.
|
|
806
806
|
|
|
807
807
|
### Spinners
|
|
808
808
|
|
|
@@ -858,13 +858,13 @@ Modifiers (combine freely):
|
|
|
858
858
|
.table-active /* highlight a row/cell */
|
|
859
859
|
.table-group-divider /* thicker rule between <tbody> groups */
|
|
860
860
|
.caption-top /* caption above the table */
|
|
861
|
-
.table-primary … .table-dark /*
|
|
861
|
+
.table-primary … .table-dark /* tone classes, on table/tr/td */
|
|
862
862
|
.align-middle /* vertical alignment, on table/tr/td */
|
|
863
863
|
```
|
|
864
864
|
|
|
865
865
|
- **Responsive:** wrap in `.table-responsive{-sm|-md|-lg|-xl|-xxl}` for horizontal scroll. Caveat: the wrapper clips overflowing content — dropdown menus inside a responsive table get cut off.
|
|
866
|
-
- **Dark tables:** `data-bs-theme="dark"` on the `<table>` (the `.table-dark`
|
|
867
|
-
- **Theming:**
|
|
866
|
+
- **Dark tables:** `data-bs-theme="dark"` on the `<table>` (the `.table-dark` class approach is superseded).
|
|
867
|
+
- **Theming:** the `.table-*` tone classes set CSS variables, not fixed colors — `--bs-table-bg`, `--bs-table-color`, `--bs-table-striped-bg`, `--bs-table-hover-bg`, `--bs-table-active-bg`, `--bs-table-border-color`. `--bs-table-bg` is transparent by default so striping/hover layer through.
|
|
868
868
|
- **Sticky headers are NOT built in.** Bootstrap ships no sticky-header feature; the pattern needs a few lines of custom CSS. That, plus selection columns, `aria-sort` sorting, bulk-action bars, and responsive strategies: [bootstrap-reference.md](bootstrap-reference.md) → Dense data tables.
|
|
869
869
|
|
|
870
870
|
### Toasts
|
|
@@ -881,7 +881,7 @@ Modifiers (combine freely):
|
|
|
881
881
|
|
|
882
882
|
<div class="toast align-items-center text-bg-primary border-0" role="status" aria-live="polite">
|
|
883
883
|
<div class="d-flex">
|
|
884
|
-
<div class="toast-body">Color
|
|
884
|
+
<div class="toast-body">Color tone</div>
|
|
885
885
|
<button
|
|
886
886
|
type="button"
|
|
887
887
|
class="btn-close me-2 m-auto"
|
|
@@ -899,7 +899,7 @@ Modifiers (combine freely):
|
|
|
899
899
|
</div>
|
|
900
900
|
```
|
|
901
901
|
|
|
902
|
-
Toasts are **opt-in** — hidden until `.show()` is called (or shown
|
|
902
|
+
Toasts are **opt-in** — hidden until `.show()` is called (or shown through 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.
|
|
903
903
|
|
|
904
904
|
### Tooltip (Requires Popper.js)
|
|
905
905
|
|
|
@@ -926,7 +926,7 @@ Toasts are **opt-in** — hidden until `.show()` is called (or shown via a trigg
|
|
|
926
926
|
</button>
|
|
927
927
|
```
|
|
928
928
|
|
|
929
|
-
Tooltips are **opt-in** (JS init required, below). Only attach to focusable elements so keyboard users can trigger them; never put essential information _only_ in a tooltip, and never report form errors
|
|
929
|
+
Tooltips are **opt-in** (JS init required, below). Only attach to focusable elements so keyboard users can trigger them; never put essential information _only_ in a tooltip, and never report form errors through a tooltip. `data-bs-html` with untrusted content is an XSS vector.
|
|
930
930
|
|
|
931
931
|
## JavaScript Initialization
|
|
932
932
|
|
|
@@ -949,7 +949,7 @@ const myToast = bootstrap.Toast.getOrCreateInstance('#myToast')
|
|
|
949
949
|
myToast.show()
|
|
950
950
|
```
|
|
951
951
|
|
|
952
|
-
Constructors accept elements or CSS selector strings. Full lifecycle — `getInstance`, `dispose()` on unmount, event pairs (`show.bs.*` / `shown.bs.*`), async behavior, and why
|
|
952
|
+
Constructors accept elements or CSS selector strings. Full lifecycle — `getInstance`, `dispose()` on unmount, event pairs (`show.bs.*` / `shown.bs.*`), async behavior, and why an SPA prefers a framework wrapper: [bootstrap-reference.md](bootstrap-reference.md) → JavaScript lifecycle.
|
|
953
953
|
|
|
954
954
|
## Icons
|
|
955
955
|
|
|
@@ -1005,7 +1005,7 @@ The textless mark that survives both themes — dots, ticks, rings, pulses — i
|
|
|
1005
1005
|
A selected row, pill, or filter chip repaints everything inside it — marks included. These traps stay invisible until the selected state is captured in both themes:
|
|
1006
1006
|
|
|
1007
1007
|
- **A mark on an active fill of the same family disappears.** `.active` on a `list-group-item`, `nav-pill`, or `page-item` sets the item's own color, and a `text-bg-primary`-family mark inside it inherits or loses to that fill — present in the markup, gone on screen. Carry no tone class inside the fill ([SKILL.md](../SKILL.md) → Surfaces, color, contrast). Verify by capturing the selected row, not by reading the class list.
|
|
1008
|
-
- **`btn-check` filter labels invert in dark.** A `btn-outline-secondary` label reads as "chosen" in light and as "muted" in dark, because the checked fill and the surface swap relative weight. Give chosen filters an accent
|
|
1008
|
+
- **`btn-check` filter labels invert in dark.** A `btn-outline-secondary` label reads as "chosen" in light and as "muted" in dark, because the checked fill and the surface swap relative weight. Give chosen filters an accent tone class (a real theme color) rather than the neutral outline, so "chosen" reads the same way in both modes.
|
|
1009
1009
|
|
|
1010
1010
|
Exactly one item in a selection carries `aria-current` — the visual fill and the announced state must be the same item.
|
|
1011
1011
|
|
|
@@ -1017,5 +1017,5 @@ Exactly one item in a selection carries `aria-current` — the visual fill and t
|
|
|
1017
1017
|
|
|
1018
1018
|
### Theming
|
|
1019
1019
|
|
|
1020
|
-
- Components consume CSS variables — favor `text-bg-*`, `*-subtle`, and `data-bs-theme` over one-off colors; the deprecated `*-dark` component
|
|
1020
|
+
- Components consume CSS variables — favor `text-bg-*`, `*-subtle`, and `data-bs-theme` over one-off colors; the deprecated `*-dark` component classes (`navbar-dark`, `dropdown-menu-dark`, `btn-close-white`, `carousel-dark`) all map to `data-bs-theme="dark"`.
|
|
1021
1021
|
- 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.
|
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
# Input affordances
|
|
2
|
+
|
|
3
|
+
Pick an affordance from what the person is asked for, not from the name a schema gives the field.
|
|
4
|
+
Where one category draws several ways, let the density and the list size decide.
|
|
5
|
+
|
|
6
|
+
Read [The fixed state set](#the-fixed-state-set) before the catalog: every affordance handles that
|
|
7
|
+
same set, and each category names only what it adds or changes. Take the data states a whole surface
|
|
8
|
+
ships — ideal, empty, loading, partial, error — from
|
|
9
|
+
[bootstrap-reference.md](bootstrap-reference.md) → The data states instead.
|
|
10
|
+
|
|
11
|
+
## Contents
|
|
12
|
+
|
|
13
|
+
- [The fixed state set](#the-fixed-state-set)
|
|
14
|
+
- [Rules that cross every category](#rules-that-cross-every-category)
|
|
15
|
+
- [The catalog](#the-catalog)
|
|
16
|
+
- [Where Bootstrap ships no component](#where-bootstrap-ships-no-component)
|
|
17
|
+
|
|
18
|
+
## The fixed state set
|
|
19
|
+
|
|
20
|
+
Draw every one of these for every affordance you place. A category adds `empty` and `full` when its
|
|
21
|
+
value is a set.
|
|
22
|
+
|
|
23
|
+
- **rest** — no pointer, no keyboard focus, the value the field holds.
|
|
24
|
+
- **hover** — pointer over the control. The chrome moves, the value does not.
|
|
25
|
+
- **focus-visible** — keyboard focus carrying the ring the theme ships. Never write `outline: none`.
|
|
26
|
+
- **disabled** — not editable and not submitted. Use the `disabled` attribute; the contrast bars
|
|
27
|
+
exempt it.
|
|
28
|
+
- **locked** — not editable and still submitted. Use `readonly` on a control that honors it, and
|
|
29
|
+
`disabled` plus a carrier that submits the value on one that does not.
|
|
30
|
+
- **invalid** — `is-invalid` on the control, a sibling `.invalid-feedback` message,
|
|
31
|
+
`aria-invalid="true"`, and the message wired to the control with `aria-describedby`.
|
|
32
|
+
- **busy** — waiting on work the person cannot see: a select whose options are still loading, a
|
|
33
|
+
field checking a value against a server. Mark the region `aria-busy="true"` and show a
|
|
34
|
+
`spinner-border spinner-border-sm` in the control's own chrome. Leave the control operable unless
|
|
35
|
+
its value depends on the work.
|
|
36
|
+
- **required** — state the requirement in the visible label and set the `required` attribute on the
|
|
37
|
+
control. A `text-danger` asterisk is decoration and carries `aria-hidden="true"`; the word in the
|
|
38
|
+
label is what a screen reader user gets.
|
|
39
|
+
- **with help** — a `.form-text` under the control, wired with `aria-describedby` beside the error
|
|
40
|
+
message rather than in place of it.
|
|
41
|
+
- **empty** — the set holds nothing. Say what an entry would be, not "nothing here".
|
|
42
|
+
- **full** — the set is at its cap. State the cap and stop accepting, rather than dropping an entry
|
|
43
|
+
silently.
|
|
44
|
+
|
|
45
|
+
## Rules that cross every category
|
|
46
|
+
|
|
47
|
+
- **Keep a read-only field on the same affordance the edit state uses.** Take `readonly`, or
|
|
48
|
+
`disabled` plus a carrier, and neutralize the chrome with one transparent combination declared
|
|
49
|
+
once by name — the combination is a class contract, so declare it and reuse it rather than
|
|
50
|
+
retyping the utilities. Never swap to `form-control-plaintext`: it drops the horizontal padding,
|
|
51
|
+
so the read view and the edit view reflow against each other.
|
|
52
|
+
- **Give a locked select `disabled` and a hidden input beside it.** A native select cannot be
|
|
53
|
+
read-only, so `disabled` stops its value submitting and the hidden input carries that value.
|
|
54
|
+
- **Give a chosen filter an accent tone class, not the neutral outline.** A `btn-outline-secondary`
|
|
55
|
+
label reads as chosen in light and as muted in dark, so one markup says opposite things.
|
|
56
|
+
- **Give each field one visible label**, per [bootstrap-reference.md](bootstrap-reference.md) →
|
|
57
|
+
Forms in production. Take labels, validation timing, and the error summary from that section, and
|
|
58
|
+
the affordance that carries them from [The catalog](#the-catalog).
|
|
59
|
+
- **Match the control sizes in a row.** Take `form-control-sm`, `form-select-sm`, `input-group-sm`,
|
|
60
|
+
and `btn-sm` together so a dense row shares one height.
|
|
61
|
+
- **Where Bootstrap ships no component, name the APG pattern the hand-roll owes** and route the
|
|
62
|
+
build-or-buy decision to [bootstrap-reference.md](bootstrap-reference.md) → When not to hand-roll.
|
|
63
|
+
Take those categories and their patterns from
|
|
64
|
+
[Where Bootstrap ships no component](#where-bootstrap-ships-no-component).
|
|
65
|
+
|
|
66
|
+
## The catalog
|
|
67
|
+
|
|
68
|
+
### One line of text
|
|
69
|
+
|
|
70
|
+
**Default.** An `input.form-control` under its own `label.form-label`, at rung 1.
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<label for="account-name" class="form-label">Account name</label>
|
|
74
|
+
<input type="text" class="form-control" id="account-name" aria-describedby="account-name-help" />
|
|
75
|
+
<div id="account-name-help" class="form-text">The name on the invoice.</div>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Alternates.** Take `.form-floating` when the row is too dense for a label line and the field is
|
|
79
|
+
never empty at rest. Take `.input-group` with `.input-group-text` when a prefix, a unit, or an
|
|
80
|
+
adjacent action belongs to the field; add `.has-validation` to the group so the feedback keeps the
|
|
81
|
+
border radius. Inside a dense grid cell, keep `form-control` and neutralize its chrome with the
|
|
82
|
+
declared transparent combination rather than dropping the control.
|
|
83
|
+
|
|
84
|
+
**States.** The fixed set, and nothing more.
|
|
85
|
+
|
|
86
|
+
### Text over many lines
|
|
87
|
+
|
|
88
|
+
**Default.** A `textarea.form-control` with a `rows` attribute sized to the expected answer, at
|
|
89
|
+
rung 1.
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
<label for="incident-notes" class="form-label">Notes</label>
|
|
93
|
+
<textarea class="form-control" id="incident-notes" rows="3"></textarea>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Alternates.** Take `.form-floating` when the surrounding rows use it, and set the height with a
|
|
97
|
+
stylesheet rule or a component variable rather than a `style` attribute. Draw a one-row composer that
|
|
98
|
+
grows as the person types from this same control with a scripted height. Bootstrap ships no
|
|
99
|
+
rich-text editor, so treat one as a hand-roll.
|
|
100
|
+
|
|
101
|
+
**States.** The fixed set, plus `full` where a character cap bounds the answer. Show the remaining
|
|
102
|
+
count in the `.form-text`, and keep it out of a live region unless the cap is close.
|
|
103
|
+
|
|
104
|
+
### A secret
|
|
105
|
+
|
|
106
|
+
**Default.** An `input[type=password].form-control` under a visible label, at rung 1.
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<label for="passphrase" class="form-label">Passphrase</label>
|
|
110
|
+
<div class="input-group">
|
|
111
|
+
<input type="password" class="form-control" id="passphrase" autocomplete="current-password" />
|
|
112
|
+
<button type="button" class="btn btn-outline-secondary" aria-pressed="false">Show</button>
|
|
113
|
+
</div>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Alternates.** Take the `.input-group` reveal button whenever the value is typed rather than pasted
|
|
117
|
+
from a manager, and toggle `aria-pressed` with the input `type`. Never block paste, and never mask a
|
|
118
|
+
one-time code the person must read back.
|
|
119
|
+
|
|
120
|
+
**States.** The fixed set. A strength or availability check runs as `busy` while it waits, not as
|
|
121
|
+
`invalid`.
|
|
122
|
+
|
|
123
|
+
### A number
|
|
124
|
+
|
|
125
|
+
**Default.** An `input[type=number].form-control`, at rung 1. In a column of figures add
|
|
126
|
+
`text-end font-monospace` so the digits align, at rung 2.
|
|
127
|
+
|
|
128
|
+
```html
|
|
129
|
+
<label for="unit-count" class="form-label">Units</label>
|
|
130
|
+
<input type="number" class="form-control text-end font-monospace" id="unit-count" step="1" />
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Alternates.** Take `.input-group` with `.input-group-text` for a currency symbol or a unit, so the
|
|
134
|
+
unit is chrome rather than something the person must type. Where the value is an identifier rather
|
|
135
|
+
than a quantity, take the one-line-of-text category instead.
|
|
136
|
+
|
|
137
|
+
**States.** The fixed set. Validate the range on blur and state the bound in the message.
|
|
138
|
+
|
|
139
|
+
### A number in a bounded range
|
|
140
|
+
|
|
141
|
+
**Default.** An `input.form-range`, at rung 1, and only when a minimum, a maximum, and a step are all
|
|
142
|
+
fixed.
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<label for="threshold" class="form-label">Threshold</label>
|
|
146
|
+
<div class="d-flex align-items-center gap-2">
|
|
147
|
+
<input type="range" class="form-range" id="threshold" min="0" max="100" step="5" />
|
|
148
|
+
<output for="threshold" class="font-monospace">50</output>
|
|
149
|
+
</div>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Alternates.** Keep a number input beside or instead of the slider when an exact value matters — a
|
|
153
|
+
range paints no read-out of its own, so a lone slider hides the value it sets. A two-thumb range is a
|
|
154
|
+
hand-roll: Bootstrap ships one thumb per input.
|
|
155
|
+
|
|
156
|
+
**States.** The fixed set. A disabled range still shows its value, so keep the read-out visible.
|
|
157
|
+
|
|
158
|
+
### A date
|
|
159
|
+
|
|
160
|
+
**Default.** An `input[type=date].form-control`, at rung 1. Take the calendar, the keyboard model,
|
|
161
|
+
and the locale format from the platform rather than authoring any of them.
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<label for="starts" class="form-label">Starts</label>
|
|
165
|
+
<input type="date" class="form-control" id="starts" />
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Alternates.** For a period, take two native inputs — a start and an end — before reaching for a
|
|
169
|
+
range picker, and validate the order on blur. Treat a calendar grid of your own as a hand-roll.
|
|
170
|
+
|
|
171
|
+
**States.** The fixed set. Express an unavailable day with `min`, `max`, and a stated rule in the
|
|
172
|
+
help text, because a native picker takes no per-day exclusion.
|
|
173
|
+
|
|
174
|
+
### A time
|
|
175
|
+
|
|
176
|
+
**Default.** An `input[type=time].form-control`, at rung 1, with `step` set to the granularity the
|
|
177
|
+
value actually carries.
|
|
178
|
+
|
|
179
|
+
```html
|
|
180
|
+
<label for="cutoff" class="form-label">Cutoff</label>
|
|
181
|
+
<input type="time" class="form-control" id="cutoff" step="900" />
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Alternates.** Treat segmented numeric fields as a hand-roll owing a spinbutton contract per
|
|
185
|
+
segment. Where the person picks from a fixed set of slots, take the one-of-many category instead: a
|
|
186
|
+
select is the lighter control.
|
|
187
|
+
|
|
188
|
+
**States.** The fixed set.
|
|
189
|
+
|
|
190
|
+
### A date and time
|
|
191
|
+
|
|
192
|
+
**Default.** An `input[type=datetime-local].form-control`, at rung 1.
|
|
193
|
+
|
|
194
|
+
```html
|
|
195
|
+
<label for="window-opens" class="form-label">Window opens</label>
|
|
196
|
+
<input type="datetime-local" class="form-control" id="window-opens" />
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Alternates.** Split into a date field and a time field when the two halves validate apart, when
|
|
200
|
+
one half is optional, or when a time zone control belongs between them. Name the zone the value is
|
|
201
|
+
stored in; a local datetime carries none.
|
|
202
|
+
|
|
203
|
+
**States.** The fixed set.
|
|
204
|
+
|
|
205
|
+
### A color
|
|
206
|
+
|
|
207
|
+
**Default.** An `input.form-control-color[type=color]`, at rung 1.
|
|
208
|
+
|
|
209
|
+
```html
|
|
210
|
+
<label for="brand-tint" class="form-label">Brand tint</label>
|
|
211
|
+
<input type="color" class="form-control form-control-color" id="brand-tint" value="#4a6fa5" />
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Alternates.** Pair the swatch with a text field when the value is copied, pasted, or read aloud
|
|
215
|
+
between people. Keep the swatch at a 24×24px target or larger.
|
|
216
|
+
|
|
217
|
+
**States.** The fixed set. A color input has no empty value, so give the field a default and say what
|
|
218
|
+
it is.
|
|
219
|
+
|
|
220
|
+
### One on/off answer
|
|
221
|
+
|
|
222
|
+
**Default.** A `.form-check` holding one `input.form-check-input[type=checkbox]` and its
|
|
223
|
+
`label.form-check-label`, at rung 1.
|
|
224
|
+
|
|
225
|
+
```html
|
|
226
|
+
<div class="form-check">
|
|
227
|
+
<input class="form-check-input" type="checkbox" id="send-receipt" />
|
|
228
|
+
<label class="form-check-label" for="send-receipt">Email me a receipt</label>
|
|
229
|
+
</div>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Alternates.** Take `.form-check.form-switch` with `role="switch"` when the change applies the
|
|
233
|
+
moment it is flipped, and keep the plain checkbox when the value commits on submit. A lone box is
|
|
234
|
+
one answer; a group of boxes holding a list is a different category, so read the any-of rows before
|
|
235
|
+
grouping boxes.
|
|
236
|
+
|
|
237
|
+
**States.** The fixed set. A switch that applies immediately is `busy` while the change is in
|
|
238
|
+
flight, and reverts visibly when it fails.
|
|
239
|
+
|
|
240
|
+
### One of a few
|
|
241
|
+
|
|
242
|
+
**Default.** A radio group: `fieldset` and `legend` around `.form-check` rows, at rung 1.
|
|
243
|
+
|
|
244
|
+
```html
|
|
245
|
+
<fieldset>
|
|
246
|
+
<legend class="form-label">Billing cycle</legend>
|
|
247
|
+
<div class="form-check">
|
|
248
|
+
<input class="form-check-input" type="radio" name="cycle" id="cycle-monthly" />
|
|
249
|
+
<label class="form-check-label" for="cycle-monthly">Monthly</label>
|
|
250
|
+
</div>
|
|
251
|
+
<div class="form-check">
|
|
252
|
+
<input class="form-check-input" type="radio" name="cycle" id="cycle-annual" />
|
|
253
|
+
<label class="form-check-label" for="cycle-annual">Annual</label>
|
|
254
|
+
</div>
|
|
255
|
+
</fieldset>
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Alternates.** Take a segmented `.btn-group` of `.btn-check` radios, at rung 2, when the choice
|
|
259
|
+
sits in a toolbar or a filter bar and every option fits on one row without wrapping; give the group
|
|
260
|
+
`role="radiogroup"` and one accessible name. A radio group and a segmented group draw the same
|
|
261
|
+
question, and the list size decides between them. Give a chosen filter an accent tone class rather
|
|
262
|
+
than `btn-outline-secondary`.
|
|
263
|
+
|
|
264
|
+
**States.** The fixed set, applied to the group rather than to one option. Mark the group invalid,
|
|
265
|
+
name it in the message, and keep the error under the last row.
|
|
266
|
+
|
|
267
|
+
### One of many
|
|
268
|
+
|
|
269
|
+
**Default.** A `select.form-select`, at rung 1.
|
|
270
|
+
|
|
271
|
+
```html
|
|
272
|
+
<label for="territory" class="form-label">Territory</label>
|
|
273
|
+
<select class="form-select" id="territory">
|
|
274
|
+
<option value="" selected disabled>Choose a territory</option>
|
|
275
|
+
<option value="emea">EMEA</option>
|
|
276
|
+
</select>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Alternates.** Drop to a radio group when the whole list fits in view and the options deserve
|
|
280
|
+
comparison. Move up to a searched list when the person can name the value faster than they can find
|
|
281
|
+
it, or when the list outgrows one scroll of the menu.
|
|
282
|
+
|
|
283
|
+
**States.** The fixed set, plus `empty` when the option list itself is empty — say why, and offer
|
|
284
|
+
the action that fills it. A locked select is `disabled`, so its value stops submitting; carry it in a
|
|
285
|
+
hidden input. A select whose options are loading is `busy`.
|
|
286
|
+
|
|
287
|
+
### One of many with an unlisted value admitted
|
|
288
|
+
|
|
289
|
+
**Default.** An `input.form-control` bound to a `<datalist>`, at rung 1. The list suggests; the
|
|
290
|
+
person can still submit a value it does not hold.
|
|
291
|
+
|
|
292
|
+
```html
|
|
293
|
+
<label for="carrier" class="form-label">Carrier</label>
|
|
294
|
+
<input class="form-control" list="carrier-options" id="carrier" />
|
|
295
|
+
<datalist id="carrier-options">
|
|
296
|
+
<option value="Northwind Freight"></option>
|
|
297
|
+
</datalist>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Alternates.** A combobox with free text is a hand-roll; take it only when the suggestions must be
|
|
301
|
+
fetched as the person types.
|
|
302
|
+
|
|
303
|
+
**States.** The fixed set. Attaching `list` changes the control's computed role to `combobox`, so
|
|
304
|
+
re-target every test and journey that finds this field by role.
|
|
305
|
+
|
|
306
|
+
### Any of a few
|
|
307
|
+
|
|
308
|
+
**Default.** `fieldset` and `legend` around `.form-check` checkbox rows sharing one name, at rung 1.
|
|
309
|
+
|
|
310
|
+
```html
|
|
311
|
+
<fieldset>
|
|
312
|
+
<legend class="form-label">Notify me about</legend>
|
|
313
|
+
<div class="form-check">
|
|
314
|
+
<input class="form-check-input" type="checkbox" name="notify" id="notify-billing" />
|
|
315
|
+
<label class="form-check-label" for="notify-billing">Billing</label>
|
|
316
|
+
</div>
|
|
317
|
+
</fieldset>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Alternates.** Take `.form-check-inline` when the options are short words and the row has space.
|
|
321
|
+
Bootstrap ships no `.form-check` color class, so an accent or danger box is an authored rule over
|
|
322
|
+
tokens — take it only under the exception in [inspection.md](inspection.md) → When an authored rule
|
|
323
|
+
is already earned.
|
|
324
|
+
|
|
325
|
+
**States.** The fixed set, plus `empty` and `full`. State a minimum or a maximum count in the help
|
|
326
|
+
text and validate it on the group.
|
|
327
|
+
|
|
328
|
+
### Any of many
|
|
329
|
+
|
|
330
|
+
**Default.** A bounded, scrollable list of `.form-check` rows inside a bordered box, at rung 2, with
|
|
331
|
+
a filter field preceding it so the person can narrow the list before choosing.
|
|
332
|
+
|
|
333
|
+
```html
|
|
334
|
+
<label for="regions-filter" class="form-label">Regions</label>
|
|
335
|
+
<input type="search" class="form-control form-control-sm mb-2" id="regions-filter" />
|
|
336
|
+
<div class="border rounded overflow-auto p-2 mh-100" role="group" aria-label="Regions">
|
|
337
|
+
<div class="form-check">
|
|
338
|
+
<input class="form-check-input" type="checkbox" name="regions" id="region-emea" />
|
|
339
|
+
<label class="form-check-label" for="region-emea">EMEA</label>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Alternates.** Take `select[multiple].form-select` where the platform control is acceptable to the
|
|
345
|
+
audience; its multi-select gesture is unteachable in a consumer flow but familiar in an internal
|
|
346
|
+
tool. A tags input is the ordered-set category. Bound the box's height from the layout that holds
|
|
347
|
+
it — Bootstrap ships `mh-100` and no other maximum-height step — never from a `style` attribute.
|
|
348
|
+
|
|
349
|
+
**States.** The fixed set, plus `empty` and `full`. Show the chosen count beside the list and give a
|
|
350
|
+
one-action way to clear it.
|
|
351
|
+
|
|
352
|
+
### A value picked from a searched list
|
|
353
|
+
|
|
354
|
+
**Default.** A combobox composed from shipped classes at rung 2, with the keyboard model
|
|
355
|
+
hand-rolled against the APG combobox pattern: an `input.form-control` carrying `role="combobox"`,
|
|
356
|
+
`aria-expanded`, `aria-controls`, `aria-autocomplete="list"`, and `aria-activedescendant`, over a
|
|
357
|
+
`ul.dropdown-menu[role=listbox]` of `.dropdown-item` buttons.
|
|
358
|
+
|
|
359
|
+
```html
|
|
360
|
+
<div class="input-group">
|
|
361
|
+
<input
|
|
362
|
+
class="form-control"
|
|
363
|
+
type="text"
|
|
364
|
+
role="combobox"
|
|
365
|
+
aria-expanded="false"
|
|
366
|
+
aria-controls="owner-listbox"
|
|
367
|
+
aria-autocomplete="list"
|
|
368
|
+
id="owner"
|
|
369
|
+
/>
|
|
370
|
+
<button type="button" class="btn btn-outline-secondary" aria-label="Clear">Clear</button>
|
|
371
|
+
</div>
|
|
372
|
+
<ul class="dropdown-menu show w-100 shadow" id="owner-listbox" role="listbox">
|
|
373
|
+
<li><button type="button" class="dropdown-item" role="option">Northwind Freight</button></li>
|
|
374
|
+
</ul>
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**Alternates.** Take the datalist in [One of many with an unlisted value
|
|
378
|
+
admitted](#one-of-many-with-an-unlisted-value-admitted) when the suggestions are static and short,
|
|
379
|
+
and pay for the combobox only when the list is fetched, ranked, or long enough to need one.
|
|
380
|
+
|
|
381
|
+
**States.** The fixed set, plus `empty` for a search that matched nothing — draw that with
|
|
382
|
+
`.dropdown-item-text`, never with an empty menu. The menu is `busy` while a query is in flight, and
|
|
383
|
+
the input stays operable throughout.
|
|
384
|
+
|
|
385
|
+
### Files
|
|
386
|
+
|
|
387
|
+
**Default.** An `input[type=file].form-control`, at rung 1.
|
|
388
|
+
|
|
389
|
+
```html
|
|
390
|
+
<label for="statement" class="form-label">Statement</label>
|
|
391
|
+
<input class="form-control" type="file" id="statement" accept=".csv" />
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Alternates.** Drive a hidden input from a button, a card, or a dropzone when the surface wants a
|
|
395
|
+
larger target; keep that input in the markup as the non-drag path, because a drag-only upload
|
|
396
|
+
strands keyboard and assistive-technology users. Dropzone chrome is an authored class contract over
|
|
397
|
+
tokens, and it owes a visible focus state of its own.
|
|
398
|
+
|
|
399
|
+
**States.** The fixed set, plus `empty` and `full`. Name the accepted types and the size cap in the
|
|
400
|
+
help text before the person picks, and report a rejected file beside the input rather than in a
|
|
401
|
+
toast.
|
|
402
|
+
|
|
403
|
+
### An ordered set of tags
|
|
404
|
+
|
|
405
|
+
**Default.** Bootstrap ships no tags input. Compose one at rung 2 from a text field that commits on
|
|
406
|
+
Enter plus a row of chips, each chip a `.badge` carrying a `btn-close` with its own accessible name.
|
|
407
|
+
|
|
408
|
+
```html
|
|
409
|
+
<label for="tag-entry" class="form-label">Tags</label>
|
|
410
|
+
<input class="form-control" type="text" id="tag-entry" aria-describedby="tag-entry-help" />
|
|
411
|
+
<div id="tag-entry-help" class="form-text">Press Enter to add a tag.</div>
|
|
412
|
+
<ul class="list-unstyled d-flex flex-wrap gap-2 mt-2">
|
|
413
|
+
<li>
|
|
414
|
+
<span class="badge text-bg-secondary d-inline-flex align-items-center gap-1">
|
|
415
|
+
Priority
|
|
416
|
+
<button
|
|
417
|
+
type="button"
|
|
418
|
+
class="btn-close"
|
|
419
|
+
data-bs-theme="dark"
|
|
420
|
+
aria-label="Remove Priority"
|
|
421
|
+
></button>
|
|
422
|
+
</span>
|
|
423
|
+
</li>
|
|
424
|
+
</ul>
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Alternates.** Where the tags come from a fixed vocabulary, this is the any-of-many category and the
|
|
428
|
+
list is the better control. Where order carries meaning, give the reorder a non-drag path — a move
|
|
429
|
+
control per chip.
|
|
430
|
+
|
|
431
|
+
**States.** The fixed set, plus `empty` and `full`. Announce an added or removed tag in a
|
|
432
|
+
`role="status"` region, because the chip row is far from the field that changed it.
|
|
433
|
+
|
|
434
|
+
### A rating
|
|
435
|
+
|
|
436
|
+
**Default.** Bootstrap ships no rating. Draw the interactive form as a radio group at rung 2 — one
|
|
437
|
+
radio per value, restyled through `.btn-check` — so the keyboard model, the name, and the submitted
|
|
438
|
+
value come from the platform. Star chrome over that structure is an authored class contract at
|
|
439
|
+
rung 4.
|
|
440
|
+
|
|
441
|
+
```html
|
|
442
|
+
<fieldset>
|
|
443
|
+
<legend class="form-label">Rating</legend>
|
|
444
|
+
<div class="btn-group" role="radiogroup">
|
|
445
|
+
<input type="radio" class="btn-check" name="rating" id="rating-1" />
|
|
446
|
+
<label class="btn btn-outline-primary" for="rating-1">1</label>
|
|
447
|
+
</div>
|
|
448
|
+
</fieldset>
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Alternates.** Draw a read-only rating as a glyph row with one accessible name stating the value,
|
|
452
|
+
per [components.md](components.md) → Status glyph marks; it is not a control. Treat a
|
|
453
|
+
`role="slider"` rating as a hand-roll owing the APG slider contract, including its keyboard model.
|
|
454
|
+
|
|
455
|
+
**States.** The fixed set. Keep a cleared rating reachable, and say what cleared means.
|
|
456
|
+
|
|
457
|
+
### A step in a sequence
|
|
458
|
+
|
|
459
|
+
**Default.** Draw a step indicator from shipped parts at rung 1 — a `nav` or `.list-group-numbered`
|
|
460
|
+
whose current item carries `aria-current="step"`, with a `.progress` bar over a long sequence. A step
|
|
461
|
+
indicator reports where the person is and holds no value, so it is not a field.
|
|
462
|
+
|
|
463
|
+
```html
|
|
464
|
+
<nav aria-label="Application progress">
|
|
465
|
+
<ol class="list-group list-group-numbered list-group-horizontal">
|
|
466
|
+
<li class="list-group-item" aria-current="step">Details</li>
|
|
467
|
+
<li class="list-group-item">Review</li>
|
|
468
|
+
</ol>
|
|
469
|
+
</nav>
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**Alternates.** Treat custom stepper chrome — connectors, dots, tick marks — as an authored class
|
|
473
|
+
contract over tokens. Take the rules for the sequence itself, including validation on leaving a step
|
|
474
|
+
and where the answers are held, from [bootstrap-reference.md](bootstrap-reference.md) → Wizards &
|
|
475
|
+
multi-step forms.
|
|
476
|
+
|
|
477
|
+
**States.** The fixed set, drawn on the controls that move between steps. The indicator holds no
|
|
478
|
+
value and is not a field, so it carries its own set instead: `rest` and a current mark that survives
|
|
479
|
+
every theme the surface ships. Never make position the only signal that a step failed.
|
|
480
|
+
|
|
481
|
+
## Where Bootstrap ships no component
|
|
482
|
+
|
|
483
|
+
Work the native-first ladder in [bootstrap-reference.md](bootstrap-reference.md) → When not to
|
|
484
|
+
hand-roll before building any category in this table, and take the hand-roll only with the named
|
|
485
|
+
pattern's contract in hand. Settle each row as a build-or-buy decision before it is a markup
|
|
486
|
+
decision. Take the contracts for dialog, combobox, radio group, and toolbar from
|
|
487
|
+
[bootstrap-reference.md](bootstrap-reference.md) → Pattern contracts.
|
|
488
|
+
|
|
489
|
+
| Category | Pattern the hand-roll owes |
|
|
490
|
+
| --------------------------- | -------------------------------------- |
|
|
491
|
+
| A date, as a calendar grid | APG dialog plus a grid keyboard model |
|
|
492
|
+
| A time, as segmented fields | APG spinbutton, per segment |
|
|
493
|
+
| A searched list | APG combobox |
|
|
494
|
+
| An ordered set of tags | APG combobox plus removable buttons |
|
|
495
|
+
| A rating, as one control | APG slider |
|
|
496
|
+
| A two-thumb range | APG slider, multi-thumb |
|
|
497
|
+
| A files dropzone | The visible input as the non-drag path |
|
|
498
|
+
| A data grid or a tree | APG grid, APG tree view |
|
|
499
|
+
|
|
500
|
+
Take skeletons from [components.md](components.md) → Placeholder (skeletons) instead; Bootstrap
|
|
501
|
+
ships them, so they stay out of this table.
|