@motion-proto/live-tokens 0.96.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 (206) 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 +309 -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/bin/set-colors.mjs +1 -0
  13. package/bin/setup-claude.mjs +2 -2
  14. package/dist-plugin/{chunk-A4TBP4MM.js → chunk-SLICH4IH.js} +2 -2
  15. package/dist-plugin/index.cjs +148 -3
  16. package/dist-plugin/index.js +250 -105
  17. package/dist-plugin/setColors/index.cjs +7 -1
  18. package/dist-plugin/setColors/index.d.cts +9 -4
  19. package/dist-plugin/setColors/index.d.ts +9 -4
  20. package/dist-plugin/setColors/index.js +8 -2
  21. package/dist-plugin/setGeometry/index.d.cts +1 -1
  22. package/dist-plugin/setGeometry/index.d.ts +1 -1
  23. package/dist-plugin/setType/index.d.cts +1 -1
  24. package/dist-plugin/setType/index.d.ts +1 -1
  25. package/dist-plugin/{themeTypes-B8_Idrp4.d.cts → themeTypes-BaTQUyac.d.cts} +10 -1
  26. package/dist-plugin/{themeTypes-B8_Idrp4.d.ts → themeTypes-BaTQUyac.d.ts} +10 -1
  27. package/package.json +49 -2
  28. package/src/demo/LabeledSelect.svelte +1 -1
  29. package/src/editor/bootstrap.ts +3 -1
  30. package/src/editor/component-editor/BadgeEditor.svelte +13 -0
  31. package/src/editor/component-editor/ButtonEditor.svelte +33 -2
  32. package/src/editor/component-editor/CalloutEditor.svelte +13 -0
  33. package/src/editor/component-editor/CardEditor.svelte +12 -2
  34. package/src/editor/component-editor/CodeSnippetEditor.svelte +11 -1
  35. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +18 -1
  36. package/src/editor/component-editor/CornerBadgeEditor.svelte +13 -0
  37. package/src/editor/component-editor/DialogEditor.svelte +15 -0
  38. package/src/editor/component-editor/IconButtonEditor.svelte +29 -1
  39. package/src/editor/component-editor/ImageEditor.svelte +20 -9
  40. package/src/editor/component-editor/ImageLightboxEditor.svelte +44 -0
  41. package/src/editor/component-editor/InlineEditActionsEditor.svelte +13 -1
  42. package/src/editor/component-editor/InputEditor.svelte +23 -0
  43. package/src/editor/component-editor/MenuSelectEditor.svelte +27 -1
  44. package/src/editor/component-editor/NotificationEditor.svelte +15 -0
  45. package/src/editor/component-editor/PanelEditor.svelte +1 -1
  46. package/src/editor/component-editor/ProgressBarEditor.svelte +13 -0
  47. package/src/editor/component-editor/RadioButtonEditor.svelte +20 -1
  48. package/src/editor/component-editor/SectionDividerEditor.svelte +41 -13
  49. package/src/editor/component-editor/SegmentedControlEditor.svelte +29 -1
  50. package/src/editor/component-editor/SideNavigationEditor.svelte +40 -1
  51. package/src/editor/component-editor/SlidePagerEditor.svelte +20 -0
  52. package/src/editor/component-editor/SliderEditor.svelte +33 -0
  53. package/src/editor/component-editor/TabBarEditor.svelte +23 -2
  54. package/src/editor/component-editor/TableEditor.svelte +14 -0
  55. package/src/editor/component-editor/ToggleEditor.svelte +17 -0
  56. package/src/editor/component-editor/TooltipEditor.svelte +11 -2
  57. package/src/editor/component-editor/VideoFrameEditor.svelte +8 -0
  58. package/src/editor/component-editor/VideoLightboxEditor.svelte +10 -0
  59. package/src/editor/component-editor/registration.ts +2 -1
  60. package/src/editor/component-editor/registry.ts +69 -27
  61. package/src/editor/component-editor/scaffolding/DemoHeader.svelte +2 -0
  62. package/src/editor/component-editor/scaffolding/StateBlock.svelte +19 -8
  63. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +3 -0
  64. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +70 -10
  65. package/src/editor/component-editor/scaffolding/editorContext.ts +7 -0
  66. package/src/editor/core/a11y/a11yReport.ts +101 -0
  67. package/src/editor/core/a11y/a11yReportTypes.ts +69 -0
  68. package/src/editor/core/a11y/contrastFailures.ts +22 -0
  69. package/src/editor/core/components/contrastPairRegistry.ts +18 -0
  70. package/src/editor/core/palettes/contrast.ts +44 -0
  71. package/src/editor/core/palettes/contrastPairs.ts +79 -0
  72. package/src/editor/core/palettes/resolveColor.ts +77 -0
  73. package/src/editor/core/store/editorPersistence.ts +9 -1
  74. package/src/editor/core/store/editorRenderer.ts +2 -0
  75. package/src/editor/core/store/editorStore.ts +29 -0
  76. package/src/editor/core/store/editorTypes.ts +9 -0
  77. package/src/editor/core/store/editorViewStore.ts +2 -2
  78. package/src/editor/core/store/unsavedEdits.ts +10 -0
  79. package/src/editor/core/themes/buildColors.ts +16 -5
  80. package/src/editor/core/themes/liveStateStream.ts +7 -1
  81. package/src/editor/core/themes/parsers/gradient.ts +5 -2
  82. package/src/editor/core/themes/slices/customColors.ts +226 -0
  83. package/src/editor/core/themes/themeTypes.ts +10 -0
  84. package/src/editor/docs/chapters.ts +1 -0
  85. package/src/editor/docs/content/accessibility.md +362 -0
  86. package/src/editor/docs/content/editing-tokens.md +56 -1
  87. package/src/editor/docs/content.generated.ts +2 -1
  88. package/src/editor/pages/ComponentEditorPage.svelte +10 -1
  89. package/src/editor/pages/EditorShell.svelte +97 -10
  90. package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
  91. package/src/editor/skill-atlas/trees/check-compliance.ts +3 -3
  92. package/src/editor/skill-atlas/trees/create-component.ts +12 -12
  93. package/src/editor/ui/CustomColorReplaceStep.svelte +118 -0
  94. package/src/editor/ui/EditorViewSwitcher.svelte +15 -2
  95. package/src/editor/ui/GradientEditor.svelte +9 -1
  96. package/src/editor/ui/ThemePanel.svelte +4 -4
  97. package/src/editor/ui/UIContrastReadout.svelte +186 -0
  98. package/src/editor/ui/UIInfoPopover.svelte +16 -2
  99. package/src/editor/ui/UIPaletteSelector.svelte +347 -15
  100. package/src/editor/ui/UITokenSelector.svelte +33 -14
  101. package/src/editor/ui/VariablesTab.svelte +3 -0
  102. package/src/editor/ui/a11y/A11yComponentIssues.svelte +103 -0
  103. package/src/editor/ui/a11y/A11yFindingRow.svelte +57 -0
  104. package/src/editor/ui/a11y/A11yIssueRow.svelte +121 -0
  105. package/src/editor/ui/a11y/A11yOverview.svelte +242 -0
  106. package/src/editor/ui/a11y/A11yPanel.svelte +128 -0
  107. package/src/editor/ui/a11y/A11yPropertyMark.svelte +84 -0
  108. package/src/editor/ui/a11y/A11yRailMark.svelte +34 -0
  109. package/src/editor/ui/a11y/AccessibilityTab.svelte +334 -0
  110. package/src/editor/ui/a11y/a11yView.ts +218 -0
  111. package/src/editor/ui/anchorToTrigger.ts +63 -0
  112. package/src/editor/ui/contrastReadout.ts +45 -0
  113. package/src/editor/ui/sections/CustomColorsSection.svelte +254 -0
  114. package/src/live-tokens/data/themes/autumn.json +20 -20
  115. package/src/live-tokens/data/themes/halloween.json +20 -20
  116. package/src/live-tokens/data/themes/midnight-study.json +20 -20
  117. package/src/live-tokens/data/themes/ocean.json +20 -20
  118. package/src/live-tokens/data/themes/royal-velvet.json +20 -20
  119. package/src/live-tokens/data/themes/sketchy.json +20 -20
  120. package/src/live-tokens/data/themes/spring-meadow.json +20 -20
  121. package/src/live-tokens/data/themes/sunset.json +20 -20
  122. package/src/system/components/Badge.svelte +3 -3
  123. package/src/system/components/Button.svelte +9 -3
  124. package/src/system/components/CodeSnippet.svelte +8 -1
  125. package/src/system/components/CornerBadge.svelte +3 -3
  126. package/src/system/components/Dialog.svelte +43 -14
  127. package/src/system/components/IconButton.svelte +1 -1
  128. package/src/system/components/Image.svelte +7 -0
  129. package/src/system/components/ImageLightbox.svelte +26 -26
  130. package/src/system/components/Input.svelte +8 -1
  131. package/src/system/components/MenuSelect.svelte +30 -2
  132. package/src/system/components/Notification.svelte +1 -1
  133. package/src/system/components/ProgressBar.svelte +6 -0
  134. package/src/system/components/RadioButton.svelte +36 -3
  135. package/src/system/components/SegmentedControl.svelte +36 -2
  136. package/src/system/components/SlidePager.svelte +2 -2
  137. package/src/system/components/Slider.svelte +13 -9
  138. package/src/system/components/TabBar.svelte +33 -3
  139. package/src/system/components/Toggle.svelte +2 -2
  140. package/src/system/components/VideoFrame.svelte +1 -1
  141. package/src/system/components/VideoLightbox.svelte +2 -2
  142. package/src/system/internal/focusTrap.ts +35 -0
  143. package/src/testing-js/__aria__/badge.yml +40 -0
  144. package/src/testing-js/__aria__/button.yml +88 -0
  145. package/src/testing-js/__aria__/callout.yml +12 -0
  146. package/src/testing-js/__aria__/card.yml +8 -0
  147. package/src/testing-js/__aria__/codesnippet.yml +21 -0
  148. package/src/testing-js/__aria__/collapsiblesection.yml +23 -0
  149. package/src/testing-js/__aria__/cornerbadge.yml +24 -0
  150. package/src/testing-js/__aria__/dialog.yml +42 -0
  151. package/src/testing-js/__aria__/iconbutton.yml +57 -0
  152. package/src/testing-js/__aria__/image.yml +10 -0
  153. package/src/testing-js/__aria__/imagelightbox.yml +50 -0
  154. package/src/testing-js/__aria__/inlineeditactions.yml +16 -0
  155. package/src/testing-js/__aria__/input.yml +61 -0
  156. package/src/testing-js/__aria__/menuselect.yml +30 -0
  157. package/src/testing-js/__aria__/notification.yml +30 -0
  158. package/src/testing-js/__aria__/panel.yml +2 -0
  159. package/src/testing-js/__aria__/progressbar.yml +2 -0
  160. package/src/testing-js/__aria__/radiobutton.yml +20 -0
  161. package/src/testing-js/__aria__/sectiondivider.yml +9 -0
  162. package/src/testing-js/__aria__/segmentedcontrol.yml +35 -0
  163. package/src/testing-js/__aria__/sidenavigation.yml +304 -0
  164. package/src/testing-js/__aria__/slidepager.yml +72 -0
  165. package/src/testing-js/__aria__/slider.yml +28 -0
  166. package/src/testing-js/__aria__/tabbar.yml +35 -0
  167. package/src/testing-js/__aria__/table.yml +144 -0
  168. package/src/testing-js/__aria__/toggle.yml +12 -0
  169. package/src/testing-js/__aria__/tooltip.yml +2 -0
  170. package/src/testing-js/__aria__/videoframe.yml +6 -0
  171. package/src/testing-js/__aria__/videolightbox.yml +12 -0
  172. package/src/testing-js/{chunk-WIZ6W7UT.js → chunk-3WCLMOM7.js} +2 -2
  173. package/src/testing-js/{chunk-SHBYZUPD.js → chunk-5HNZHIHG.js} +211 -14
  174. package/src/testing-js/chunk-5HNZHIHG.js.map +1 -0
  175. package/src/testing-js/{chunk-NB3NZRBM.js → chunk-7CM5D7PF.js} +5 -1
  176. package/src/testing-js/chunk-7CM5D7PF.js.map +1 -0
  177. package/src/testing-js/chunk-EWSGE27V.js +168 -0
  178. package/src/testing-js/chunk-EWSGE27V.js.map +1 -0
  179. package/src/testing-js/{chunk-GKO27NGN.js → chunk-R54I7YL6.js} +2 -2
  180. package/src/testing-js/{chunk-X6KHEVIG.js → chunk-RWH46IU5.js} +97 -29
  181. package/src/testing-js/chunk-RWH46IU5.js.map +1 -0
  182. package/src/testing-js/chunk-SHLPDNAQ.js +658 -0
  183. package/src/testing-js/chunk-SHLPDNAQ.js.map +1 -0
  184. package/src/testing-js/{chunk-3UKGXCDL.js → chunk-YVVR3GJZ.js} +1 -1
  185. package/src/testing-js/chunk-YVVR3GJZ.js.map +1 -0
  186. package/src/testing-js/component-a11y.contract.js +283 -0
  187. package/src/testing-js/component-a11y.contract.js.map +1 -0
  188. package/src/testing-js/component-alias.contract.js +2 -2
  189. package/src/testing-js/component-behavior.contract.js +2 -2
  190. package/src/testing-js/component-editor.contract.js +6 -5
  191. package/src/testing-js/component-editor.contract.js.map +1 -1
  192. package/src/testing-js/component-render.contract.js +10 -6
  193. package/src/testing-js/component-render.contract.js.map +1 -1
  194. package/src/testing-js/index.d.ts +44 -16
  195. package/src/testing-js/index.js +9 -9
  196. package/src/testing-js/page-compliance.contract.js +163 -18
  197. package/src/testing-js/page-compliance.contract.js.map +1 -1
  198. package/src/testing-js/vitest.js +3 -3
  199. package/template/package.json +9 -0
  200. package/src/editor/ui/keepInViewport.ts +0 -84
  201. package/src/testing-js/chunk-3UKGXCDL.js.map +0 -1
  202. package/src/testing-js/chunk-NB3NZRBM.js.map +0 -1
  203. package/src/testing-js/chunk-SHBYZUPD.js.map +0 -1
  204. package/src/testing-js/chunk-X6KHEVIG.js.map +0 -1
  205. /package/src/testing-js/{chunk-WIZ6W7UT.js.map → chunk-3WCLMOM7.js.map} +0 -0
  206. /package/src/testing-js/{chunk-GKO27NGN.js.map → chunk-R54I7YL6.js.map} +0 -0
