@motion-proto/live-tokens 0.97.0 → 0.98.2

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 (184) hide show
  1. package/.claude/skills/live-tokens-check-compliance/SKILL.md +5 -0
  2. package/.claude/skills/live-tokens-create-component/SKILL.md +4 -3
  3. package/.claude/skills/live-tokens-create-component/references/contract-tests.md +71 -11
  4. package/CHANGELOG.md +272 -0
  5. package/README.md +2 -2
  6. package/bin/check-component.mjs +8 -0
  7. package/bin/check-page.mjs +6 -0
  8. package/bin/cli.mjs +36 -6
  9. package/bin/contractRunner.mjs +142 -17
  10. package/bin/lib/findings.mjs +20 -4
  11. package/bin/rules/a11y.mjs +53 -0
  12. package/dist-plugin/{chunk-A4TBP4MM.js → chunk-SLICH4IH.js} +2 -2
  13. package/dist-plugin/index.cjs +148 -3
  14. package/dist-plugin/index.js +250 -105
  15. package/dist-plugin/setColors/index.js +1 -1
  16. package/package.json +47 -2
  17. package/src/demo/LabeledSelect.svelte +1 -1
  18. package/src/editor/bootstrap.ts +3 -1
  19. package/src/editor/component-editor/BadgeEditor.svelte +13 -0
  20. package/src/editor/component-editor/ButtonEditor.svelte +33 -2
  21. package/src/editor/component-editor/CalloutEditor.svelte +13 -0
  22. package/src/editor/component-editor/CardEditor.svelte +12 -2
  23. package/src/editor/component-editor/CodeSnippetEditor.svelte +11 -1
  24. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +18 -1
  25. package/src/editor/component-editor/CornerBadgeEditor.svelte +13 -0
  26. package/src/editor/component-editor/DialogEditor.svelte +15 -0
  27. package/src/editor/component-editor/IconButtonEditor.svelte +29 -1
  28. package/src/editor/component-editor/ImageEditor.svelte +20 -9
  29. package/src/editor/component-editor/ImageLightboxEditor.svelte +44 -0
  30. package/src/editor/component-editor/InlineEditActionsEditor.svelte +13 -1
  31. package/src/editor/component-editor/InputEditor.svelte +23 -0
  32. package/src/editor/component-editor/MenuSelectEditor.svelte +27 -1
  33. package/src/editor/component-editor/NotificationEditor.svelte +15 -0
  34. package/src/editor/component-editor/PanelEditor.svelte +1 -1
  35. package/src/editor/component-editor/ProgressBarEditor.svelte +13 -0
  36. package/src/editor/component-editor/RadioButtonEditor.svelte +20 -1
  37. package/src/editor/component-editor/SectionDividerEditor.svelte +41 -13
  38. package/src/editor/component-editor/SegmentedControlEditor.svelte +29 -1
  39. package/src/editor/component-editor/SideNavigationEditor.svelte +40 -1
  40. package/src/editor/component-editor/SlidePagerEditor.svelte +20 -0
  41. package/src/editor/component-editor/SliderEditor.svelte +33 -0
  42. package/src/editor/component-editor/TabBarEditor.svelte +23 -2
  43. package/src/editor/component-editor/TableEditor.svelte +14 -0
  44. package/src/editor/component-editor/ToggleEditor.svelte +17 -0
  45. package/src/editor/component-editor/TooltipEditor.svelte +11 -2
  46. package/src/editor/component-editor/VideoFrameEditor.svelte +8 -0
  47. package/src/editor/component-editor/VideoLightboxEditor.svelte +10 -0
  48. package/src/editor/component-editor/registration.ts +2 -1
  49. package/src/editor/component-editor/registry.ts +69 -27
  50. package/src/editor/component-editor/scaffolding/DemoHeader.svelte +2 -0
  51. package/src/editor/component-editor/scaffolding/StateBlock.svelte +19 -8
  52. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +3 -0
  53. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +70 -3
  54. package/src/editor/component-editor/scaffolding/editorContext.ts +7 -0
  55. package/src/editor/core/a11y/a11yReport.ts +101 -0
  56. package/src/editor/core/a11y/a11yReportTypes.ts +69 -0
  57. package/src/editor/core/a11y/contrastFailures.ts +22 -0
  58. package/src/editor/core/components/contrastPairRegistry.ts +18 -0
  59. package/src/editor/core/palettes/contrast.ts +44 -0
  60. package/src/editor/core/palettes/contrastPairs.ts +79 -0
  61. package/src/editor/core/palettes/resolveColor.ts +77 -0
  62. package/src/editor/core/store/editorStore.ts +4 -0
  63. package/src/editor/core/store/editorViewStore.ts +2 -2
  64. package/src/editor/core/store/unsavedEdits.ts +10 -0
  65. package/src/editor/core/themes/liveStateStream.ts +7 -1
  66. package/src/editor/core/themes/parsers/gradient.ts +5 -2
  67. package/src/editor/docs/chapters.ts +1 -0
  68. package/src/editor/docs/content/accessibility.md +362 -0
  69. package/src/editor/docs/content/editing-tokens.md +28 -1
  70. package/src/editor/docs/content.generated.ts +2 -1
  71. package/src/editor/pages/ComponentEditorPage.svelte +10 -1
  72. package/src/editor/pages/EditorShell.svelte +96 -10
  73. package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
  74. package/src/editor/skill-atlas/trees/check-compliance.ts +3 -3
  75. package/src/editor/skill-atlas/trees/create-component.ts +12 -12
  76. package/src/editor/ui/EditorViewSwitcher.svelte +15 -2
  77. package/src/editor/ui/GradientEditor.svelte +9 -1
  78. package/src/editor/ui/ThemePanel.svelte +4 -4
  79. package/src/editor/ui/UIContrastReadout.svelte +186 -0
  80. package/src/editor/ui/UIInfoPopover.svelte +16 -2
  81. package/src/editor/ui/UIPaletteSelector.svelte +9 -0
  82. package/src/editor/ui/UITokenSelector.svelte +26 -2
  83. package/src/editor/ui/a11y/A11yComponentIssues.svelte +103 -0
  84. package/src/editor/ui/a11y/A11yFindingRow.svelte +57 -0
  85. package/src/editor/ui/a11y/A11yIssueRow.svelte +121 -0
  86. package/src/editor/ui/a11y/A11yOverview.svelte +242 -0
  87. package/src/editor/ui/a11y/A11yPanel.svelte +128 -0
  88. package/src/editor/ui/a11y/A11yPropertyMark.svelte +84 -0
  89. package/src/editor/ui/a11y/A11yRailMark.svelte +34 -0
  90. package/src/editor/ui/a11y/AccessibilityTab.svelte +334 -0
  91. package/src/editor/ui/a11y/a11yView.ts +218 -0
  92. package/src/editor/ui/contrastReadout.ts +45 -0
  93. package/src/live-tokens/data/themes/autumn.json +20 -20
  94. package/src/live-tokens/data/themes/halloween.json +20 -20
  95. package/src/live-tokens/data/themes/midnight-study.json +20 -20
  96. package/src/live-tokens/data/themes/ocean.json +20 -20
  97. package/src/live-tokens/data/themes/royal-velvet.json +20 -20
  98. package/src/live-tokens/data/themes/sketchy.json +20 -20
  99. package/src/live-tokens/data/themes/spring-meadow.json +20 -20
  100. package/src/live-tokens/data/themes/sunset.json +20 -20
  101. package/src/system/components/Badge.svelte +3 -3
  102. package/src/system/components/Button.svelte +9 -3
  103. package/src/system/components/CodeSnippet.svelte +8 -1
  104. package/src/system/components/CornerBadge.svelte +3 -3
  105. package/src/system/components/Dialog.svelte +43 -14
  106. package/src/system/components/IconButton.svelte +1 -1
  107. package/src/system/components/Image.svelte +7 -0
  108. package/src/system/components/ImageLightbox.svelte +18 -25
  109. package/src/system/components/Input.svelte +8 -1
  110. package/src/system/components/MenuSelect.svelte +30 -2
  111. package/src/system/components/Notification.svelte +1 -1
  112. package/src/system/components/ProgressBar.svelte +6 -0
  113. package/src/system/components/RadioButton.svelte +36 -3
  114. package/src/system/components/SegmentedControl.svelte +36 -2
  115. package/src/system/components/SlidePager.svelte +2 -2
  116. package/src/system/components/Slider.svelte +13 -9
  117. package/src/system/components/TabBar.svelte +33 -3
  118. package/src/system/components/Toggle.svelte +2 -2
  119. package/src/system/components/VideoFrame.svelte +1 -1
  120. package/src/system/components/VideoLightbox.svelte +2 -2
  121. package/src/system/internal/focusTrap.ts +35 -0
  122. package/src/testing-js/__aria__/badge.yml +40 -0
  123. package/src/testing-js/__aria__/button.yml +88 -0
  124. package/src/testing-js/__aria__/callout.yml +12 -0
  125. package/src/testing-js/__aria__/card.yml +8 -0
  126. package/src/testing-js/__aria__/codesnippet.yml +21 -0
  127. package/src/testing-js/__aria__/collapsiblesection.yml +23 -0
  128. package/src/testing-js/__aria__/cornerbadge.yml +24 -0
  129. package/src/testing-js/__aria__/dialog.yml +42 -0
  130. package/src/testing-js/__aria__/iconbutton.yml +57 -0
  131. package/src/testing-js/__aria__/image.yml +10 -0
  132. package/src/testing-js/__aria__/imagelightbox.yml +50 -0
  133. package/src/testing-js/__aria__/inlineeditactions.yml +16 -0
  134. package/src/testing-js/__aria__/input.yml +61 -0
  135. package/src/testing-js/__aria__/menuselect.yml +30 -0
  136. package/src/testing-js/__aria__/notification.yml +30 -0
  137. package/src/testing-js/__aria__/panel.yml +2 -0
  138. package/src/testing-js/__aria__/progressbar.yml +2 -0
  139. package/src/testing-js/__aria__/radiobutton.yml +20 -0
  140. package/src/testing-js/__aria__/sectiondivider.yml +9 -0
  141. package/src/testing-js/__aria__/segmentedcontrol.yml +35 -0
  142. package/src/testing-js/__aria__/sidenavigation.yml +304 -0
  143. package/src/testing-js/__aria__/slidepager.yml +72 -0
  144. package/src/testing-js/__aria__/slider.yml +28 -0
  145. package/src/testing-js/__aria__/tabbar.yml +35 -0
  146. package/src/testing-js/__aria__/table.yml +144 -0
  147. package/src/testing-js/__aria__/toggle.yml +12 -0
  148. package/src/testing-js/__aria__/tooltip.yml +2 -0
  149. package/src/testing-js/__aria__/videoframe.yml +6 -0
  150. package/src/testing-js/__aria__/videolightbox.yml +12 -0
  151. package/src/testing-js/{chunk-WIZ6W7UT.js → chunk-3WCLMOM7.js} +2 -2
  152. package/src/testing-js/{chunk-SHBYZUPD.js → chunk-5HNZHIHG.js} +211 -14
  153. package/src/testing-js/chunk-5HNZHIHG.js.map +1 -0
  154. package/src/testing-js/{chunk-NB3NZRBM.js → chunk-7CM5D7PF.js} +5 -1
  155. package/src/testing-js/chunk-7CM5D7PF.js.map +1 -0
  156. package/src/testing-js/chunk-EWSGE27V.js +168 -0
  157. package/src/testing-js/chunk-EWSGE27V.js.map +1 -0
  158. package/src/testing-js/{chunk-GKO27NGN.js → chunk-R54I7YL6.js} +2 -2
  159. package/src/testing-js/{chunk-X6KHEVIG.js → chunk-RWH46IU5.js} +97 -29
  160. package/src/testing-js/chunk-RWH46IU5.js.map +1 -0
  161. package/src/testing-js/chunk-SHLPDNAQ.js +658 -0
  162. package/src/testing-js/chunk-SHLPDNAQ.js.map +1 -0
  163. package/src/testing-js/{chunk-3UKGXCDL.js → chunk-YVVR3GJZ.js} +1 -1
  164. package/src/testing-js/chunk-YVVR3GJZ.js.map +1 -0
  165. package/src/testing-js/component-a11y.contract.js +283 -0
  166. package/src/testing-js/component-a11y.contract.js.map +1 -0
  167. package/src/testing-js/component-alias.contract.js +2 -2
  168. package/src/testing-js/component-behavior.contract.js +2 -2
  169. package/src/testing-js/component-editor.contract.js +6 -5
  170. package/src/testing-js/component-editor.contract.js.map +1 -1
  171. package/src/testing-js/component-render.contract.js +10 -6
  172. package/src/testing-js/component-render.contract.js.map +1 -1
  173. package/src/testing-js/index.d.ts +44 -16
  174. package/src/testing-js/index.js +9 -9
  175. package/src/testing-js/page-compliance.contract.js +132 -13
  176. package/src/testing-js/page-compliance.contract.js.map +1 -1
  177. package/src/testing-js/vitest.js +3 -3
  178. package/template/package.json +9 -0
  179. package/src/testing-js/chunk-3UKGXCDL.js.map +0 -1
  180. package/src/testing-js/chunk-NB3NZRBM.js.map +0 -1
  181. package/src/testing-js/chunk-SHBYZUPD.js.map +0 -1
  182. package/src/testing-js/chunk-X6KHEVIG.js.map +0 -1
  183. /package/src/testing-js/{chunk-WIZ6W7UT.js.map → chunk-3WCLMOM7.js.map} +0 -0
  184. /package/src/testing-js/{chunk-GKO27NGN.js.map → chunk-R54I7YL6.js.map} +0 -0
