@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.
Files changed (76) hide show
  1. package/README.md +13 -10
  2. package/dist/bin/main.js +632 -320
  3. package/dist/bin/main.js.map +1 -1
  4. package/dist/host/CLAUDE.md +5 -1
  5. package/dist/host/agents/orchestration.md +44 -19
  6. package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +91 -82
  7. package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +5 -5
  8. package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +15 -15
  9. package/dist/host/agents/skills/enterprise-bootstrap/references/inputs.md +501 -0
  10. package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +167 -0
  11. package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +2 -2
  12. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
  13. package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +10 -9
  14. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +21 -11
  15. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +20 -2
  16. package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +1 -1
  17. package/dist/host/agents/skills/orkestrel-harden-package/references/hardening.md +1 -1
  18. package/dist/host/agents/skills/orkestrel-harden-package/references/research.md +1 -1
  19. package/dist/host/agents/skills/orkestrel-polish-surface/SKILL.md +4 -1
  20. package/dist/host/agents/skills/orkestrel-polish-surface/references/capture-harness.md +71 -50
  21. package/dist/host/agents/skills/orkestrel-prove-journey/SKILL.md +93 -29
  22. package/dist/host/agents/skills/orkestrel-prove-journey/agents/openai.yaml +1 -1
  23. package/dist/host/agents/skills/orkestrel-prove-journey/references/captures.md +62 -38
  24. package/dist/host/agents/skills/orkestrel-prove-journey/references/decide.md +68 -0
  25. package/dist/host/agents/skills/orkestrel-prove-journey/references/layer.md +107 -79
  26. package/dist/host/agents/skills/orkestrel-prove-journey/references/statechart.md +84 -0
  27. package/dist/host/agents/skills/orkestrel-prove-journey/references/styles.md +87 -0
  28. package/dist/host/agents/templates/brief.md +16 -7
  29. package/dist/host/agents/transports/claude.md +4 -2
  30. package/dist/host/agents/transports/codex.md +4 -1
  31. package/dist/host/claude/agents/analyst.md +3 -1
  32. package/dist/host/claude/agents/application.md +1 -1
  33. package/dist/host/claude/agents/builder.md +3 -3
  34. package/dist/host/claude/agents/checker.md +5 -0
  35. package/dist/host/claude/agents/grok.md +15 -5
  36. package/dist/host/claude/agents/implementer.md +1 -1
  37. package/dist/host/claude/agents/orkestrel.md +12 -11
  38. package/dist/host/claude/agents/planner.md +10 -0
  39. package/dist/host/claude/agents/reviewer.md +14 -8
  40. package/dist/host/claude/agents/sol.md +3 -1
  41. package/dist/host/claude/agents/verifier.md +2 -4
  42. package/dist/host/claude/rules/architecture.md +7 -5
  43. package/dist/host/claude/rules/documentation.md +1 -0
  44. package/dist/host/claude/rules/names.md +23 -5
  45. package/dist/host/claude/rules/patterns.md +1 -0
  46. package/dist/host/claude/rules/quality.md +1 -1
  47. package/dist/host/claude/rules/tests.md +3 -3
  48. package/dist/host/claude/rules/typescript.md +4 -1
  49. package/dist/host/claude/rules/writing.md +2 -2
  50. package/dist/host/claude/skills/orkestrel-prove-journey/SKILL.md +1 -1
  51. package/dist/host/codex/agents/builder.toml +6 -6
  52. package/dist/host/codex/agents/checker.toml +2 -1
  53. package/dist/host/codex/agents/grok.toml +12 -5
  54. package/dist/host/codex/agents/implementer.toml +2 -2
  55. package/dist/host/codex/agents/opus.toml +6 -1
  56. package/dist/host/codex/agents/planner.toml +11 -6
  57. package/dist/host/codex/agents/reviewer.toml +8 -6
  58. package/dist/host/guides/scaffold.md +39 -14
  59. package/dist/host/manifest.json +81 -51
  60. package/dist/host/scripts/codex.sh +0 -0
  61. package/dist/host/scripts/cursor.sh +0 -0
  62. package/dist/host/scripts/deps.sh +0 -0
  63. package/dist/host/scripts/ollama.sh +0 -0
  64. package/dist/src/core/index.cjs +424 -282
  65. package/dist/src/core/index.cjs.map +1 -1
  66. package/dist/src/core/index.d.cts +361 -220
  67. package/dist/src/core/index.d.ts +361 -220
  68. package/dist/src/core/index.js +421 -283
  69. package/dist/src/core/index.js.map +1 -1
  70. package/dist/src/server/index.cjs +208 -170
  71. package/dist/src/server/index.cjs.map +1 -1
  72. package/dist/src/server/index.d.cts +276 -152
  73. package/dist/src/server/index.d.ts +276 -152
  74. package/dist/src/server/index.js +200 -172
  75. package/dist/src/server/index.js.map +1 -1
  76. 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` + variants — see [Tables](#tables)
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
- Variants: `.accordion-flush` (edge-to-edge, no outer borders); omit `data-bs-parent` to allow multiple items open.
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 variant by contrast rather than by taste ([SKILL.md](../SKILL.md) → Hierarchy & actions).
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 variant -->
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 via `--bs-offcanvas-width` (400px) / `--bs-offcanvas-height` (30vh). Full app-shell pattern: [bootstrap-reference.md](bootstrap-reference.md) → App shell.
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 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.
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 /* variants, on table/tr/td */
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` variant approach is superseded).
867
- - **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.
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 variant</div>
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 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.
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 via tooltip. `data-bs-html` with untrusted content is an XSS vector.
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 SPAs should prefer framework wrappers: [bootstrap-reference.md](bootstrap-reference.md) → JavaScript lifecycle.
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 variant (a real theme color) rather than the neutral outline, so "chosen" reads the same way in both modes.
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 variants (`navbar-dark`, `dropdown-menu-dark`, `btn-close-white`, `carousel-dark`) all map to `data-bs-theme="dark"`.
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.