@chromvoid/headless-ui 0.1.0

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 (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/dist/a11y-contracts/index.d.ts +23 -0
  4. package/dist/a11y-contracts/index.js +1 -0
  5. package/dist/accordion/index.d.ts +78 -0
  6. package/dist/accordion/index.js +264 -0
  7. package/dist/adapters/index.d.ts +9 -0
  8. package/dist/adapters/index.js +1 -0
  9. package/dist/alert/index.d.ts +33 -0
  10. package/dist/alert/index.js +54 -0
  11. package/dist/alert-dialog/index.d.ts +69 -0
  12. package/dist/alert-dialog/index.js +94 -0
  13. package/dist/badge/index.d.ts +48 -0
  14. package/dist/badge/index.js +89 -0
  15. package/dist/breadcrumb/index.d.ts +55 -0
  16. package/dist/breadcrumb/index.js +77 -0
  17. package/dist/button/index.d.ts +46 -0
  18. package/dist/button/index.js +86 -0
  19. package/dist/callout/index.d.ts +41 -0
  20. package/dist/callout/index.js +63 -0
  21. package/dist/card/index.d.ts +54 -0
  22. package/dist/card/index.js +103 -0
  23. package/dist/carousel/index.d.ts +98 -0
  24. package/dist/carousel/index.js +243 -0
  25. package/dist/checkbox/index.d.ts +50 -0
  26. package/dist/checkbox/index.js +87 -0
  27. package/dist/combobox/index.d.ts +114 -0
  28. package/dist/combobox/index.js +431 -0
  29. package/dist/command-palette/index.d.ts +73 -0
  30. package/dist/command-palette/index.js +147 -0
  31. package/dist/context-menu/index.d.ts +111 -0
  32. package/dist/context-menu/index.js +372 -0
  33. package/dist/copy-button/index.d.ts +62 -0
  34. package/dist/copy-button/index.js +183 -0
  35. package/dist/core/index.d.ts +20 -0
  36. package/dist/core/index.js +2 -0
  37. package/dist/core/selection.d.ts +5 -0
  38. package/dist/core/selection.js +39 -0
  39. package/dist/core/value-range.d.ts +49 -0
  40. package/dist/core/value-range.js +134 -0
  41. package/dist/date-picker/index.d.ts +210 -0
  42. package/dist/date-picker/index.js +895 -0
  43. package/dist/dialog/index.d.ts +95 -0
  44. package/dist/dialog/index.js +153 -0
  45. package/dist/disclosure/index.d.ts +52 -0
  46. package/dist/disclosure/index.js +159 -0
  47. package/dist/drawer/index.d.ts +30 -0
  48. package/dist/drawer/index.js +39 -0
  49. package/dist/feed/index.d.ts +77 -0
  50. package/dist/feed/index.js +260 -0
  51. package/dist/grid/index.d.ts +103 -0
  52. package/dist/grid/index.js +415 -0
  53. package/dist/index.d.ts +51 -0
  54. package/dist/index.js +51 -0
  55. package/dist/input/index.d.ts +86 -0
  56. package/dist/input/index.js +156 -0
  57. package/dist/interactions/composite-navigation.d.ts +69 -0
  58. package/dist/interactions/composite-navigation.js +169 -0
  59. package/dist/interactions/index.d.ts +15 -0
  60. package/dist/interactions/index.js +4 -0
  61. package/dist/interactions/keyboard-intents.d.ts +16 -0
  62. package/dist/interactions/keyboard-intents.js +33 -0
  63. package/dist/interactions/overlay-focus.d.ts +40 -0
  64. package/dist/interactions/overlay-focus.js +93 -0
  65. package/dist/interactions/typeahead.d.ts +20 -0
  66. package/dist/interactions/typeahead.js +41 -0
  67. package/dist/landmarks/index.d.ts +39 -0
  68. package/dist/landmarks/index.js +58 -0
  69. package/dist/link/index.d.ts +34 -0
  70. package/dist/link/index.js +39 -0
  71. package/dist/listbox/index.d.ts +92 -0
  72. package/dist/listbox/index.js +337 -0
  73. package/dist/menu/index.d.ts +132 -0
  74. package/dist/menu/index.js +541 -0
  75. package/dist/menu-button/index.d.ts +71 -0
  76. package/dist/menu-button/index.js +121 -0
  77. package/dist/meter/index.d.ts +45 -0
  78. package/dist/meter/index.js +106 -0
  79. package/dist/number/index.d.ts +113 -0
  80. package/dist/number/index.js +252 -0
  81. package/dist/popover/index.d.ts +70 -0
  82. package/dist/popover/index.js +126 -0
  83. package/dist/progress/index.d.ts +49 -0
  84. package/dist/progress/index.js +79 -0
  85. package/dist/radio-group/index.d.ts +61 -0
  86. package/dist/radio-group/index.js +150 -0
  87. package/dist/select/index.d.ts +92 -0
  88. package/dist/select/index.js +239 -0
  89. package/dist/sidebar/index.d.ts +74 -0
  90. package/dist/sidebar/index.js +186 -0
  91. package/dist/slider/index.d.ts +61 -0
  92. package/dist/slider/index.js +150 -0
  93. package/dist/slider-multi-thumb/index.d.ts +70 -0
  94. package/dist/slider-multi-thumb/index.js +222 -0
  95. package/dist/spinbutton/index.d.ts +75 -0
  96. package/dist/spinbutton/index.js +214 -0
  97. package/dist/spinner/index.d.ts +1 -0
  98. package/dist/spinner/index.js +1 -0
  99. package/dist/spinner/spinner.d.ts +23 -0
  100. package/dist/spinner/spinner.js +25 -0
  101. package/dist/switch/index.d.ts +40 -0
  102. package/dist/switch/index.js +61 -0
  103. package/dist/table/index.d.ts +117 -0
  104. package/dist/table/index.js +377 -0
  105. package/dist/tabs/index.d.ts +63 -0
  106. package/dist/tabs/index.js +174 -0
  107. package/dist/textarea/index.d.ts +68 -0
  108. package/dist/textarea/index.js +137 -0
  109. package/dist/toast/index.d.ts +67 -0
  110. package/dist/toast/index.js +145 -0
  111. package/dist/toolbar/index.d.ts +59 -0
  112. package/dist/toolbar/index.js +139 -0
  113. package/dist/tooltip/index.d.ts +52 -0
  114. package/dist/tooltip/index.js +169 -0
  115. package/dist/treegrid/index.d.ts +101 -0
  116. package/dist/treegrid/index.js +463 -0
  117. package/dist/treeview/index.d.ts +68 -0
  118. package/dist/treeview/index.js +370 -0
  119. package/dist/window-splitter/index.d.ts +65 -0
  120. package/dist/window-splitter/index.js +204 -0
  121. package/package.json +92 -0
  122. package/specs/ADR-001-headless-architecture.md +461 -0
  123. package/specs/ADR-002-repo-release-model.md +108 -0
  124. package/specs/ADR-003-public-api-versioning.md +136 -0
  125. package/specs/ADR-004-focus-selection-policy.md +117 -0
  126. package/specs/IMPLEMENTATION-ROADMAP.md +237 -0
  127. package/specs/ISSUE-BACKLOG.md +681 -0
  128. package/specs/RELEASE-CANDIDATE.md +30 -0
  129. package/specs/components/accordion.md +130 -0
  130. package/specs/components/alert-dialog.md +72 -0
  131. package/specs/components/alert.md +65 -0
  132. package/specs/components/badge.md +220 -0
  133. package/specs/components/breadcrumb.md +74 -0
  134. package/specs/components/button.md +115 -0
  135. package/specs/components/callout.md +195 -0
  136. package/specs/components/card.md +280 -0
  137. package/specs/components/carousel.md +140 -0
  138. package/specs/components/checkbox.md +172 -0
  139. package/specs/components/combobox.md +423 -0
  140. package/specs/components/command-palette.md +92 -0
  141. package/specs/components/context-menu.md +556 -0
  142. package/specs/components/copy-button.md +293 -0
  143. package/specs/components/date-picker.md +400 -0
  144. package/specs/components/dialog.md +298 -0
  145. package/specs/components/disclosure.md +257 -0
  146. package/specs/components/drawer.md +353 -0
  147. package/specs/components/feed.md +265 -0
  148. package/specs/components/grid.md +186 -0
  149. package/specs/components/input.md +254 -0
  150. package/specs/components/landmarks.md +136 -0
  151. package/specs/components/link.md +134 -0
  152. package/specs/components/listbox.md +351 -0
  153. package/specs/components/menu-button.md +76 -0
  154. package/specs/components/menu.md +623 -0
  155. package/specs/components/meter.md +149 -0
  156. package/specs/components/number.md +393 -0
  157. package/specs/components/popover.md +252 -0
  158. package/specs/components/progress.md +188 -0
  159. package/specs/components/radio-group.md +151 -0
  160. package/specs/components/select.md +144 -0
  161. package/specs/components/sidebar.md +321 -0
  162. package/specs/components/slider-multi-thumb.md +78 -0
  163. package/specs/components/slider.md +84 -0
  164. package/specs/components/spinbutton.md +140 -0
  165. package/specs/components/spinner.md +132 -0
  166. package/specs/components/switch.md +175 -0
  167. package/specs/components/table.md +403 -0
  168. package/specs/components/tabs.md +265 -0
  169. package/specs/components/textarea.md +185 -0
  170. package/specs/components/toast.md +198 -0
  171. package/specs/components/toolbar.md +278 -0
  172. package/specs/components/tooltip.md +252 -0
  173. package/specs/components/treegrid.md +281 -0
  174. package/specs/components/treeview.md +91 -0
  175. package/specs/components/window-splitter.md +297 -0
  176. package/specs/ops/git-shard-sync.md +107 -0
  177. package/specs/ops/release-checklist.md +76 -0
  178. package/specs/release/GAP-TO-GREEN-ISSUES.md +88 -0
  179. package/specs/release/api-freeze-candidate.md +54 -0
  180. package/specs/release/changelog-automation.md +76 -0
  181. package/specs/release/changelog.generated.md +53 -0
  182. package/specs/release/changelog.patch.generated.md +46 -0
  183. package/specs/release/consumer-integration.md +53 -0
  184. package/specs/release/migration-notes-pre-v1.md +40 -0
  185. package/specs/release/mvp-changelog.md +57 -0
  186. package/specs/release/release-notes-template.md +61 -0
  187. package/specs/release/release-rehearsal.md +113 -0
  188. package/specs/release/semver-deprecation-dry-run.md +89 -0
  189. package/specs/release/shard-release-drill-report.md +50 -0
  190. package/specs/release/shard-release-follow-ups.md +31 -0
  191. package/specs/signals.md +208 -0
@@ -0,0 +1,140 @@
1
+ # Carousel Component Contract
2
+
3
+ ## Purpose
4
+
5
+ `Carousel` provides a headless APG-aligned model for a slideshow or image rotator, enabling users to navigate through a set of items (slides) sequentially.
6
+
7
+ ## Component Files
8
+
9
+ - `src/carousel/index.ts` - model and public `createCarousel` API
10
+ - `src/carousel/carousel.test.ts` - unit behavior tests
11
+
12
+ ## Public API
13
+
14
+ - `createCarousel(options)`
15
+ - `state` (signal-backed):
16
+ - `activeSlideIndex()`
17
+ - `isPaused()`
18
+ - `slideCount()`
19
+ - `visibleSlideIndices()` - array of indices currently in view
20
+ - `actions`:
21
+ - navigation: `moveNext`, `movePrev`, `moveTo(index)`
22
+ - playback: `play`, `pause`, `togglePlay`
23
+ - keyboard: `handleKeyDown`
24
+ - `contracts`:
25
+ - `getRootProps()`
26
+ - `getSlideGroupProps()`
27
+ - `getSlideProps(index)`
28
+ - `getNextButtonProps()`
29
+ - `getPrevButtonProps()`
30
+ - `getPlayPauseButtonProps()`
31
+ - `getIndicatorProps(index)`
32
+
33
+ ## APG and A11y Contract
34
+
35
+ - root role: `region` (with `aria-roledescription="carousel"`)
36
+ - slide group role: `group` (with `aria-roledescription="slide"`)
37
+ - required attributes:
38
+ - root: `aria-label` or `aria-labelledby`
39
+ - slide: `aria-label` or `aria-labelledby`, `aria-hidden` (if not visible)
40
+ - buttons: `aria-controls`, `aria-label`
41
+ - play/pause button: toggle `aria-label` between "Stop slide rotation" / "Start slide rotation" (NO `aria-pressed` per W3C APG guidance)
42
+ - focus management:
43
+ - slides are not focusable by default unless they contain interactive elements
44
+ - navigation controls (buttons, indicators) are in the tab sequence
45
+
46
+ ## Behavior Contract
47
+
48
+ - `Carousel` supports automatic rotation (autoplay) with a configurable interval.
49
+ - rotation MUST pause on focus (when any element inside the carousel is focused) or on mouse hover.
50
+ - rotation MUST stop permanently if the user explicitly pauses it.
51
+ - `aria-live` is used to announce slide changes:
52
+ - `off` when autoplaying
53
+ - `polite` when the user manually navigates
54
+
55
+ ## Invariants
56
+
57
+ - `activeSlideIndex` must be within `[0, slideCount - 1]`.
58
+ - `aria-roledescription` is used to provide a more descriptive role than `region` or `group`.
59
+ - only visible slides should be accessible to assistive technologies (`aria-hidden="false"`).
60
+
61
+ ## Transitions Table
62
+
63
+ | Event / Action | Current State | Next State / Effect |
64
+ | --------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------ |
65
+ | `moveNext()` | any | `activeSlideIndex` = `(current + 1) % slideCount`; `aria-live` = `polite`; reset autoplay timer |
66
+ | `movePrev()` | any | `activeSlideIndex` = `(current - 1 + slideCount) % slideCount`; `aria-live` = `polite`; reset autoplay timer |
67
+ | `moveTo(index)` | any | `activeSlideIndex` = clamped index; `aria-live` = `polite`; reset autoplay timer |
68
+ | `play()` | `userPaused = true` | `userPaused` = `false`; autoplay resumes |
69
+ | `pause()` | `userPaused = false` | `userPaused` = `true`; autoplay timer cleared |
70
+ | `togglePlay()` | `userPaused = true` | calls `play()` |
71
+ | `togglePlay()` | `userPaused = false` | calls `pause()` |
72
+ | `handleFocusIn()` | any | `isFocusWithin` = `true`; autoplay timer cleared |
73
+ | `handleFocusOut()` | any | `isFocusWithin` = `false`; autoplay resumes if eligible |
74
+ | `handlePointerEnter()` | any | `isPointerInside` = `true`; autoplay timer cleared |
75
+ | `handlePointerLeave()` | any | `isPointerInside` = `false`; autoplay resumes if eligible |
76
+ | `handleKeyDown(ArrowRight)` | any | calls `moveNext()` |
77
+ | `handleKeyDown(ArrowLeft)` | any | calls `movePrev()` |
78
+ | `handleKeyDown(Home)` | any | calls `moveTo(0)` |
79
+ | `handleKeyDown(End)` | any | calls `moveTo(slideCount - 1)` |
80
+ | autoplay timer fires | autoplay running | `activeSlideIndex` advances by 1; `aria-live` = `off`; timer restarted |
81
+
82
+ Autoplay is "running" when: `autoplay` option enabled AND `userPaused = false` AND `isPointerInside = false` AND `isFocusWithin = false` AND `slideCount > 1`.
83
+
84
+ ## Adapter Expectations
85
+
86
+ UIKit adapters MUST bind to the headless model as follows:
87
+
88
+ **Signals read (reactive, drive re-renders):**
89
+
90
+ - `state.activeSlideIndex()` — current active slide index
91
+ - `state.isPaused()` — computed pause state (combines user-paused, focus, pointer)
92
+ - `state.slideCount()` — number of slides
93
+ - `state.visibleSlideIndices()` — array of currently visible slide indices
94
+
95
+ **Actions called (event handlers, never mutate state directly):**
96
+
97
+ - `actions.moveNext()` / `actions.movePrev()` — on next/prev button click
98
+ - `actions.moveTo(index)` — on indicator click
99
+ - `actions.togglePlay()` — on play/pause button click
100
+ - `actions.handleKeyDown(event)` — on keydown within carousel
101
+ - `actions.handleFocusIn()` / `actions.handleFocusOut()` — on focusin/focusout on root
102
+ - `actions.handlePointerEnter()` / `actions.handlePointerLeave()` — on pointerenter/pointerleave on root
103
+
104
+ **Contracts spread (attribute maps applied directly to DOM elements):**
105
+
106
+ - `contracts.getRootProps()` — spread onto the carousel root element
107
+ - `contracts.getSlideGroupProps()` — spread onto the slide container
108
+ - `contracts.getSlideProps(index)` — spread onto each slide element
109
+ - `contracts.getNextButtonProps()` — spread onto the next button
110
+ - `contracts.getPrevButtonProps()` — spread onto the previous button
111
+ - `contracts.getPlayPauseButtonProps()` — spread onto the play/pause toggle button (returns `aria-label` only, no `aria-pressed`)
112
+ - `contracts.getIndicatorProps(index)` — spread onto each indicator button
113
+
114
+ **UIKit-only concerns (NOT in headless):**
115
+
116
+ - Touch/swipe gesture handling
117
+ - CSS transition animations
118
+ - Responsive layout logic
119
+
120
+ ## Minimum Test Matrix
121
+
122
+ - manual navigation via `moveNext` / `movePrev`
123
+ - autoplay lifecycle (start, pause on focus, resume on blur)
124
+ - `aria-live` state transitions based on interaction source
125
+ - correct `aria-hidden` mapping for visible vs hidden slides
126
+ - indicator synchronization with `activeSlideIndex`
127
+
128
+ ## ADR-001 Compliance
129
+
130
+ - **Runtime Policy**: Reatom v1000 only; no @statx/\* in headless core.
131
+ - **Layering**: core -> interactions -> a11y-contracts -> adapters; adapters remain thin mappings.
132
+ - **Independence**: No imports from @project/_, apps/_, or other out-of-package modules.
133
+ - **Verification**: Mandatory adapter integration tests and standalone package test execution.
134
+
135
+ ## Out of Scope (Current)
136
+
137
+ - touch swipe gestures (should be handled in the adapter)
138
+ - complex transition animations (CSS/JS animations are out of scope for headless)
139
+ - vertical carousels
140
+ - variable-width slides
@@ -0,0 +1,172 @@
1
+ # Checkbox Component Contract
2
+
3
+ ## Purpose
4
+
5
+ `Checkbox` provides a headless APG-aligned model for a two-state or three-state (indeterminate) toggle control.
6
+
7
+ It handles checked state transitions, indeterminate state management, and standard keyboard interactions.
8
+
9
+ ## Terminology (Normative)
10
+
11
+ Canonical conceptual states:
12
+
13
+ | Conceptual state | Component state (canonical) | ARIA mapping |
14
+ | ---------------- | -------------------------------------- | ---------------------- |
15
+ | `unchecked` | `checked=false`, `indeterminate=false` | `aria-checked="false"` |
16
+ | `checked` | `checked=true`, `indeterminate=false` | `aria-checked="true"` |
17
+ | `indeterminate` | `checked=false`, `indeterminate=true` | `aria-checked="mixed"` |
18
+
19
+ Notes:
20
+
21
+ - `indeterminate` is the canonical component third-state term.
22
+ - `mixed` is an ARIA token only (used exclusively in `aria-checked="mixed"`).
23
+
24
+ ## Component Files
25
+
26
+ - `src/checkbox/index.ts` - model and public `createCheckbox` API
27
+ - `src/checkbox/checkbox.test.ts` - unit behavior tests
28
+
29
+ ## Public API
30
+
31
+ - `createCheckbox(options)`
32
+ - `state` (signal-backed):
33
+ - `checked()`: `boolean`
34
+ - `indeterminate()`: `boolean`
35
+ - `isDisabled()`: `boolean`
36
+ - `isReadOnly()`: `boolean`
37
+ - `actions`:
38
+ - `toggle()`: toggles between checked and unchecked (see indeterminate transition)
39
+ - `setChecked(value: boolean)`: explicitly sets `checked` (normalizes indeterminate)
40
+ - `setIndeterminate(value: boolean)`: explicitly sets `indeterminate` (normalizes checked)
41
+ - `handleKeyDown(event)`
42
+ - `contracts`:
43
+ - `getCheckboxProps()`
44
+
45
+ ## Form-Associated Primitives (Normative)
46
+
47
+ The headless core defines form-associated primitives and expected semantics. Adapters/wrappers MUST map these primitives to the host platform's form APIs (for example, a native `input[type="checkbox"]`).
48
+
49
+ ### Primitive surface
50
+
51
+ - `name`: `string | undefined`
52
+ - Form field name used during submission. If `name` is empty/undefined, the control does not contribute a value.
53
+ - `value`: `string | undefined`
54
+ - Value submitted for `name` when the checkbox is submitted.
55
+ - If not provided, the default value is `"on"` (matching HTML checkbox defaults).
56
+ - `required`: `boolean | undefined`
57
+ - Constraint that requires the checkbox to be checked.
58
+ - `form`: `string | undefined`
59
+ - Associates the control to a specific form owner (maps to the HTML `form` attribute). If omitted, the nearest containing form is used.
60
+ - `defaultChecked`: `boolean | undefined`
61
+ - Initial `checked` state used for uncontrolled usage. Applied once during initialization.
62
+ - `autofocus`: `boolean | undefined`
63
+ - If true, the rendered control SHOULD receive focus on initial mount/connection.
64
+
65
+ ### Submission semantics
66
+
67
+ - The control contributes a name/value pair only when `checked=true`.
68
+ - `indeterminate` behaves as unchecked for submission: if `indeterminate=true` then the control MUST NOT submit a value.
69
+ - When submitted, the pair is `name=value` (or `name="on"` when `value` is not provided).
70
+
71
+ ### Required / validity semantics
72
+
73
+ - If `required=true`, the constraint is satisfied only when `checked=true`.
74
+ - `indeterminate` does not satisfy `required` (treated as unchecked for validity).
75
+
76
+ ## APG and A11y Contract
77
+
78
+ - role: `checkbox`
79
+ - `aria-checked`: `"true" | "false" | "mixed"`
80
+ - `aria-disabled`: boolean (if disabled)
81
+ - `aria-readonly`: boolean (if readonly)
82
+ - `tabindex`: `0` (or `-1` if disabled)
83
+ - linkage: supports `aria-labelledby` and `aria-describedby` via options
84
+
85
+ ## Behavior Contract
86
+
87
+ - `Space` key toggles the checked state.
88
+ - `Click` interaction toggles the checked state.
89
+ - If `indeterminate` is enabled, the component can be initialized in an indeterminate state.
90
+ - User toggle transition: `indeterminate` -> `checked` (standard APG recommendation).
91
+ - Disabled or Read-only checkboxes do not respond to toggle actions.
92
+
93
+ ## Cross-Spec Consistency (Normative)
94
+
95
+ This document defines the canonical checkbox state model and invariants.
96
+
97
+ - UIKit components (for example `cv-checkbox`) and any adapters/wrappers MUST preserve the same conceptual states, invariants, and user-driven transitions defined here.
98
+ - If a UIKit surface intentionally diverges, the divergence MUST be explicitly documented in both specs to prevent drift.
99
+
100
+ ## Invariants
101
+
102
+ - Canonical conceptual states are exactly: `unchecked`, `checked`, `indeterminate`.
103
+ - If represented as booleans, `indeterminate=true` implies `checked=false`.
104
+ - Normalization rules:
105
+ - When setting state, if `indeterminate=true`, then force `checked=false`.
106
+ - If indeterminate behavior is not enabled/configured, then any attempt to set `indeterminate=true` MUST normalize to `indeterminate=false`.
107
+ - A disabled checkbox cannot be toggled via `actions.toggle()`.
108
+ - A read-only checkbox cannot be toggled via `actions.toggle()`.
109
+
110
+ ## Minimum Test Matrix
111
+
112
+ - toggle behavior (false -> true -> false)
113
+ - indeterminate state initialization and transition to checked on toggle
114
+ - disabled state prevents state changes
115
+ - read-only state prevents state changes
116
+ - keyboard `Space` interaction
117
+ - correct `aria-checked` mapping for all states (including `aria-checked="mixed"` for indeterminate)
118
+
119
+ ## Migration Notes (Non-normative)
120
+
121
+ This section documents known terminology/state-shape changes and the breaking-change communication policy.
122
+
123
+ ### Terminology change: `mixed` -> `indeterminate`
124
+
125
+ - `indeterminate` is the canonical third-state term.
126
+ - `mixed` remains an ARIA token only (used exclusively in `aria-checked="mixed"`).
127
+
128
+ ### State shape change: tri-state -> two booleans
129
+
130
+ The current runtime implementation historically used an internal tri-state value for `checked` and related options.
131
+
132
+ - Old (legacy/internal): `checked: boolean | 'mixed'` (+ optional `mixed: boolean`) and `allowMixed`.
133
+ - New (canonical/public): `checked: boolean`, `indeterminate: boolean`, and `allowIndeterminate`.
134
+
135
+ Mappings:
136
+
137
+ - `checked === 'mixed'` -> `indeterminate=true` and `checked=false`.
138
+ - `allowMixed` -> `allowIndeterminate`.
139
+
140
+ ### Breaking-change communication policy
141
+
142
+ When this contract changes in a breaking way, the change MUST be documented in this section as:
143
+
144
+ - terminology changes (old term -> new term)
145
+ - state/payload shape changes (old shape -> new shape)
146
+ - a short, explicit statement that the change is breaking and requires consumer migration
147
+
148
+ ### Parity matrix (Headless vs UIKit)
149
+
150
+ This matrix is intentionally short and exists to prevent drift between `packages/headless/specs/components/checkbox.md` and `packages/uikit/specs/components/checkbox.md`.
151
+
152
+ | Surface | Headless | UIKit |
153
+ | ---------------------------- | ------------------------------------------ | ---------------------------------------- |
154
+ | Canonical third-state term | `indeterminate` | `indeterminate` attribute + event detail |
155
+ | ARIA token for third state | `aria-checked="mixed"` only | `aria-checked="mixed"` only |
156
+ | State representation | `checked:boolean`, `indeterminate:boolean` | `checked`/`indeterminate` attributes |
157
+ | User toggle transition | `indeterminate` -> `checked` | `indeterminate` -> `checked` |
158
+ | Disabled/read-only semantics | cannot toggle | cannot toggle |
159
+ | Payload on user interaction | N/A (actions/state API) | `{ checked, indeterminate, value? }` |
160
+ | Form primitives | specified (see above) | not specified on `cv-checkbox` surface |
161
+
162
+ ## ADR-001 Compliance
163
+
164
+ - **Runtime Policy**: Reatom v1000 only; no @statx/\* in headless core.
165
+ - **Layering**: core -> interactions -> a11y-contracts -> adapters; adapters remain thin mappings.
166
+ - **Independence**: No imports from @project/_, apps/_, or other out-of-package modules.
167
+ - **Verification**: Mandatory adapter integration tests and standalone package test execution.
168
+
169
+ ## Out of Scope (Current)
170
+
171
+ - native form submission integration (handled by adapters/wrappers)
172
+ - grouping logic (see `CheckboxGroup` or `Fieldset` specs)