@@ -0,0 +1,362 @@
1
+ # Accessibility
2
+
3
+ `check-component --tests` and `check-page --tests` test the shipped
4
+ components, your own components, and your pages against WCAG 2 A and AA. Every
5
+ accessibility finding reports at `warn`: the run prints it and leaves the exit
6
+ status to the other rules. `--strict` turns each one into an error that fails
7
+ the run, as it does every other warning.
8
+
9
+ ```bash
10
+ npx live-tokens check-component statcard --tests
11
+ npx live-tokens check-page src/pages/Home.svelte --tests --strict
12
+ ```
13
+
14
+ ## Suites and checks
15
+
16
+ **Components.** The component suite opens every variant and state the
17
+ component's contract names and scans the preview:
18
+
19
+ - **axe-core** checks names, roles, ARIA, contrast, target size, and
20
+ structure.
21
+ - **Siteimprove Alfa**, rule SIA-R65, checks that each focusable element
22
+ looks different when it takes focus.
23
+ - **Focus indicator contrast** measures each focus ring against the backdrop
24
+ around it, at 3:1 under WCAG 2.2 SC 1.4.11.
25
+ - **Control boundary contrast** measures each part the contract lists in
26
+ `boundaries` against the backdrop behind it, at 3:1 under WCAG 2.2
27
+ SC 1.4.11.
28
+ - **Text spacing** applies the WCAG 2.2 SC 1.4.12 override inside the
29
+ component and reports text that clips under it.
30
+ - **Reduced motion** reports motion longer than 200 ms that still runs under
31
+ `prefers-reduced-motion: reduce`.
32
+ - **Playwright** reads each view's accessibility tree and compares it with
33
+ the tree recorded for that view.
34
+ - **Keyboard cases** in the contract press Enter, Space, the arrow keys, and
35
+ Escape, and read what each key did.
36
+
37
+ A project's run scans the open theme. The library's own test run also repeats
38
+ axe's contrast rule, and both contrast checks, under each of the eight preset
39
+ themes.
40
+
41
+ **Pages.** `check-page --tests` runs three tests on each page at each
42
+ viewport:
43
+
44
+ - `page-a11y` runs axe over the page, leaving out the router's chrome and
45
+ every component the component suite scans. axe's contrast rule stays off here, because
46
+ `page-contrast` already reports text contrast at `error`.
47
+ - `page-text-spacing` applies the WCAG 2.2 SC 1.4.12 text spacing override
48
+ (line height 1.5, paragraph spacing 2em, letter spacing 0.12em, word
49
+ spacing 0.16em) and reports content that overflows under it.
50
+ - `page-reduced-motion` loads the page under `prefers-reduced-motion: reduce`
51
+ and reports animations that keep running.
52
+
53
+ ## Rules
54
+
55
+ | Rule | Checker | Reports |
56
+ |------|---------|---------|
57
+ | `a11y-component` | `check-component` | An axe failure inside a component's preview, a focus ring or control boundary below 3:1, or a changed accessibility tree. |
58
+ | `a11y-focus-visible` | `check-component` | An element that looks the same focused as at rest (SIA-R65). |
59
+ | `a11y-keyboard` | `check-component` | A keyboard case whose key failed to activate, move, or close. |
60
+ | `a11y-page` | `check-page` | An axe failure in the page's own markup. |
61
+ | `a11y-text-spacing` | `check-component`, `check-page` | Content that overflows under the text spacing override. |
62
+ | `a11y-reduced-motion` | `check-component`, `check-page` | Motion that still runs under reduced motion. |
63
+ | `a11y-review` | both | A check the engine could not decide, for you to judge by eye. |
64
+
65
+ Each finding's message names the engine's own rule, the element, the view it
66
+ failed in, and a help page that states the requirement and its repair. The
67
+ rules take the same severity settings as every other rule. `--off=a11y-review`
68
+ silences one for a single run, `--error=a11y-page` raises one, and
69
+ `live-tokens.config.json` records either for good:
70
+
71
+ ```json
72
+ { "checks": { "rules": { "a11y-review": "off", "a11y-page": "error" } } }
73
+ ```
74
+
75
+ A scan that crashes reports `tests-setup`.
76
+
77
+ ## Focus indicator contrast
78
+
79
+ The suite forces `:focus` and `:focus-visible` on each focusable element in
80
+ each view and looks for the style focus changes. It searches the element, its
81
+ descendants, and its siblings with their descendants, so a ring painted on a
82
+ sibling, such as a slider thumb's cap, counts. The indicator is the outline
83
+ when focus changes one, else the first outer box shadow, else a border side.
84
+
85
+ The backdrop is every box behind the ring's outer edge, composited in order
86
+ over white. A ring below 3:1 against it reports under `a11y-component` with
87
+ engine rule `focus-indicator-contrast`, such as "Focus outline on span.cap
88
+ 2.14:1 against the backdrop, needs 3:1". Ratios floor to two places, because
89
+ WCAG does not round. Chromium's own `outline-style: auto` ring passes: it pairs
90
+ a dark band with a light one. An element with no indicator at all is
91
+ SIA-R65's finding and adds none here.
92
+
93
+ ## Control boundary contrast
94
+
95
+ A control whose edge shows where to click or what it holds needs that edge
96
+ at 3:1 against the backdrop behind it. So does a selected or current item
97
+ whose surface or edge alone shows that state. The contract lists these parts
98
+ in `boundaries`. Each part passes when its border, on any side, or its
99
+ outline, 1px or wider, or its surface reaches 3:1. A failure names both ratios, such as "Track border
100
+ 1.45:1 and surface 1.3:1 against the backdrop, needs 3:1", and reports under
101
+ `a11y-component` with engine rule `control-boundary-contrast`. A part whose
102
+ form controls are all disabled is skipped, because SC 1.4.11 exempts inactive
103
+ controls.
104
+
105
+ The shipped controls that draw their own edge declare it: Input's field,
106
+ RadioButton's circle, Slider's and Toggle's track and thumb, and
107
+ SegmentedControl's bar. The selected states that show by surface and edge
108
+ alone declare their part too: TabBar's selected tab, SegmentedControl's
109
+ selected option, and SideNavigation's selected title, section, item and
110
+ footer. Each measures against the bar or panel behind it, the surface the
111
+ unselected items show. MenuSelect's selected option carries a check mark, so
112
+ it declares none.
113
+
114
+ ## Text spacing and motion per component
115
+
116
+ The text spacing check measures every element of the component that holds
117
+ text: its root part, its portaled parts, and their descendants. It applies
118
+ the override inside the preview alone and reports each element the override
119
+ pushes more than 1px past its box, on an axis it does not scroll. An element
120
+ already cut at rest adds nothing. The help names the measure: "Under the text
121
+ spacing override, `<span>` holds 24px of content in 18px of height, and its
122
+ overflow-y is hidden".
123
+
124
+ The reduced motion check reads the declared animations and transitions of
125
+ the same elements and their `::before` and `::after`, along with every
126
+ script animation running on them. Motion that lasts past 200 ms, its delay
127
+ included, is recorded. The check then emulates
128
+ `prefers-reduced-motion: reduce` in the same view and reports each motion
129
+ still that long, such as "A transition of left lasts 500 ms under
130
+ prefers-reduced-motion: reduce". A view with no long motion costs no second
131
+ read. The help page is WCAG technique C39, which serves WCAG 2.2 SC 2.3.3
132
+ Animation from Interactions.
133
+
134
+ ## Property tracing
135
+
136
+ A finding that a property can fix names that property. The suite reads the
137
+ CSS rules that match the failing element through the Chrome DevTools
138
+ protocol, keeps the declarations the finding depends on, such as the outline
139
+ and border of a focus ring or the height and padding of a clipped label, and
140
+ follows each `var()` to the component's semantic properties and intrinsics.
141
+ A component's private custom properties lead through to the property behind
142
+ them. Theme and editor variables drop out.
143
+
144
+ `--json` output carries the result in each finding's `details.variables`,
145
+ sorted, and leaves the field out when the trace finds nothing. An excerpt:
146
+
147
+ ```json
148
+ {
149
+ "rule": "a11y-component",
150
+ "component": "slider",
151
+ "details": {
152
+ "engineRule": "focus-indicator-contrast",
153
+ "target": "span.cap",
154
+ "variables": ["--slider-range-thumb-border", "--slider-range-thumb-border-width"]
155
+ }
156
+ }
157
+ ```
158
+
159
+ axe's contrast and target size rules, SIA-R65, both contrast checks, text
160
+ spacing, and reduced motion trace. The ARIA rules, the keyboard cases, and
161
+ the accessibility tree mark the component alone.
162
+
163
+ ## Review findings
164
+
165
+ `a11y-review` collects the checks an engine left undecided. axe reads contrast
166
+ from the colour behind the text, so it returns no verdict for text over a
167
+ gradient, an image, or a backdrop, or under a pseudo-element that covers it.
168
+ The focus indicator and control boundary checks do the same for a ring or an
169
+ edge over a gradient or an image. Judge each element by eye against the help
170
+ page the finding names.
171
+
172
+ Two sources account for most of them. Button's shimmer pseudo-element and its
173
+ tinted hover surfaces leave every Button view undecided, so a Button run lists
174
+ many `a11y-review` warnings. A theme that paints gradient surfaces does the
175
+ same for every component that sits on one.
176
+
177
+ ## Accessibility trees
178
+
179
+ The package records each shipped component's accessibility tree, one block
180
+ per view, in `src/testing/contracts/__aria__/<id>.yml`. A run that reads a
181
+ different tree reports `a11y-component` with engine rule `aria-snapshot` and
182
+ the lines that changed.
183
+
184
+ Inside the live-tokens repository, after a deliberate markup change,
185
+ `--update-aria` records the tree anew, and `LIVE_TOKENS_UPDATE_ARIA=1 npm run
186
+ test:e2e:contract` rewrites every file:
187
+
188
+ ```bash
189
+ node bin/cli.mjs check-component button --tests --update-aria
190
+ ```
191
+
192
+ In a project the trees sit under `node_modules`, where the suite reads them
193
+ and never writes. A project's own components therefore have no recorded tree
194
+ yet.
195
+
196
+ ## Keyboard cases
197
+
198
+ A component contract declares its keyboard cases among its interaction
199
+ cases, each marked `rule: 'a11y-keyboard'`: Enter or Space activates, an
200
+ arrow key moves where the component's WAI-ARIA pattern moves, and Escape
201
+ closes an overlay. A failed keyboard case reports `a11y-keyboard` at `warn`.
202
+ A case without the mark fails as `contract-interaction`, an error. The
203
+ `live-tokens-create-component` skill writes these cases for a new component,
204
+ and its `references/contract-tests.md` describes their shape.
205
+
206
+ An editor that adds placeholder content to its preview, such as a demo label
207
+ or a hover target, marks it `data-preview-demo`, and the scans skip it.
208
+
209
+ ## Boundaries in the contract
210
+
211
+ `boundaries` is an optional contract field,
212
+ `{ part: string; label: string; when?: string }[]`, for the parts that draw a
213
+ control's visible edge or show its selected state. `part` is a key of the
214
+ contract's `parts`, and `label` names the part in a finding. `when` is a
215
+ selector the part must also match to be measured, for a part that carries its
216
+ selected state as a class in some views only.
217
+
218
+ ```ts
219
+ boundaries: [
220
+ { part: 'track', label: 'Track' },
221
+ { part: 'thumb', label: 'Thumb' },
222
+ ],
223
+ ```
224
+
225
+ List a part whose border or surface is the only thing that shows the control
226
+ is there, a thumb whose position shows the value, or a selected item whose
227
+ surface or edge is the only thing that shows it is selected. Leave out a part whose
228
+ text or icon already shows it. A contract without the field runs no boundary
229
+ check.
230
+
231
+ ## Accessibility view
232
+
233
+ The editor's Accessibility view runs the component checks from the browser.
234
+ **Run test** checks every component in the open theme as saved, in about
235
+ three and a half minutes. With edits pending, it shows "Save the changes you
236
+ want to have checked." and starts nothing. Save the theme first, so the run
237
+ and the editor read the same values.
238
+
239
+ Every finding counts, results to check by eye included. An overview opens
240
+ the report. It shows the total, the count in each repair group below, and a
241
+ table of every component with a finding: its count per group and a bar of
242
+ its share. A component's name in the table scrolls to its section.
243
+
244
+ Each component's section heads its count with **Open component**, which opens
245
+ the component's page at its accessibility panel. The section sorts its rows
246
+ by repair:
247
+
248
+ - **Fix with a property** holds each colour pair below its WCAG floor, as the
249
+ editor's contrast checker measures it, such as "Brand text color 3.87:1,
250
+ needs 4.5:1", and every focus ring and control boundary below 3:1.
251
+ - **Fix in the component's code** holds every other finding: axe failures,
252
+ focus styles that SIA-R65 finds unchanged, failed keyboard cases, text that
253
+ clips under the spacing override, and motion that ignores reduced motion.
254
+ - **Check by eye** holds the `a11y-review` results.
255
+
256
+ Findings that share an engine rule, a help text, and their traced properties
257
+ merge into one row. The row names its element, or its element count, and the
258
+ views it failed in, up to four. An issue's text is amber, and a result to
259
+ check by eye is grey. The row's links sit beside its text:
260
+
261
+ - Each property the row traces to links with a "→" to its row on the
262
+ component's page, which scrolls to the row and highlights it. A row that
263
+ traces to no property links to the component's page instead.
264
+ - The link on the right opens the page the finding cites and names its kind:
265
+ "Rule" for an axe or Alfa rule, "Success criterion" with its number for a
266
+ WCAG criterion, "Technique" for a WCAG technique, and "Pattern" for a
267
+ WAI-ARIA pattern.
268
+
269
+ A property's label leads with the words from its variable name that the
270
+ editor's label leaves out, so `--input-focused-border` reads "Focused border
271
+ color" where the editor's row reads "Border color". The contrast rows read
272
+ the same labels.
273
+
274
+ The overview and the per-component counts include the results to check by
275
+ eye. A component's section ends in a "N results to check by eye" button that
276
+ opens one closed list of those results, merged per component. axe's contrast
277
+ violations stay out of the report, because the contrast checker measures the
278
+ same pairs.
279
+
280
+ When Playwright, axe, or Alfa is missing, the report names its install
281
+ command and Run test stays disabled. Install the package and press **Check
282
+ again**. The view runs its tests through the dev server, so it exists under
283
+ the dev server alone, with the rest of the editor.
284
+
285
+ The view runs `check-component --tests --a11y --no-fix --json --open-theme`.
286
+ `--a11y` runs the accessibility suite and the keyboard cases alone, and
287
+ `--open-theme` tests the theme the editor has open. A run without it tests
288
+ the `default` theme. Both work from the command line too.
289
+
290
+ ## Marks in the editor
291
+
292
+ The last finished run marks the rest of the editor:
293
+
294
+ - The component list leads the name of each component with any finding
295
+ with a warning triangle. A condensed list shows the triangle on the
296
+ component's icon.
297
+ - That component's page opens with a panel under its title that lists its
298
+ findings in all three groups, check by eye included.
299
+ - Each "→" in the panel opens the tab holding the property and highlights
300
+ its row.
301
+ - Each property row a finding traces to carries a mark beside its label: a
302
+ warning triangle in the highlight colour, or a grey "?" when every finding on the row is a
303
+ result to check by eye. On a colour or size row the mark sits between the
304
+ label and the value. On a toggle row it follows the label.
305
+
306
+ Click a row's mark to open its findings: the count, then each merged row
307
+ with its element, its views, its help page, and the properties it traces to. The
308
+ editor's own contrast failures add no row mark, because the contrast readout
309
+ on the colour row already shows them.
310
+
311
+ The marks and the panel keep the run's time and change when the next run
312
+ finishes. The contrast readout on each colour row stays live and measures
313
+ every edit as you make it.
314
+
315
+ ## Installing the engines
316
+
317
+ The engines ship as optional peer dependencies. A project made with `create`
318
+ has them. Any other project installs them beside `@playwright/test`:
319
+
320
+ ```bash
321
+ npm install -D axe-core @axe-core/playwright \
322
+ @siteimprove/alfa-act@^0.119.0 @siteimprove/alfa-device@^0.119.0 \
323
+ @siteimprove/alfa-dom@^0.119.0 @siteimprove/alfa-http@^0.119.0 \
324
+ @siteimprove/alfa-rules@^0.119.0 @siteimprove/alfa-url@^0.119.0 \
325
+ @siteimprove/alfa-web@^0.119.0
326
+ ```
327
+
328
+ The Alfa packages share one minor version. axe-core needs 4.11.1 or later to
329
+ read OKLCH colours, which every shipped theme uses. A test whose engine is
330
+ missing reports as inapplicable, and its coverage names the package. A
331
+ missing engine leaves the exit status to the other rules, under `--strict`
332
+ too.
333
+
334
+ ## Limits
335
+
336
+ - The page's text spacing test measures widths, so it reports horizontal
337
+ overflow alone. The component check measures both axes.
338
+ - The page's reduced motion test reports animations still running 200 ms
339
+ after the page loads. A short entrance animation finishes first and goes
340
+ unseen.
341
+ - The component's reduced motion check reads the preference within each
342
+ view. A script that reads it only when the component mounts goes unseen.
343
+ - The page scan leaves contrast to `page-contrast` and focus visibility to
344
+ the component suite.
345
+ - SIA-R65 reads the focused element, its descendants, and its ancestors, so
346
+ it reports a focus ring painted on a sibling as missing. The focus
347
+ indicator check measures that ring; confirm it by eye.
348
+ - A view that forces the focused look at rest, such as Input's focused
349
+ state, leaves real focus nothing to change, so SIA-R65 reports it there.
350
+ The same element passes in the views that leave it at rest.
351
+ - A finding traces to the component's own semantic properties and
352
+ intrinsics. A ring or colour that a theme token paints directly, such as
353
+ SegmentedControl's `--border-neutral-strong` focus ring, traces to
354
+ nothing. A finding on a component nested
355
+ inside another, such as a Button inside a Dialog, marks the host and names
356
+ none of the nested component's properties.
357
+ - A literal value in the component's code, such as a transition's duration,
358
+ has no property to name. The finding traces to whatever property governs
359
+ the same declaration, or to none.
360
+ - A project's own components get no recorded accessibility tree.
361
+ - Automated checks find part of what WCAG asks. Reading order, the meaning of
362
+ labels and alt text, and screen reader behaviour still need a person.
@@ -3,7 +3,7 @@
3
3
  A tour of the editor. The page behind it repaints on every change; saving