@@ -2518,7 +2518,7 @@
2518
2518
  "createdAt": "2026-05-01T08:28:00.345Z",
2519
2519
  "updatedAt": "2026-08-19T22:39:32.785Z",
2520
2520
  "aliases": {
2521
- "--badge-brand-surface": "--surface-brand",
2521
+ "--badge-brand-surface": "--surface-brand-low",
2522
2522
  "--badge-brand-text": "--text-brand",
2523
2523
  "--badge-brand-border": "--border-brand",
2524
2524
  "--badge-brand-text-font-family": "--font-sans",
@@ -2571,7 +2571,7 @@
2571
2571
  "--badge-alternate-blur": "--blur-none",
2572
2572
  "--badge-alternate-icon-size": "--icon-size-sm",
2573
2573
  "--badge-canvas-surface": "--surface-canvas",
2574
- "--badge-canvas-text": "--text-canvas",
2574
+ "--badge-canvas-text": "--text-secondary",
2575
2575
  "--badge-canvas-border": "--border-canvas",
2576
2576
  "--badge-canvas-text-font-family": "--font-sans",
2577
2577
  "--badge-canvas-text-font-size": "--font-size-md",
@@ -2622,7 +2622,7 @@
2622
2622
  "--badge-warning-shadow": "--shadow-none",
2623
2623
  "--badge-warning-blur": "--blur-none",
2624
2624
  "--badge-warning-icon-size": "--icon-size-sm",
2625
- "--badge-danger-surface": "--surface-danger",
2625
+ "--badge-danger-surface": "--surface-danger-low",
2626
2626
  "--badge-danger-text": "--text-danger",
2627
2627
  "--badge-danger-border": "--border-danger",
2628
2628
  "--badge-danger-text-font-family": "--font-sans",
@@ -2995,7 +2995,7 @@
2995
2995
  "--cornerbadge-text-font-size": "--font-size-sm",
2996
2996
  "--cornerbadge-text-font-weight": "--font-weight-medium",
2997
2997
  "--cornerbadge-text-line-height": "--line-height-none",
2998
- "--cornerbadge-brand-surface": "--surface-brand",
2998
+ "--cornerbadge-brand-surface": "--surface-brand-low",
2999
2999
  "--cornerbadge-brand-border": "--border-brand",
3000
3000
  "--cornerbadge-brand-text": "--text-brand",
3001
3001
  "--cornerbadge-accent-surface": "--surface-accent",
@@ -3009,7 +3009,7 @@
3009
3009
  "--cornerbadge-alternate-text": "--text-alternate",
3010
3010
  "--cornerbadge-canvas-surface": "--surface-canvas",
3011
3011
  "--cornerbadge-canvas-border": "--border-canvas",
3012
- "--cornerbadge-canvas-text": "--text-canvas",
3012
+ "--cornerbadge-canvas-text": "--text-secondary",
3013
3013
  "--cornerbadge-special-surface": "--surface-special",
3014
3014
  "--cornerbadge-special-border": "--border-special",
3015
3015
  "--cornerbadge-special-text": "--text-special",
@@ -3019,7 +3019,7 @@
3019
3019
  "--cornerbadge-warning-surface": "--surface-warning",
3020
3020
  "--cornerbadge-warning-border": "--border-warning",
3021
3021
  "--cornerbadge-warning-text": "--text-warning",
3022
- "--cornerbadge-danger-surface": "--surface-danger",
3022
+ "--cornerbadge-danger-surface": "--surface-danger-low",
3023
3023
  "--cornerbadge-danger-border": "--border-danger",
3024
3024
  "--cornerbadge-danger-text": "--text-danger",
3025
3025
  "--cornerbadge-info-surface": "--surface-info",
@@ -3237,7 +3237,7 @@
3237
3237
  "--input-border-width": "--border-width-1",
3238
3238
  "--input-gap": "--space-6",
3239
3239
  "--input-default-surface": "--surface-neutral-lower",
3240
- "--input-default-border": "--border-neutral",
3240
+ "--input-default-border": "--border-neutral-medium",
3241
3241
  "--input-default-text": "--text-primary",
3242
3242
  "--input-default-text-font-family": "--font-sans",
3243
3243
  "--input-default-text-font-size": "--font-size-sm",
@@ -3396,7 +3396,7 @@
3396
3396
  "--notification-warning-text-font-size": "--font-size-md",
3397
3397
  "--notification-warning-text-font-weight": "--font-weight-normal",
3398
3398
  "--notification-warning-text-line-height": "--line-height-normal",
3399
- "--notification-danger-surface": "--surface-danger",
3399
+ "--notification-danger-surface": "--surface-danger-low",
3400
3400
  "--notification-danger-action-surface": "--surface-neutral-lowest",
3401
3401
  "--notification-danger-border": "--border-danger",
3402
3402
  "--notification-danger-border-width": "--border-width-1",
@@ -3480,7 +3480,7 @@
3480
3480
  "createdAt": "2026-05-10T20:44:44.372Z",
3481
3481
  "updatedAt": "2026-08-19T22:39:32.785Z",
3482
3482
  "aliases": {
3483
- "--radiobutton-default-dot-border-color": "--border-neutral",
3483
+ "--radiobutton-default-dot-border-color": "--border-neutral-medium",
3484
3484
  "--radiobutton-default-dot-border-width": "--border-width-2",
3485
3485
  "--radiobutton-default-dot-fill": "--text-secondary",
3486
3486
  "--radiobutton-default-dot-size": "--dot-size-0",
@@ -3489,7 +3489,7 @@
3489
3489
  "--radiobutton-default-label-font-size": "--font-size-md",
3490
3490
  "--radiobutton-default-label-font-weight": "--font-weight-semibold",
3491
3491
  "--radiobutton-default-label-line-height": "--line-height-none",
3492
- "--radiobutton-hover-dot-border-color": "--border-neutral",
3492
+ "--radiobutton-hover-dot-border-color": "--border-neutral-medium",
3493
3493
  "--radiobutton-hover-dot-border-width": "--border-width-2",
3494
3494
  "--radiobutton-hover-dot-fill": "--text-secondary",
3495
3495
  "--radiobutton-hover-dot-size": "--dot-size-50",
@@ -3498,7 +3498,7 @@
3498
3498
  "--radiobutton-hover-label-font-size": "--font-size-md",
3499
3499
  "--radiobutton-hover-label-font-weight": "--font-weight-semibold",
3500
3500
  "--radiobutton-hover-label-line-height": "--line-height-none",
3501
- "--radiobutton-selected-dot-border-color": "--border-neutral",
3501
+ "--radiobutton-selected-dot-border-color": "--border-neutral-medium",
3502
3502
  "--radiobutton-selected-dot-border-width": "--border-width-2",
3503
3503
  "--radiobutton-selected-dot-fill": "--text-secondary",
3504
3504
  "--radiobutton-selected-dot-size": "--dot-size-50",
@@ -3607,7 +3607,7 @@
3607
3607
  "--segmentedcontrol-hover-tint": "--tint",
3608
3608
  "--segmentedcontrol-hover-tint-enabled": "--color-transparent",
3609
3609
  "--segmentedcontrol-bar-surface": "color-mix(in srgb, var(--surface-neutral-lowest) 75%, transparent)",
3610
- "--segmentedcontrol-bar-border": "--border-neutral",
3610
+ "--segmentedcontrol-bar-border": "--border-neutral-medium",
3611
3611
  "--segmentedcontrol-bar-border-width": "--border-width-1",
3612
3612
  "--segmentedcontrol-bar-radius": "--radius-xl",
3613
3613
  "--segmentedcontrol-bar-padding": "--space-6",
@@ -3638,7 +3638,7 @@
3638
3638
  "--segmentedcontrol-selected-text-font-weight": "--font-weight-normal",
3639
3639
  "--segmentedcontrol-selected-text-line-height": "--line-height-normal",
3640
3640
  "--segmentedcontrol-selected-icon": "--text-secondary",
3641
- "--segmentedcontrol-selected-border": "--border-brand",
3641
+ "--segmentedcontrol-selected-border": "--border-brand-medium",
3642
3642
  "--segmentedcontrol-selected-border-width": "--border-width-1",
3643
3643
  "--segmentedcontrol-selected-radius": "--radius-lg",
3644
3644
  "--segmentedcontrol-disabled-surface": "--color-transparent",
@@ -3865,7 +3865,7 @@
3865
3865
  "--tabbar-hover-tab-bottom-radius": "--radius-sm",
3866
3866
  "--tabbar-hover-indicator-width": "--border-width-2",
3867
3867
  "--tabbar-selected-text": "--text-primary",
3868
- "--tabbar-selected-border": "--color-brand-500",
3868
+ "--tabbar-selected-border": "--border-brand-medium",
3869
3869
  "--tabbar-selected-surface": "--tint-low",
3870
3870
  "--tabbar-selected-text-font-family": "--font-sans",
3871
3871
  "--tabbar-selected-text-font-size": "--font-size-md",
@@ -3935,7 +3935,7 @@
3935
3935
  "updatedAt": "2026-08-19T22:39:32.785Z",
3936
3936
  "aliases": {
3937
3937
  "--toggle-track-surface": "--surface-neutral",
3938
- "--toggle-track-border": "--border-neutral",
3938
+ "--toggle-track-border": "--border-neutral-medium",
3939
3939
  "--toggle-track-border-width": "--border-width-1",
3940
3940
  "--toggle-track-radius": "--radius-full",
3941
3941
  "--toggle-track-padding": "--space-4",
@@ -3950,7 +3950,7 @@
3950
3950
  "--toggle-hover-track-surface": "--surface-neutral-high",
3951
3951
  "--toggle-hover-thumb-surface": "--text-primary",
3952
3952
  "--toggle-on-track-surface": "--surface-brand-high",
3953
- "--toggle-on-track-border": "--border-brand",
3953
+ "--toggle-on-track-border": "--border-brand-medium",
3954
3954
  "--toggle-on-thumb-surface": "--text-primary",
3955
3955
  "--toggle-on-thumb-border": "--border-brand-strong",
3956
3956
  "--toggle-on-hover-track-surface": "--surface-brand-higher",
@@ -3986,13 +3986,13 @@
3986
3986
  "updatedAt": "2026-09-02T10:55:50.831Z",
3987
3987
  "aliases": {
3988
3988
  "--slider-single-track-surface": "--surface-neutral-lowest",
3989
- "--slider-single-track-border": "--border-neutral-subtle",
3989
+ "--slider-single-track-border": "--border-neutral-medium",
3990
3990
  "--slider-single-track-border-width": "--border-width-1",
3991
3991
  "--slider-single-track-height": "--space-8",
3992
3992
  "--slider-single-track-radius": "--radius-full",
3993
3993
  "--slider-single-fill": "--surface-brand-high",
3994
3994
  "--slider-single-thumb-surface": "--surface-neutral-highest",
3995
- "--slider-single-thumb-border": "--border-brand",
3995
+ "--slider-single-thumb-border": "--border-brand-medium",
3996
3996
  "--slider-single-thumb-border-width": "--border-width-2",
3997
3997
  "--slider-single-thumb-size": "--font-size-lg",
3998
3998
  "--slider-single-thumb-radius": "--radius-full",
@@ -4004,13 +4004,13 @@
4004
4004
  "--slider-single-disabled-thumb-surface": "--surface-neutral-lower",
4005
4005
  "--slider-single-disabled-thumb-border": "--border-neutral",
4006
4006
  "--slider-range-track-surface": "--surface-neutral-lowest",
4007
- "--slider-range-track-border": "--border-neutral-subtle",
4007
+ "--slider-range-track-border": "--border-neutral-medium",
4008
4008
  "--slider-range-track-border-width": "--border-width-1",
4009
4009
  "--slider-range-track-height": "--space-8",
4010
4010
  "--slider-range-track-radius": "--radius-full",
4011
4011
  "--slider-range-fill": "--surface-brand-high",
4012
4012
  "--slider-range-thumb-surface": "--surface-neutral-highest",
4013
- "--slider-range-thumb-border": "--border-brand",
4013
+ "--slider-range-thumb-border": "--border-brand-medium",
4014
4014
  "--slider-range-thumb-border-width": "--border-width-2",
4015
4015
  "--slider-range-thumb-size": "--font-size-lg",
4016
4016
  "--slider-range-thumb-radius": "--radius-full",
@@ -75,7 +75,7 @@
75
75
  // -secondary/-tertiary scale); `--text-brand` is the brand-family text.
76
76
  :global(:root) {
77
77
  /* Brand */
78
- --badge-brand-surface: var(--surface-brand);
78
+ --badge-brand-surface: var(--surface-brand-low);
79
79
  --badge-brand-text: var(--text-brand);
80
80
  --badge-brand-border: var(--border-brand);
81
81
  --badge-brand-text-font-family: var(--font-sans);
@@ -136,7 +136,7 @@
136
136
 
137
137
  /* Canvas */
138
138
  --badge-canvas-surface: var(--surface-canvas);
139
- --badge-canvas-text: var(--text-canvas);
139
+ --badge-canvas-text: var(--text-secondary);
140
140
  --badge-canvas-border: var(--border-canvas);
141
141
  --badge-canvas-text-font-family: var(--font-sans);
142
142
  --badge-canvas-text-font-size: var(--font-size-md);
@@ -195,7 +195,7 @@
195
195
  --badge-warning-icon-size: var(--icon-size-sm);
196
196
 
197
197
  /* Danger */
198
- --badge-danger-surface: var(--surface-danger);
198
+ --badge-danger-surface: var(--surface-danger-low);
199
199
  --badge-danger-text: var(--text-danger);
200
200
  --badge-danger-border: var(--border-danger);
201
201
  --badge-danger-text-font-family: var(--font-sans);
@@ -2,8 +2,8 @@
2
2
  import type { CatalogueEntry } from '../../editor/component-editor/scaffolding/types';
3
3
 
4
4
  export const catalogue = {
5
- description: 'A labelled action.',
6
- whenToUse: 'an action that needs a word to be unambiguous.',
5
+ description: 'A labelled trigger.',
6
+ whenToUse: 'a trigger that needs a word to be unambiguous. It runs an action or opens another page, such as View details or Create project.',
7
7
  whenNotToUse: [
8
8
  { when: 'the glyph alone is plain, and space is short.', use: 'iconbutton' },
9
9
  { when: 'the actions confirm or cancel an inline edit.', use: 'inlineeditactions' },
@@ -11,7 +11,7 @@
11
11
  constraints: [
12
12
  'One primary Button per page.',
13
13
  'A danger Button opens a Dialog to confirm before it runs.',
14
- 'A link to another page is an anchor.',
14
+ 'A link inside running text is an anchor.',
15
15
  ],
16
16
  props: {
17
17
  variant: 'one primary per page; secondary for the rest; outline for a tertiary action; danger for a destructive one.',
@@ -542,4 +542,10 @@
542
542
  margin-left: var(--space-4);
543
543
  }
544
544
  }
545
+
546
+ @media (prefers-reduced-motion: reduce) {
547
+ .button::before {
548
+ display: none;
549
+ }
550
+ }
545
551
  </style>
@@ -46,7 +46,9 @@
46
46
  </script>
47
47
 
48
48
  <div class="codesnippet {className}">
49
- <code class="code">{code}</code>
49
+ <!-- A long snippet scrolls sideways, so the keyboard needs a stop on it. -->
50
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
51
+ <code class="code" tabindex="0">{code}</code>
50
52
  <Tooltip text={copied ? 'Copied' : ''} open={copied} position="top">
51
53
  <button
52
54
  type="button"
@@ -118,6 +120,11 @@
118
120
  scrollbar-color: var(--codesnippet-scrollbar-thumb) transparent;
119
121
  }
120
122
 
123
+ .code:focus-visible {
124
+ outline: none;
125
+ box-shadow: 0 0 0 var(--border-width-2) var(--border-neutral-strong);
126
+ }
127
+
121
128
  .code::-webkit-scrollbar {
122
129
  height: var(--codesnippet-scrollbar-border-width);
123
130
  }
@@ -73,7 +73,7 @@
73
73
  /* Per-variant color overrides. Default = inherit the same family/level the
74
74
  inner Badge uses; author can swap to a different level (e.g. -low) to
75
75
  reduce contrast against the host surface. */
76
- --cornerbadge-brand-surface: var(--surface-brand);
76
+ --cornerbadge-brand-surface: var(--surface-brand-low);
77
77
  --cornerbadge-brand-border: var(--border-brand);
78
78
  --cornerbadge-brand-text: var(--text-brand);
79
79
  --cornerbadge-accent-surface: var(--surface-accent);
@@ -87,7 +87,7 @@
87
87
  --cornerbadge-alternate-text: var(--text-alternate);
88
88
  --cornerbadge-canvas-surface: var(--surface-canvas);
89
89
  --cornerbadge-canvas-border: var(--border-canvas);
90
- --cornerbadge-canvas-text: var(--text-canvas);
90
+ --cornerbadge-canvas-text: var(--text-secondary);
91
91
  --cornerbadge-special-surface: var(--surface-special);
92
92
  --cornerbadge-special-border: var(--border-special);
93
93
  --cornerbadge-special-text: var(--text-special);
@@ -97,7 +97,7 @@
97
97
  --cornerbadge-warning-surface: var(--surface-warning);
98
98
  --cornerbadge-warning-border: var(--border-warning);
99
99
  --cornerbadge-warning-text: var(--text-warning);
100
- --cornerbadge-danger-surface: var(--surface-danger);
100
+ --cornerbadge-danger-surface: var(--surface-danger-low);
101
101
  --cornerbadge-danger-border: var(--border-danger);
102
102
  --cornerbadge-danger-text: var(--text-danger);
103
103
  --cornerbadge-info-surface: var(--surface-info);
@@ -15,6 +15,7 @@
15
15
  import { createEventDispatcher, tick } from 'svelte';
16
16
  import type { Snippet } from 'svelte';
17
17
  import { portal } from '../internal/portal';
18
+ import { tabbables, trapTab } from '../internal/focusTrap';
18
19
  import Button from './Button.svelte';
19
20
  import type { ButtonVariant, DialogButtonSpec } from './types';
20
21
 
@@ -64,6 +65,8 @@
64
65
  children,
65
66
  }: Props = $props();
66
67
 
68
+ const titleId = $props.id();
69
+
67
70
  let effectiveConfirmVariant = $derived(confirm?.variant ?? asVariant(confirmVarValue, 'primary'));
68
71
  let effectiveCancelVariant = $derived(cancel?.variant ?? asVariant(cancelVarValue, 'outline'));
69
72
 
@@ -75,6 +78,7 @@
75
78
  let confirmButtonRef: HTMLButtonElement = $state()!;
76
79
  let cancelButtonRef: HTMLButtonElement = $state()!;
77
80
  let closeButtonRef: HTMLButtonElement = $state()!;
81
+ let dialogEl: HTMLDivElement | undefined = $state();
78
82
 
79
83
  $effect(() => {
80
84
  if (typeof document === 'undefined') return;
@@ -86,21 +90,29 @@
86
90
  return () => obs.disconnect();
87
91
  });
88
92
 
89
- // Focus the primary button when dialog opens (skip in inline mode so the editor doesn't steal focus).
93
+ // The inline preview stays out of the trap, so the editor keeps its focus.
90
94
  $effect(() => {
91
- if (open && !inline) {
92
- tick().then(() => {
93
- if (confirm && confirmButtonRef && !confirm.disabled) {
94
- confirmButtonRef.focus();
95
- } else if (cancel && cancelButtonRef) {
96
- cancelButtonRef.focus();
97
- } else if (closeButtonRef) {
98
- closeButtonRef.focus();
99
- }
100
- });
101
- }
95
+ if (!open || inline) return;
96
+ const returnTo = document.activeElement as HTMLElement | null;
97
+ tick().then(() => {
98
+ const target =
99
+ (confirm && !confirm.disabled && confirmButtonRef) ||
100
+ (cancel && cancelButtonRef) ||
101
+ closeButtonRef ||
102
+ (dialogEl && tabbables(dialogEl)[0]) ||
103
+ dialogEl;
104
+ target?.focus();
105
+ });
106
+ return () => returnTo?.focus();
102
107
  });
103
108
 
109
+ function holdTab(event: KeyboardEvent) {
110
+ if (event.key !== 'Tab' || !open || inline || !dialogEl) return;
111
+ if (trapTab(dialogEl, event)) return;
112
+ event.preventDefault();
113
+ dialogEl.focus();
114
+ }
115
+
104
116
  function handleConfirm() {
105
117
  if (confirm && !confirm.disabled) {
106
118
  confirm.onClick();
@@ -116,17 +128,34 @@
116
128
  open = false;
117
129
  }
118
130
  }
131
+
132
+ function closeOnEscape(event: KeyboardEvent) {
133
+ if (event.key !== 'Escape') return;
134
+ event.preventDefault();
135
+ handleCancel();
136
+ }
119
137
  </script>
120
138
 
139
+ <svelte:document onkeydown={holdTab} />
140
+
121
141
  {#if open}
122
142
  <!-- The fixed backdrop portals to <body> to escape transformed/isolated
123
143
  ancestors; the inline preview variant stays in flow. -->
124
144
  <div class="dialog-backdrop" class:inline use:portal={!inline}>
125
- <div class="dialog" style="width: {width}; max-width: {width};">
145
+ <div
146
+ bind:this={dialogEl}
147
+ class="dialog"
148
+ role="dialog"
149
+ aria-modal={inline ? undefined : true}
150
+ aria-labelledby={title ? titleId : undefined}
151
+ tabindex="-1"
152
+ style="width: {width}; max-width: {width};"
153
+ onkeydown={closeOnEscape}
154
+ >
126
155
  <div class="dialog-content">
127
156
  {#if title}
128
157
  <div class="dialog-header">
129
- <h3 class="dialog-title">{title}</h3>
158
+ <h3 class="dialog-title" id={titleId}>{title}</h3>
130
159
  <button
131
160
  bind:this={closeButtonRef}
132
161
  class="dialog-close"
@@ -5,7 +5,7 @@
5
5
  description: 'An action that an icon alone carries.',
6
6
  whenToUse: 'an action whose meaning is plain from the glyph, such as close, edit, or delete.',
7
7
  whenNotToUse: [
8
- { when: 'the action needs a word to be unambiguous.', use: 'button' },
8
+ { when: 'the trigger needs a word to be unambiguous.', use: 'button' },
9
9
  { when: 'the pair confirms or cancels an inline edit.', use: 'inlineeditactions' },
10
10
  ],
11
11
  props: {
@@ -144,4 +144,11 @@
144
144
  .image.force-hover img {
145
145
  transform: var(--image-zoom-enabled);
146
146
  }
147
+
148
+ @media (prefers-reduced-motion: reduce) {
149
+ .image,
150
+ img {
151
+ transition: none;
152
+ }
153
+ }
147
154
  </style>
@@ -12,6 +12,8 @@
12
12
  props: {
13
13
  thumbAspect:
14
14
  'the ratio the thumbnail box holds, when it should not be the image\'s own. The image still opens whole, at its own ratio.',
15
+ thumbSrc:
16
+ 'a smaller file for the closed thumbnail, when the full image is far larger than the tile. The lightbox still opens `src`.',
15
17
  },
16
18
  } satisfies CatalogueEntry;
17
19
  </script>
@@ -19,6 +21,7 @@
19
21
  <script lang="ts">
20
22
  import { onMount, tick, type Snippet } from 'svelte';
21
23
  import { portal } from '../internal/portal';
24
+ import { trapTab } from '../internal/focusTrap';
22
25
 
23
26
  interface GalleryImage {
24
27
  src: string;
@@ -39,8 +42,12 @@
39
42
  maxWidth?: number | string | undefined;
40
43
  /** The thumbnail box's ratio, when it differs from the image's own. */
41
44
  thumbAspect?: number | undefined;
45
+ /** A smaller file for the closed thumbnail; the modal still opens `src`. */
46
+ thumbSrc?: string | undefined;
42
47
  /** Closed-thumbnail object-fit. `cover` crops the thumbnail to fill its box;
43
48
  the expanded modal always uses `contain` so the whole image stays visible.
49
+ Unset, it follows the theme's `--imagelightbox-tile-object-fit`, which
50
+ defaults to `contain`.
44
51
  `cover` only crops when the thumbnail box differs from the image's shape
45
52
  (a `thumbAspect`, or a CSS-constrained container). */
46
53
  fit?: 'contain' | 'cover';
@@ -106,7 +113,8 @@
106
113
  images = undefined,
107
114
  maxWidth = undefined,
108
115
  thumbAspect = undefined,
109
- fit = 'contain',
116
+ thumbSrc = undefined,
117
+ fit = undefined,
110
118
  shadow = 'box',
111
119
  surface = undefined,
112
120
  border = undefined,
@@ -602,29 +610,12 @@
602
610
  if (!isGallery && scale <= MIN_SCALE) closeLightbox();
603
611
  }
604
612
 
605
- // Keep Tab inside the open dialog. The stage (tabindex -1) is excluded, so an
606
- // image-only lightbox holds focus on its close button.
607
- function trapTab(e: KeyboardEvent) {
608
- if (!modalEl) return;
609
- const f = [
610
- ...modalEl.querySelectorAll<HTMLElement>('button:not([disabled]), [href], [tabindex]:not([tabindex="-1"])'),
611
- ];
612
- if (f.length === 0) {
613
- e.preventDefault();
614
- stageEl?.focus();
615
- return;
616
- }
617
- const first = f[0];
618
- const last = f[f.length - 1];
619
- const active = document.activeElement as HTMLElement | null;
620
- const inside = active && modalEl.contains(active);
621
- if (e.shiftKey && (!inside || active === first)) {
622
- e.preventDefault();
623
- last.focus();
624
- } else if (!e.shiftKey && (!inside || active === last)) {
625
- e.preventDefault();
626
- first.focus();
627
- }
613
+ // The stage (tabindex -1) is no tab stop, so an image-only lightbox holds
614
+ // focus on its close button.
615
+ function onTab(e: KeyboardEvent) {
616
+ if (!modalEl || trapTab(modalEl, e)) return;
617
+ e.preventDefault();
618
+ stageEl?.focus();
628
619
  }
629
620
 
630
621
  onMount(() => {
@@ -633,7 +624,7 @@
633
624
  if (e.key === 'Escape') {
634
625
  closeLightbox();
635
626
  } else if (e.key === 'Tab') {
636
- trapTab(e);
627
+ onTab(e);
637
628
  } else if (isGallery && e.key === 'ArrowRight') {
638
629
  e.preventDefault();
639
630
  next();
@@ -694,7 +685,9 @@
694
685
  aria-expanded={open}
695
686
  onclick={openLightbox}
696
687
  >
697
- <img src={cover?.src} alt={cover?.alt} draggable="false" onload={onCoverLoad} />
688
+ <!-- A thumbnail's pixels understate the full image, so it never feeds the
689
+ measured size behind the tile aspect and the zoom caps. -->
690
+ <img src={thumbSrc ?? cover?.src} alt={cover?.alt} draggable="false" onload={thumbSrc ? undefined : onCoverLoad} />
698
691
  </button>
699
692
  {#if overlay}
700
693
  <!-- Beside the thumb, not in it: the thumb clips its corners. Hidden while
@@ -913,6 +906,13 @@
913
906
  transition: none;
914
907
  }
915
908
 
909
+ @media (prefers-reduced-motion: reduce) {
910
+ .image-lightbox-thumb,
911
+ .image-lightbox-attached {
912
+ transition: none;
913
+ }
914
+ }
915
+
916
916
  .image-lightbox-thumb:focus-visible {
917
917
  outline: 2px solid var(--border-brand-medium);
918
918
  outline-offset: 2px;
@@ -165,7 +165,7 @@
165
165
 
166
166
  /* Default */
167
167
  --input-default-surface: var(--surface-neutral-lower);
168
- --input-default-border: var(--border-neutral);
168
+ --input-default-border: var(--border-neutral-medium);
169
169
  --input-default-text: var(--text-primary);
170
170
  --input-default-text-font-family: var(--font-sans);
171
171
  --input-default-text-font-size: var(--font-size-sm);
@@ -305,6 +305,13 @@
305
305
  outline: var(--input-error-border-width) solid var(--input-error-border);
306
306
  }
307
307
 
308
+ /* The red outline stays put on focus, so focus adds a neutral ring outside
309
+ it. The spread covers the outline's width, which paints over the shadow. */
310
+ .input-field.invalid .input-control:focus,
311
+ .input-field.invalid.force-focus .input-control {
312
+ box-shadow: 0 0 0 calc(var(--input-error-border-width) + var(--border-width-2)) var(--border-neutral-strong);
313
+ }
314
+
308
315
  .input-icon {
309
316
  color: var(--input-default-icon);
310
317
  font-size: var(--input-default-icon-size);
@@ -40,6 +40,8 @@
40
40
  minWidth?: string;
41
41
  /** ARIA role on the wrapper. `listbox` for select-like menus; `menu` for command menus. */
42
42
  role?: 'listbox' | 'menu';
43
+ /** The list's accessible name, such as the text of the Button that opens it. */
44
+ ariaLabel?: string;
43
45
  /** Selection callback. */
44
46
  onchange?: (value: string) => void;
45
47
  }
@@ -51,6 +53,7 @@
51
53
  forceHoverValue = null,
52
54
  minWidth = undefined,
53
55
  role = 'listbox',
56
+ ariaLabel = undefined,
54
57
  onchange,
55
58
  }: Props = $props();
56
59
 
@@ -67,16 +70,38 @@
67
70
  onchange?.(v);
68
71
  dispatch('change', v);
69
72
  }
73
+
74
+ const buttons: HTMLButtonElement[] = $state([]);
75
+
76
+ // Roving tab stop: the list takes one Tab press, on the selected item.
77
+ let tabStop = $derived(
78
+ items.find((i) => i.value === value && !i.disabled)?.value ?? items.find((i) => !i.disabled)?.value,
79
+ );
80
+
81
+ function moveFocus(event: KeyboardEvent, from: number) {
82
+ const enabled = items.flatMap((item, i) => (item.disabled ? [] : [i]));
83
+ const at = enabled.indexOf(from);
84
+ const to = {
85
+ ArrowDown: enabled[(at + 1) % enabled.length],
86
+ ArrowUp: enabled[(at - 1 + enabled.length) % enabled.length],
87
+ Home: enabled[0],
88
+ End: enabled[enabled.length - 1],
89
+ }[event.key];
90
+ if (to === undefined) return;
91
+ event.preventDefault();
92
+ buttons[to].focus();
93
+ }
70
94
  </script>
71
95
 
72
96
  <ul
73
97
  style:--menuselect-hover-tint-enabled={hoverTintValue}
74
98
  class="menuselect"
75
99
  {role}
100
+ aria-label={ariaLabel}
76
101
  style:min-width={minWidth}
77
102
  >
78
- {#each items as item (item.value)}
79
- <li>
103
+ {#each items as item, i (item.value)}
104
+ <li role="none">
80
105
  <button
81
106
  type="button"
82
107
  class="menuselect-item"
@@ -85,7 +110,10 @@
85
110
  disabled={item.disabled}
86
111
  role={role === 'listbox' ? 'option' : 'menuitem'}
87
112
  aria-selected={role === 'listbox' ? value === item.value : undefined}
113
+ tabindex={item.value === tabStop ? 0 : -1}
114
+ bind:this={buttons[i]}
88
115
  onclick={() => select(item.value)}
116
+ onkeydown={(event) => moveFocus(event, i)}
89
117
  >
90
118
  {#if item.icon}<i class="menuselect-icon {item.icon}" aria-hidden="true"></i>{/if}
91
119
  <span class="menuselect-label">{item.label}</span>
@@ -188,7 +188,7 @@
188
188
  --notification-warning-text-line-height: var(--line-height-normal);
189
189
 
190
190
  /* Danger */
191
- --notification-danger-surface: var(--surface-danger);
191
+ --notification-danger-surface: var(--surface-danger-low);
192
192
  --notification-danger-action-surface: var(--surface-neutral-lowest);
193
193
  --notification-danger-border: var(--border-danger);
194
194
  --notification-danger-border-width: var(--border-width-1);
@@ -135,4 +135,10 @@
135
135
  border-radius: var(--progressbar-radius);
136
136
  transition: width 0.4s ease;
137
137
  }
138
+
139
+ @media (prefers-reduced-motion: reduce) {
140
+ .progress-fill {
141
+ transition: none;
142
+ }
143
+ }
138
144
  </style>