4
4
  writes a theme file you can reload later.
5
5
 
6
- The editor has four views:
6
+ The editor has five views:
7
7
 
8
8
  - **Tokens**: the design-system primitives (colour, type, spacing, and so on).
9
9
  They apply everywhere your site uses them.
@@ -13,6 +13,8 @@ The editor has four views:
13
13
  without changing the underlying system.
14
14
  - **Sketchstyle**: an effect layer that redraws the page by hand. See
15
15
  [Sketch mode](sketch-mode.md).
16
+ - **Accessibility**: Run test checks every component in the open theme as
17
+ saved and lists each issue it finds by component.
16
18
 
17
19
  This page covers **Tokens**. For components, see
18
20
  [Creating components](creating-components.md).
@@ -102,6 +104,31 @@ and the grid takes the tablet values at 767px and narrower and the phone values
102
104
  at 480px and narrower. The columns button in the editor bar draws the grid over
103
105
  the page, and a page on the grid reflows as you edit.
104
106
 
107
+ ## Contrast readout
108
+
109
+ In the **Components** view, every colour row that paints text, an icon, or
110
+ another foreground mark shows its WCAG contrast ratio beside the colour's name.
111
+ The readout measures the colour against the layers the component paints behind
112
+ it. Each edit and each theme load updates it, and the rows keep their places.
113
+
114
+ - **Value.** The ratio shows on every foreground row, in grey while it meets
115
+ its floor. It truncates to two decimals, so 4.499 reads `4.49:1`, below the
116
+ 4.5:1 floor. Text needs 4.5:1, or 3:1 at 24px or 18.66px bold. An icon or
117
+ another mark needs 3:1.
118
+ - **Amber mark.** A ratio below its floor turns amber and gains an exclamation
119
+ mark.
120
+ - **Popover.** Click the ratio to open it. For each pair the colour forms, it
121
+ names the WCAG rule and its floor, the measured ratio, and the layers behind
122
+ the colour, top first, with a link to the success criterion. A colour over
123
+ several backgrounds forms one pair per background, and the row shows the pair
124
+ closest to failing. Notification's body colour paints the dismiss icon on the
125
+ header and the body text on the page, so its popover lists two pairs.
126
+ - **Disabled states.** WCAG exempts disabled controls, so a disabled state's
127
+ ratio shows in a dimmer grey, whatever its value.
128
+ - **Page background.** Every stack ends on the page background, `--page-bg`.
129
+ A translucent layer blends into the layers beneath it, and the readout
130
+ measures the blend. A gradient layer counts its worst stop.
131
+
105
132
  ## Saving
106
133
 
107
134
  The editor saves to your browser continuously, so work survives a reload
@@ -3,8 +3,9 @@
3
3
 
4
4
  export const docContent: Record<string, string> = {
5
5
  "01-overview": "# Overview\n\nLiveTokens is a design system for building Svelte microsites quickly. You\nstyle your site by editing tokens and components in a live editor. When it looks right, you save the theme and ship it.\n\n## How it works\n\n- The editor runs in your dev server, on top of your real pages. You style in\n context, not in a separate sandbox.\n- Every change updates a CSS variable, so the page repaints instantly. No\n reload, no build step.\n- Saving writes a small JSON file into your project. Shipping bakes your chosen\n theme into a plain CSS file that the build bundles.\n- The editor is dev-only. Production ships plain CSS variables and the\n components you used, nothing else.\n\n## What you can edit\n\n- **Tokens**: the design-system primitives, colour palettes, type, spacing,\n radius, shadow, and gradients, that apply across your whole site.\n- **Components**: the package ships about 25 editable components (Button,\n IconButton, Card, Dialog, Table, and more). You style components by changing\n the tokens assigned to each property.\n\n## Where to go next\n\n- **[Getting started](getting-started.md)**: scaffold a project and make your\n first edit.\n- **[Editing tokens](editing-tokens.md)**: a tour of the editor.\n- **[Sketch mode](sketch-mode.md)**: redraw the page by hand.\n- **[Themes](themes-workflow.md)**: save, switch, and ship.\n- **[Creating components](creating-components.md)**: make your own components\n editable.\n",
6
+ "accessibility": "# Accessibility\n\n`check-component --tests` and `check-page --tests` test the shipped\ncomponents, your own components, and your pages against WCAG 2 A and AA. Every\naccessibility finding reports at `warn`: the run prints it and leaves the exit\nstatus to the other rules. `--strict` turns each one into an error that fails\nthe run, as it does every other warning.\n\n```bash\nnpx live-tokens check-component statcard --tests\nnpx live-tokens check-page src/pages/Home.svelte --tests --strict\n```\n\n## Suites and checks\n\n**Components.** The component suite opens every variant and state the\ncomponent's contract names and scans the preview:\n\n- **axe-core** checks names, roles, ARIA, contrast, target size, and\n structure.\n- **Siteimprove Alfa**, rule SIA-R65, checks that each focusable element\n looks different when it takes focus.\n- **Focus indicator contrast** measures each focus ring against the backdrop\n around it, at 3:1 under WCAG 2.2 SC 1.4.11.\n- **Control boundary contrast** measures each part the contract lists in\n `boundaries` against the backdrop behind it, at 3:1 under WCAG 2.2\n SC 1.4.11.\n- **Text spacing** applies the WCAG 2.2 SC 1.4.12 override inside the\n component and reports text that clips under it.\n- **Reduced motion** reports motion longer than 200 ms that still runs under\n `prefers-reduced-motion: reduce`.\n- **Playwright** reads each view's accessibility tree and compares it with\n the tree recorded for that view.\n- **Keyboard cases** in the contract press Enter, Space, the arrow keys, and\n Escape, and read what each key did.\n\nA project's run scans the open theme. The library's own test run also repeats\naxe's contrast rule, and both contrast checks, under each of the eight preset\nthemes.\n\n**Pages.** `check-page --tests` runs three tests on each page at each\nviewport:\n\n- `page-a11y` runs axe over the page, leaving out the router's chrome and\n every component the component suite scans. axe's contrast rule stays off here, because\n `page-contrast` already reports text contrast at `error`.\n- `page-text-spacing` applies the WCAG 2.2 SC 1.4.12 text spacing override\n (line height 1.5, paragraph spacing 2em, letter spacing 0.12em, word\n spacing 0.16em) and reports content that overflows under it.\n- `page-reduced-motion` loads the page under `prefers-reduced-motion: reduce`\n and reports animations that keep running.\n\n## Rules\n\n| Rule | Checker | Reports |\n|------|---------|---------|\n| `a11y-component` | `check-component` | An axe failure inside a component's preview, a focus ring or control boundary below 3:1, or a changed accessibility tree. |\n| `a11y-focus-visible` | `check-component` | An element that looks the same focused as at rest (SIA-R65). |\n| `a11y-keyboard` | `check-component` | A keyboard case whose key failed to activate, move, or close. |\n| `a11y-page` | `check-page` | An axe failure in the page's own markup. |\n| `a11y-text-spacing` | `check-component`, `check-page` | Content that overflows under the text spacing override. |\n| `a11y-reduced-motion` | `check-component`, `check-page` | Motion that still runs under reduced motion. |\n| `a11y-review` | both | A check the engine could not decide, for you to judge by eye. |\n\nEach finding's message names the engine's own rule, the element, the view it\nfailed in, and a help page that states the requirement and its repair. The\nrules take the same severity settings as every other rule. `--off=a11y-review`\nsilences one for a single run, `--error=a11y-page` raises one, and\n`live-tokens.config.json` records either for good:\n\n```json\n{ \"checks\": { \"rules\": { \"a11y-review\": \"off\", \"a11y-page\": \"error\" } } }\n```\n\nA scan that crashes reports `tests-setup`.\n\n## Focus indicator contrast\n\nThe suite forces `:focus` and `:focus-visible` on each focusable element in\neach view and looks for the style focus changes. It searches the element, its\ndescendants, and its siblings with their descendants, so a ring painted on a\nsibling, such as a slider thumb's cap, counts. The indicator is the outline\nwhen focus changes one, else the first outer box shadow, else a border side.\n\nThe backdrop is every box behind the ring's outer edge, composited in order\nover white. A ring below 3:1 against it reports under `a11y-component` with\nengine rule `focus-indicator-contrast`, such as \"Focus outline on span.cap\n2.14:1 against the backdrop, needs 3:1\". Ratios floor to two places, because\nWCAG does not round. Chromium's own `outline-style: auto` ring passes: it pairs\na dark band with a light one. An element with no indicator at all is\nSIA-R65's finding and adds none here.\n\n## Control boundary contrast\n\nA control whose edge shows where to click or what it holds needs that edge\nat 3:1 against the backdrop behind it. So does a selected or current item\nwhose surface or edge alone shows that state. The contract lists these parts\nin `boundaries`. Each part passes when its border, on any side, or its\noutline, 1px or wider, or its surface reaches 3:1. A failure names both ratios, such as \"Track border\n1.45:1 and surface 1.3:1 against the backdrop, needs 3:1\", and reports under\n`a11y-component` with engine rule `control-boundary-contrast`. A part whose\nform controls are all disabled is skipped, because SC 1.4.11 exempts inactive\ncontrols.\n\nThe shipped controls that draw their own edge declare it: Input's field,\nRadioButton's circle, Slider's and Toggle's track and thumb, and\nSegmentedControl's bar. The selected states that show by surface and edge\nalone declare their part too: TabBar's selected tab, SegmentedControl's\nselected option, and SideNavigation's selected title, section, item and\nfooter. Each measures against the bar or panel behind it, the surface the\nunselected items show. MenuSelect's selected option carries a check mark, so\nit declares none.\n\n## Text spacing and motion per component\n\nThe text spacing check measures every element of the component that holds\ntext: its root part, its portaled parts, and their descendants. It applies\nthe override inside the preview alone and reports each element the override\npushes more than 1px past its box, on an axis it does not scroll. An element\nalready cut at rest adds nothing. The help names the measure: \"Under the text\nspacing override, `<span>` holds 24px of content in 18px of height, and its\noverflow-y is hidden\".\n\nThe reduced motion check reads the declared animations and transitions of\nthe same elements and their `::before` and `::after`, along with every\nscript animation running on them. Motion that lasts past 200 ms, its delay\nincluded, is recorded. The check then emulates\n`prefers-reduced-motion: reduce` in the same view and reports each motion\nstill that long, such as \"A transition of left lasts 500 ms under\nprefers-reduced-motion: reduce\". A view with no long motion costs no second\nread. The help page is WCAG technique C39, which serves WCAG 2.2 SC 2.3.3\nAnimation from Interactions.\n\n## Property tracing\n\nA finding that a property can fix names that property. The suite reads the\nCSS rules that match the failing element through the Chrome DevTools\nprotocol, keeps the declarations the finding depends on, such as the outline\nand border of a focus ring or the height and padding of a clipped label, and\nfollows each `var()` to the component's semantic properties and intrinsics.\nA component's private custom properties lead through to the property behind\nthem. Theme and editor variables drop out.\n\n`--json` output carries the result in each finding's `details.variables`,\nsorted, and leaves the field out when the trace finds nothing. An excerpt:\n\n```json\n{\n \"rule\": \"a11y-component\",\n \"component\": \"slider\",\n \"details\": {\n \"engineRule\": \"focus-indicator-contrast\",\n \"target\": \"span.cap\",\n \"variables\": [\"--slider-range-thumb-border\", \"--slider-range-thumb-border-width\"]\n }\n}\n```\n\naxe's contrast and target size rules, SIA-R65, both contrast checks, text\nspacing, and reduced motion trace. The ARIA rules, the keyboard cases, and\nthe accessibility tree mark the component alone.\n\n## Review findings\n\n`a11y-review` collects the checks an engine left undecided. axe reads contrast\nfrom the colour behind the text, so it returns no verdict for text over a\ngradient, an image, or a backdrop, or under a pseudo-element that covers it.\nThe focus indicator and control boundary checks do the same for a ring or an\nedge over a gradient or an image. Judge each element by eye against the help\npage the finding names.\n\nTwo sources account for most of them. Button's shimmer pseudo-element and its\ntinted hover surfaces leave every Button view undecided, so a Button run lists\nmany `a11y-review` warnings. A theme that paints gradient surfaces does the\nsame for every component that sits on one.\n\n## Accessibility trees\n\nThe package records each shipped component's accessibility tree, one block\nper view, in `src/testing/contracts/__aria__/<id>.yml`. A run that reads a\ndifferent tree reports `a11y-component` with engine rule `aria-snapshot` and\nthe lines that changed.\n\nInside the live-tokens repository, after a deliberate markup change,\n`--update-aria` records the tree anew, and `LIVE_TOKENS_UPDATE_ARIA=1 npm run\ntest:e2e:contract` rewrites every file:\n\n```bash\nnode bin/cli.mjs check-component button --tests --update-aria\n```\n\nIn a project the trees sit under `node_modules`, where the suite reads them\nand never writes. A project's own components therefore have no recorded tree\nyet.\n\n## Keyboard cases\n\nA component contract declares its keyboard cases among its interaction\ncases, each marked `rule: 'a11y-keyboard'`: Enter or Space activates, an\narrow key moves where the component's WAI-ARIA pattern moves, and Escape\ncloses an overlay. A failed keyboard case reports `a11y-keyboard` at `warn`.\nA case without the mark fails as `contract-interaction`, an error. The\n`live-tokens-create-component` skill writes these cases for a new component,\nand its `references/contract-tests.md` describes their shape.\n\nAn editor that adds placeholder content to its preview, such as a demo label\nor a hover target, marks it `data-preview-demo`, and the scans skip it.\n\n## Boundaries in the contract\n\n`boundaries` is an optional contract field,\n`{ part: string; label: string; when?: string }[]`, for the parts that draw a\ncontrol's visible edge or show its selected state. `part` is a key of the\ncontract's `parts`, and `label` names the part in a finding. `when` is a\nselector the part must also match to be measured, for a part that carries its\nselected state as a class in some views only.\n\n```ts\nboundaries: [\n { part: 'track', label: 'Track' },\n { part: 'thumb', label: 'Thumb' },\n],\n```\n\nList a part whose border or surface is the only thing that shows the control\nis there, a thumb whose position shows the value, or a selected item whose\nsurface or edge is the only thing that shows it is selected. Leave out a part whose\ntext or icon already shows it. A contract without the field runs no boundary\ncheck.\n\n## Accessibility view\n\nThe editor's Accessibility view runs the component checks from the browser.\n**Run test** checks every component in the open theme as saved, in about\nthree and a half minutes. With edits pending, it shows \"Save the changes you\nwant to have checked.\" and starts nothing. Save the theme first, so the run\nand the editor read the same values.\n\nEvery finding counts, results to check by eye included. An overview opens\nthe report. It shows the total, the count in each repair group below, and a\ntable of every component with a finding: its count per group and a bar of\nits share. A component's name in the table scrolls to its section.\n\nEach component's section heads its count with **Open component**, which opens\nthe component's page at its accessibility panel. The section sorts its rows\nby repair:\n\n- **Fix with a property** holds each colour pair below its WCAG floor, as the\n editor's contrast checker measures it, such as \"Brand text color 3.87:1,\n needs 4.5:1\", and every focus ring and control boundary below 3:1.\n- **Fix in the component's code** holds every other finding: axe failures,\n focus styles that SIA-R65 finds unchanged, failed keyboard cases, text that\n clips under the spacing override, and motion that ignores reduced motion.\n- **Check by eye** holds the `a11y-review` results.\n\nFindings that share an engine rule, a help text, and their traced properties\nmerge into one row. The row names its element, or its element count, and the\nviews it failed in, up to four. An issue's text is amber, and a result to\ncheck by eye is grey. The row's links sit beside its text:\n\n- Each property the row traces to links with a \"→\" to its row on the\n component's page, which scrolls to the row and highlights it. A row that\n traces to no property links to the component's page instead.\n- The link on the right opens the page the finding cites and names its kind:\n \"Rule\" for an axe or Alfa rule, \"Success criterion\" with its number for a\n WCAG criterion, \"Technique\" for a WCAG technique, and \"Pattern\" for a\n WAI-ARIA pattern.\n\nA property's label leads with the words from its variable name that the\neditor's label leaves out, so `--input-focused-border` reads \"Focused border\ncolor\" where the editor's row reads \"Border color\". The contrast rows read\nthe same labels.\n\nThe overview and the per-component counts include the results to check by\neye. A component's section ends in a \"N results to check by eye\" button that\nopens one closed list of those results, merged per component. axe's contrast\nviolations stay out of the report, because the contrast checker measures the\nsame pairs.\n\nWhen Playwright, axe, or Alfa is missing, the report names its install\ncommand and Run test stays disabled. Install the package and press **Check\nagain**. The view runs its tests through the dev server, so it exists under\nthe dev server alone, with the rest of the editor.\n\nThe view runs `check-component --tests --a11y --no-fix --json --open-theme`.\n`--a11y` runs the accessibility suite and the keyboard cases alone, and\n`--open-theme` tests the theme the editor has open. A run without it tests\nthe `default` theme. Both work from the command line too.\n\n## Marks in the editor\n\nThe last finished run marks the rest of the editor:\n\n- The component list leads the name of each component with any finding\n with a warning triangle. A condensed list shows the triangle on the\n component's icon.\n- That component's page opens with a panel under its title that lists its\n findings in all three groups, check by eye included.\n- Each \"→\" in the panel opens the tab holding the property and highlights\n its row.\n- Each property row a finding traces to carries a mark beside its label: a\n warning triangle in the highlight colour, or a grey \"?\" when every finding on the row is a\n result to check by eye. On a colour or size row the mark sits between the\n label and the value. On a toggle row it follows the label.\n\nClick a row's mark to open its findings: the count, then each merged row\nwith its element, its views, its help page, and the properties it traces to. The\neditor's own contrast failures add no row mark, because the contrast readout\non the colour row already shows them.\n\nThe marks and the panel keep the run's time and change when the next run\nfinishes. The contrast readout on each colour row stays live and measures\nevery edit as you make it.\n\n## Installing the engines\n\nThe engines ship as optional peer dependencies. A project made with `create`\nhas them. Any other project installs them beside `@playwright/test`:\n\n```bash\nnpm install -D axe-core @axe-core/playwright \\\n @siteimprove/alfa-act@^0.119.0 @siteimprove/alfa-device@^0.119.0 \\\n @siteimprove/alfa-dom@^0.119.0 @siteimprove/alfa-http@^0.119.0 \\\n @siteimprove/alfa-rules@^0.119.0 @siteimprove/alfa-url@^0.119.0 \\\n @siteimprove/alfa-web@^0.119.0\n```\n\nThe Alfa packages share one minor version. axe-core needs 4.11.1 or later to\nread OKLCH colours, which every shipped theme uses. A test whose engine is\nmissing reports as inapplicable, and its coverage names the package. A\nmissing engine leaves the exit status to the other rules, under `--strict`\ntoo.\n\n## Limits\n\n- The page's text spacing test measures widths, so it reports horizontal\n overflow alone. The component check measures both axes.\n- The page's reduced motion test reports animations still running 200 ms\n after the page loads. A short entrance animation finishes first and goes\n unseen.\n- The component's reduced motion check reads the preference within each\n view. A script that reads it only when the component mounts goes unseen.\n- The page scan leaves contrast to `page-contrast` and focus visibility to\n the component suite.\n- SIA-R65 reads the focused element, its descendants, and its ancestors, so\n it reports a focus ring painted on a sibling as missing. The focus\n indicator check measures that ring; confirm it by eye.\n- A view that forces the focused look at rest, such as Input's focused\n state, leaves real focus nothing to change, so SIA-R65 reports it there.\n The same element passes in the views that leave it at rest.\n- A finding traces to the component's own semantic properties and\n intrinsics. A ring or colour that a theme token paints directly, such as\n SegmentedControl's `--border-neutral-strong` focus ring, traces to\n nothing. A finding on a component nested\n inside another, such as a Button inside a Dialog, marks the host and names\n none of the nested component's properties.\n- A literal value in the component's code, such as a transition's duration,\n has no property to name. The finding traces to whatever property governs\n the same declaration, or to none.\n- A project's own components get no recorded accessibility tree.\n- Automated checks find part of what WCAG asks. Reading order, the meaning of\n labels and alt text, and screen reader behaviour still need a person.\n",
6
7
  "creating-components": "# Creating components\n\nThe package ships about 25 editable components. When you need one it doesn't\nhave, you can make your own Svelte component editable, so anyone using the\neditor can re-point its colours, type, and spacing without touching code.\n\nThe simplest way is to ask Claude. The package bundles a Claude Code skill that\nknows the conventions, writes the files, and checks the result for you.\n\n## Install the skills\n\n```bash\nnpx @motion-proto/live-tokens setup-claude\n```\n\nThis copies the bundled skills into your project's `.claude/skills/`. Once\nthey're there, Claude Code picks them up automatically.\n\n## Browse the skill atlas\n\nThe package also ships a Skill Atlas component: a diagram of the reasoning\nbehind each bundled skill, with its `SKILL.md` and reference files alongside\nso you can see exactly which lines drive which step. Import it from\n`@motion-proto/live-tokens/skill-atlas` and mount it on a route of your own:\n\n```js\n'/skills': {\n lazy: () => import('@motion-proto/live-tokens/skill-atlas'),\n},\n```\n\n## Ask for a component\n\nDescribe what you want in plain English. Phrases like these trigger the skill:\n\n- \"Add a Toggle component to live-tokens\"\n- \"Make this Svelte component editable in the live-tokens editor\"\n- \"Create a Stat component with a value and a label\"\n\nClaude asks any clarifying questions it needs (which variants, which states,\nwhich parts), then writes the component, registers it with the editor, and runs\nits verification checklist. When it finishes, open `/live-tokens/components` to see your new\ncomponent in the editor and confirm everything works.\n\n## What you get\n\n- A runtime component whose editable properties default to your theme tokens.\n- An editor entry that appears under **Custom** in the `/live-tokens/components` view.\n- A catalogue entry in the runtime file that says what the component is for and\n what to use instead. `npx live-tokens components` prints it, and the skills\n read it when they choose a component for a page.\n- The naming and wiring handled for you, so the component fits the system.\n\nAdvanced authors who want to write a component by hand can read the naming and\nstate-model conventions shipped in the package\n(`src/system/styles/CONVENTIONS.md` and the skill's own `SKILL.md`).\n",
7
- "editing-tokens": "# Editing tokens\n\nA tour of the editor. The page behind it repaints on every change; saving\nwrites a theme file you can reload later.\n\nThe editor has four views:\n\n- **Tokens**: the design-system primitives (colour, type, spacing, and so on).\n They apply everywhere your site uses them.\n- **Color Wheel**: the harmony wheel, the palette curves, and the story your colours\n tell across a page.\n- **Components**: per-component editors. Re-Assign what tokens a component uses\n without changing the underlying system.\n- **Sketchstyle**: an effect layer that redraws the page by hand. See\n [Sketch mode](sketch-mode.md).\n\nThis page covers **Tokens**. For components, see\n[Creating components](creating-components.md).\n\n## Palettes\n\nMost colour work happens here. Each palette (Brand, Accent, Neutral, Canvas,\nSuccess, Warning, Info, Danger, and a few more) has:\n\n- **Base colour.** Pick a hex; the palette derives an 11-step ramp (100 to 950)\n from it.\n- **Curves.** Three curves shape the ramp, in stack order: Hue, Saturation,\n Lightness. Drag the handles to bias it warmer or cooler, more or less\n saturated, darker or lighter. Hue drifts the ramp's temperature without\n moving contrast, because OKLCH hue rotation is close to lightness-preserving.\n It holds ±45 degrees; a bigger shift belongs on the base colour or the\n harmony axis.\n- **Overrides.** Lock a single step to a hand-picked hex when the curve doesn't\n land where you want.\n\nEditing a palette base ripples through every colour that depends on it, in real\ntime. Colours use OKLCH, so the ramp stays perceptually even across hues\nwithout muddy mid-tones.\n\n## Custom colours\n\nA custom colour is one colour of your own, for a slot no palette step fits.\nThe open theme holds it as a token named by number: `--color-custom-1`,\n`--color-custom-2`, and so on.\n\n- **Adding.** Every colour picker has a **Custom** row after Danger. Open it\n and click the plus swatch. The new colour starts from the slot's current colour and\n opens in the OKLCH editor, and the slot previews it as you drag. The check\n assigns it to the slot; the cross discards it and restores the slot.\n- **Reusing.** Pick a custom colour from the same row as you pick a palette\n step, opacity included. Every slot that names it follows its value.\n- **Editing.** Click the pencil on a custom swatch in the picker, or\n click **Edit** in the **Custom Colours** section of the Tokens view. The edit\n repaints every slot that names the colour.\n- **Removing.** The bin on a custom swatch in the picker, or **Remove** in\n the Tokens view, deletes a colour nothing names. When a component property,\n gradient stop, or wash names it, the editor first asks for a replacement and\n preselects the nearest colour. **Replace and remove** points each of those\n at the replacement, keeps each one's opacity, and deletes the custom colour.\n\nEach add, edit, and remove is one undo step. A custom colour holds one value in\nlight and dark mode. Saving writes the theme's custom colours into its file,\nand loading another theme brings in that theme's set. Reach a custom colour\nthrough a component property: `live-tokens check-page` reports `unknown-token`\nfor page CSS that writes `var(--color-custom-1)`, since the name exists only in\nthe theme.\n\n## Type\n\n- **Fonts.** Add sources from Google Fonts, Adobe (Typekit), a CSS URL, or an\n inline `@font-face`. The font loads in the page as soon as you add it.\n- **Stacks.** Named font cascades you reference by token, such as a display\n stack and a body stack.\n- **Sizes and weights.** A t-shirt scale (xs, sm, md, lg, xl, 2xl…) for size and\n a numeric scale (100 to 900) for weight.\n\n## Spacing, radius, shadow\n\nNumeric scales with a slider per step.\n\n- **Spacing**: the padding, gap, and margin scale.\n- **Radius**: none through full.\n- **Shadow**: colour, offset, blur, spread, and opacity per step, with stacked\n shadows supported.\n\nChange a step and every element using it repaints.\n\n## Washes and gradients\n\n- **Scrims** are translucent layers that dim what sits behind them, like the\n one a dialog draws over the page. Set a colour and opacity per stop.\n- **Tints** are the opposite operation: they shade the surface they sit on\n rather than dimming what is behind it, which is what a hover needs.\n- **Gradients** are reusable gradient tokens with a stop list and direction, for\n hero panels and accent backgrounds.\n\n## Columns\n\nPages lay out on the column grid. The column count and the max width hold at\nevery width. Desktop, Tablet, and Phone each set a gutter and an outer margin,\nand the grid takes the tablet values at 767px and narrower and the phone values\nat 480px and narrower. The columns button in the editor bar draws the grid over\nthe page, and a page on the grid reflows as you edit.\n\n## Saving\n\nThe editor saves to your browser continuously, so work survives a reload\nmid-edit. Writing a file is a separate step: the **Theme** panel at the foot of\nthe sidebar has **Save**, **Save As**, and **Load**, and each theme is one JSON\nfile under `src/live-tokens/data/themes/`.\n\nThe header gives you undo/redo (`Cmd/Ctrl+Z`, `Cmd/Ctrl+Shift+Z`). You can keep\nmany themes side by side; one is open at a time, and only **Adopt** publishes\none. See [Themes](themes-workflow.md) for the full lifecycle.\n",
8
+ "editing-tokens": "# Editing tokens\n\nA tour of the editor. The page behind it repaints on every change; saving\nwrites a theme file you can reload later.\n\nThe editor has five views:\n\n- **Tokens**: the design-system primitives (colour, type, spacing, and so on).\n They apply everywhere your site uses them.\n- **Color Wheel**: the harmony wheel, the palette curves, and the story your colours\n tell across a page.\n- **Components**: per-component editors. Re-Assign what tokens a component uses\n without changing the underlying system.\n- **Sketchstyle**: an effect layer that redraws the page by hand. See\n [Sketch mode](sketch-mode.md).\n- **Accessibility**: Run test checks every component in the open theme as\n saved and lists each issue it finds by component.\n\nThis page covers **Tokens**. For components, see\n[Creating components](creating-components.md).\n\n## Palettes\n\nMost colour work happens here. Each palette (Brand, Accent, Neutral, Canvas,\nSuccess, Warning, Info, Danger, and a few more) has:\n\n- **Base colour.** Pick a hex; the palette derives an 11-step ramp (100 to 950)\n from it.\n- **Curves.** Three curves shape the ramp, in stack order: Hue, Saturation,\n Lightness. Drag the handles to bias it warmer or cooler, more or less\n saturated, darker or lighter. Hue drifts the ramp's temperature without\n moving contrast, because OKLCH hue rotation is close to lightness-preserving.\n It holds ±45 degrees; a bigger shift belongs on the base colour or the\n harmony axis.\n- **Overrides.** Lock a single step to a hand-picked hex when the curve doesn't\n land where you want.\n\nEditing a palette base ripples through every colour that depends on it, in real\ntime. Colours use OKLCH, so the ramp stays perceptually even across hues\nwithout muddy mid-tones.\n\n## Custom colours\n\nA custom colour is one colour of your own, for a slot no palette step fits.\nThe open theme holds it as a token named by number: `--color-custom-1`,\n`--color-custom-2`, and so on.\n\n- **Adding.** Every colour picker has a **Custom** row after Danger. Open it\n and click the plus swatch. The new colour starts from the slot's current colour and\n opens in the OKLCH editor, and the slot previews it as you drag. The check\n assigns it to the slot; the cross discards it and restores the slot.\n- **Reusing.** Pick a custom colour from the same row as you pick a palette\n step, opacity included. Every slot that names it follows its value.\n- **Editing.** Click the pencil on a custom swatch in the picker, or\n click **Edit** in the **Custom Colours** section of the Tokens view. The edit\n repaints every slot that names the colour.\n- **Removing.** The bin on a custom swatch in the picker, or **Remove** in\n the Tokens view, deletes a colour nothing names. When a component property,\n gradient stop, or wash names it, the editor first asks for a replacement and\n preselects the nearest colour. **Replace and remove** points each of those\n at the replacement, keeps each one's opacity, and deletes the custom colour.\n\nEach add, edit, and remove is one undo step. A custom colour holds one value in\nlight and dark mode. Saving writes the theme's custom colours into its file,\nand loading another theme brings in that theme's set. Reach a custom colour\nthrough a component property: `live-tokens check-page` reports `unknown-token`\nfor page CSS that writes `var(--color-custom-1)`, since the name exists only in\nthe theme.\n\n## Type\n\n- **Fonts.** Add sources from Google Fonts, Adobe (Typekit), a CSS URL, or an\n inline `@font-face`. The font loads in the page as soon as you add it.\n- **Stacks.** Named font cascades you reference by token, such as a display\n stack and a body stack.\n- **Sizes and weights.** A t-shirt scale (xs, sm, md, lg, xl, 2xl…) for size and\n a numeric scale (100 to 900) for weight.\n\n## Spacing, radius, shadow\n\nNumeric scales with a slider per step.\n\n- **Spacing**: the padding, gap, and margin scale.\n- **Radius**: none through full.\n- **Shadow**: colour, offset, blur, spread, and opacity per step, with stacked\n shadows supported.\n\nChange a step and every element using it repaints.\n\n## Washes and gradients\n\n- **Scrims** are translucent layers that dim what sits behind them, like the\n one a dialog draws over the page. Set a colour and opacity per stop.\n- **Tints** are the opposite operation: they shade the surface they sit on\n rather than dimming what is behind it, which is what a hover needs.\n- **Gradients** are reusable gradient tokens with a stop list and direction, for\n hero panels and accent backgrounds.\n\n## Columns\n\nPages lay out on the column grid. The column count and the max width hold at\nevery width. Desktop, Tablet, and Phone each set a gutter and an outer margin,\nand the grid takes the tablet values at 767px and narrower and the phone values\nat 480px and narrower. The columns button in the editor bar draws the grid over\nthe page, and a page on the grid reflows as you edit.\n\n## Contrast readout\n\nIn the **Components** view, every colour row that paints text, an icon, or\nanother foreground mark shows its WCAG contrast ratio beside the colour's name.\nThe readout measures the colour against the layers the component paints behind\nit. Each edit and each theme load updates it, and the rows keep their places.\n\n- **Value.** The ratio shows on every foreground row, in grey while it meets\n its floor. It truncates to two decimals, so 4.499 reads `4.49:1`, below the\n 4.5:1 floor. Text needs 4.5:1, or 3:1 at 24px or 18.66px bold. An icon or\n another mark needs 3:1.\n- **Amber mark.** A ratio below its floor turns amber and gains an exclamation\n mark.\n- **Popover.** Click the ratio to open it. For each pair the colour forms, it\n names the WCAG rule and its floor, the measured ratio, and the layers behind\n the colour, top first, with a link to the success criterion. A colour over\n several backgrounds forms one pair per background, and the row shows the pair\n closest to failing. Notification's body colour paints the dismiss icon on the\n header and the body text on the page, so its popover lists two pairs.\n- **Disabled states.** WCAG exempts disabled controls, so a disabled state's\n ratio shows in a dimmer grey, whatever its value.\n- **Page background.** Every stack ends on the page background, `--page-bg`.\n A translucent layer blends into the layers beneath it, and the readout\n measures the blend. A gradient layer counts its worst stop.\n\n## Saving\n\nThe editor saves to your browser continuously, so work survives a reload\nmid-edit. Writing a file is a separate step: the **Theme** panel at the foot of\nthe sidebar has **Save**, **Save As**, and **Load**, and each theme is one JSON\nfile under `src/live-tokens/data/themes/`.\n\nThe header gives you undo/redo (`Cmd/Ctrl+Z`, `Cmd/Ctrl+Shift+Z`). You can keep\nmany themes side by side; one is open at a time, and only **Adopt** publishes\none. See [Themes](themes-workflow.md) for the full lifecycle.\n",
8
9
  "getting-started": "# Getting started\n\nScaffold a live token site in a moments. You need Node 20 or later, a\npackage manager (npm, pnpm, or yarn), and a browser. Open claude code in your repo and start building.\n\n## Scaffold a new app\n\n```bash\nnpm create @motion-proto/live-tokens@latest my-app\ncd my-app\nnpm install\nnpm run dev\n```\n\nOpen the URL Vite prints (usually `http://localhost:5173`). You get a\none-page Svelte + Vite app that depends on the published package, with the\neditor wired up and the full component set ready to import.\n\n`npx @motion-proto/live-tokens create my-app` runs the same scaffold without\nthe initialiser package.\n\n### What the scaffold gives you\n\nEvery editable file lives under `src/` and is committed, so `npm install` and\nversion upgrades never touch your styles. The package code stays in\n`node_modules`.\n\n| Path | What it is |\n|------|------------|\n| `src/pages/Home.svelte` | The starter page. Replace it with your own content. |\n| `src/App.svelte` | Your routes. `<LiveTokensRouter>` adds dev-only routes under a reserved `/live-tokens/*` namespace: `/live-tokens/editor`, `/live-tokens/components`, and `/live-tokens/docs`. |\n| `src/system/styles/tokens.css` | Your base token vocabulary, hand-authored. |\n| `src/styles/site.css` | Themed page typography, yours to edit. |\n\n## Your first edit\n\n1. Run `npm run dev` and open the home page.\n2. Click **Open Token Editor**, or visit `/live-tokens/editor`. The editor opens beside\n the page.\n3. Open **Palettes**, pick **Brand**, and change the base hex. The page\n repaints as you type.\n4. In the **Theme** panel at the foot of the sidebar, choose **Save As**. Your\n theme appears as JSON under `src/live-tokens/data/themes/`.\n5. Reload. The editor reopens on your theme, so the page returns as you left\n it.\n\n## What you just changed\n\nEvery edit sets a CSS custom property on `:root`. Your components read those\nproperties through `var(--...)`. There is no token build step and no\npreprocessor rewriting your code: the page renders against plain CSS variables\nthe editor swaps live.\n\nTo ship, click **Adopt** in the Theme panel. That saves the open theme and bakes\nit into `src/live-tokens/data/tokens.generated.css`, which your build bundles\nalongside `tokens.css`. Adopt is the only action that changes what your site\nships, so try any theme you like first. The editor itself never reaches\nproduction.\n\nAlready have a Svelte 5 + Vite app? The\n[README](https://github.com/motionproto/live-tokens#readme) covers installing\ninto an existing project.\n\n## Where to go next\n\n- **[Editing tokens](editing-tokens.md)**: a tour of the editor.\n- **[Themes](themes-workflow.md)**: save, switch, and ship.\n- **[Creating components](creating-components.md)**: make your own component\n editable.\n",
9
10
  "light-and-dark": "# Light and dark\n\nSome things on a page cannot be written as a token. A wordmark drawn in white\ndisappears on a pale theme. Ink that multiplies onto paper vanishes on a dark\none. A photograph behind a headline is dark no matter what the palette says.\n\nEach of those needs the same fact first: which way does the surface behind this\nthing lean? One attribute carries it.\n\n## The attribute\n\n`data-backdrop` is either `light` or `dark`, and it does two things at once: it\nselects, so a rule can key on it, and it sets `color-scheme`, so every\n`light-dark()` under it resolves the half that reads.\n\n```css\n.title {\n color: light-dark(var(--color-black), var(--color-white));\n}\n```\n\nThat line is right on both sides of the theme, and it is right inside a dark\nsection on a pale page, because the nearest `color-scheme` wins.\n\n## Stating it\n\nPut it in the markup when the surface knows its own tone — a hero over a\nphotograph, a plate that stays pale in every theme:\n\n```svelte\n<div class=\"hero-panel\" data-backdrop=\"dark\">\n```\n\nA stated tone beats any measurement, and it inherits, so everything inside the\npanel resolves against it.\n\n## Measuring it\n\nWhere the tone is a property of the theme rather than of the markup, let it be\nmeasured:\n\n```svelte\n<script>\n import { backdrop } from '@motion-proto/live-tokens/backdrop';\n</script>\n\n<section use:backdrop>\n```\n\nThe action reads whatever actually paints behind the element — the nearest\nancestor with an opaque fill, averaged across its gradient stops, falling back\nto the theme's `--page-bg` — and stamps the answer. It re-reads when the theme\nchanges, which the editor does by rewriting custom properties with no reload,\nso the stamp follows a live edit.\n\nThe page itself is stamped for you: the build bakes the production theme's\npolarity into `tokens.generated.css`, so the first paint is already right, and\n`syncDocumentBackdrop()` keeps `<html>` current as themes switch.\n\n```ts\nimport { syncDocumentBackdrop } from '@motion-proto/live-tokens/backdrop';\n\nsyncDocumentBackdrop();\n```\n\n## Reading it from JavaScript\n\nAnything that paints outside CSS — a canvas, a WebGL uniform, an `<img>` that\ncomes in two versions — asks the same question through the same module:\n\n```ts\nimport { isLightBackdrop, watchBackdrop, cssColorToHex } from '@motion-proto/live-tokens/backdrop';\n\nconst stop = watchBackdrop(logoEl, {\n stamp: false,\n onChange: (polarity) => (src = polarity === 'light' ? darkMark : lightMark),\n});\n```\n\n`isLightBackdrop(el)` answers once. `watchBackdrop` keeps answering and returns\na stop function. `cssColorToHex` resolves any CSS colour — including the\n`oklch()` a token holds — to a hex a non-CSS consumer can take.\n\n## What it does not do\n\nPolarity is a property of a surface, not of a component, so nothing is stamped\nfor you below `<html>`: a section that needs an answer either states one or asks\nfor one. And a measurement reads the paint at the moment it runs — an element\nthat scrolls from a pale section onto a dark one keeps the answer it was given.\nState the tone on each section instead.\n",
10
11
  "production-css": "# Production CSS\n\nIn dev, every design token sits on `:root` as a CSS variable, because the\neditor can rewrite any of them while the page runs. A production build runs no\neditor, so most of those values never change. `vite build` rewrites the CSS it\nemits to match: a token stays a variable only where its value can change at\nruntime, and every other `var()` gives way to the value it reads.\n\nThe dev server keeps every variable. Your source files keep every token too:\nthe build rewrites only the files it emits, so `tokens.css`,\n`tokens.generated.css` and your components still name each token, and the\ntoken names stay the API you write against.\n\n## The build step\n\n`themeFileApi` runs the step at the end of every `vite build`, after Vite has\nbundled and minified the CSS. It reads every stylesheet and script in the\nbundle, including lazy chunks, and then:\n\n- **removes** each custom property that no rule reads, directly or through\n another property;\n- **substitutes** each `var()` that reads a static property with the text of\n that property's value, so `calc()` and `color-mix()` still resolve on the\n element that uses them;\n- **declares** each remaining property once on `:root`, with the value that\n wins the cascade, so DevTools lists it once.\n\nThe build prints one line with the result:\n\n```\nlive-tokens: the production CSS declares 76 of 848 custom properties and is 61.3 kB smaller.\n```\n\nA file's hashed name follows its content. Each stylesheet the build rewrites\ntakes a new hash, and so does each script that loads it, so a cache that keeps\nhashed files forever serves each release its own files. A file name pattern\nwithout `[hash]` keeps every name.\n\n## The dynamic rule\n\nA property stays a variable when its value can change while the page runs, or\nwhen the build cannot prove it never does. A property that changes and loses\nits variable breaks a page, and an extra variable costs a few bytes, so a case\nthat could fall either way stays a variable. A property is **dynamic** when any\nof these hold:\n\n- A rule declares it under a selector besides a root selector such as `:root`\n or `:root:root`, or inside `@media`, `@supports` or `@container`. The size\n tokens and the column gutter and margin in the three responsive `@media`\n blocks of `tokens.css` stay variables this way.\n- Its name appears in a script or HTML page the build emits. That covers\n `style:--name` directives, `setProperty`, `getPropertyValue` and inline\n `style` attributes. Card sets `--card-hover-border-enabled` through a\n `style:` directive, so the token stays a variable.\n- Its name appears in an at-rule's prelude, such as a `style()` container\n query.\n- An `@property` rule registers it.\n- The `keep` option lists it.\n- Its value reads a dynamic property.\n- Its winning value is a CSS-wide keyword such as `initial`, or it reads\n itself through a loop of `var()` references.\n- Two stylesheets give it different `:root` values.\n- Its value reads a name that no stylesheet declares and no script or `keep`\n entry names.\n- Only a lazily loaded stylesheet declares it, and another stylesheet reads\n it.\n\nEvery other property is **static**: one value on `:root` for the life of the\npage.\n\n## Computed names\n\nThe build finds a name in a script only when the script spells it out. A\nscript that assembles a name while the page runs, such as\n`` `--color-${family}-500` ``, hides it. The build then substitutes or removes\nthat token, so a read returns an empty string and a write changes nothing. List\neach such name under `keep`:\n\n```ts\nthemeFileApi({\n tokensCssPath: 'src/system/styles/tokens.css',\n productionCss: { keep: ['--color-brand-500', '--color-accent-500'] },\n}),\n```\n\n`keep` takes exact names. The same goes for a stylesheet outside the bundle,\nsuch as one in `public/`: the build never reads it, so list each token it\nreads under `keep`.\n\n## Options\n\n`productionCss` on `themeFileApi` sets the step:\n\n- **Default.** The build removes, substitutes and declares, as above.\n- **`productionCss: { keep: [...] }`.** Each listed name stays a variable.\n- **`productionCss: { substitute: false }`.** The build removes properties no\n rule reads and keeps every other `var()`, so DevTools shows token names while\n you debug. It still declares each property once on `:root`.\n- **`productionCss: false`.** The build keeps every variable, as dev does.\n\n`keep` and `substitute` combine in one object. When a page renders differently\nin production, build it with `productionCss: false`. A difference that goes\naway points at a token the rule missed, and `keep` fixes it.\n\n## Limits\n\n- **Values a property rejects.** In dev, `width: var(--color-brand-500)` passes\n the parser, and when the color reaches `width` the browser treats the\n declaration as `unset`. After substitution, `width` holds a color, the parser\n drops the declaration, and an earlier `width` rule applies. Keep each token's\n value valid for every property that reads it.\n- **Fixed stylesheet names.** The build renames each stylesheet it rewrites\n through the hash in the build's `assetFileNames` pattern. A plugin that emits\n a stylesheet under a fixed `fileName` breaks that pattern, and the build\n stops with `live-tokens: <file> does not follow the output pattern\n \"<pattern>\"`. Set `productionCss: false` for that build.\n",
@@ -8,7 +8,9 @@
8
8
  import { getComponentRegistryEntries, validateRegistryAgainstServerScan } from '../component-editor/registry';
9
9
  import { listComponents } from '../core/components/componentConfigService';
10
10
  import { selectedComponent } from '../core/store/editorViewStore';
11
- import { componentDirty, editorState, mutate } from '../core/store/editorStore';
11
+ import { a11yIssues, componentDirty, editorState, mutate } from '../core/store/editorStore';
12
+ import A11yRailMark from '../ui/a11y/A11yRailMark.svelte';
13
+ import { componentsWithIssues } from '../ui/a11y/a11yView';
12
14
  import { setSketch } from '../core/sketch';
13
15
  import type { LiveTokensEditorHandle } from './liveTokensEditorHandle';
14
16
  // Editor chrome + form controls + icon font must be JS imports (not @import
@@ -110,6 +112,7 @@
110
112
  const allComponentNavItems = getComponentRegistryEntries().map(({ id, label, icon, origin }) => ({ id, label, icon, origin }));
111
113
  const systemNavItems = allComponentNavItems.filter((i) => i.origin === 'system');
112
114
  const customNavItems = allComponentNavItems.filter((i) => i.origin === 'custom');
115
+ let a11yMarked = $derived(new Set(componentsWithIssues($a11yIssues).map((row) => row.component)));
113
116
  </script>
114
117
 
115
118
  <!--
@@ -167,6 +170,9 @@
167
170
  {#if $componentDirty[item.id]}
168
171
  <span class="dirty-dot" aria-label="Unsaved changes" title="Unsaved changes"></span>
169
172
  {/if}
173
+ {#if a11yMarked.has(item.id)}
174
+ <A11yRailMark />
175
+ {/if}
170
176
  </button>
171
177
  {/each}
172
178
  {#if customNavItems.length > 0}
@@ -187,6 +193,9 @@
187
193
  {#if $componentDirty[item.id]}
188
194
  <span class="dirty-dot" aria-label="Unsaved changes" title="Unsaved changes"></span>
189
195
  {/if}
196
+ {#if a11yMarked.has(item.id)}
197
+ <A11yRailMark />
198
+ {/if}
190
199
  </button>
191
200
  {/each}
192
201
  {/if}