@motion-proto/live-tokens 0.97.0 → 0.99.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/.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 +339 -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 +144 -19
  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 +204 -3
  14. package/dist-plugin/index.js +306 -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 +5 -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 +40 -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 +164 -0
  56. package/src/editor/core/a11y/a11yReportTypes.ts +77 -0
  57. package/src/editor/core/a11y/contrastFailures.ts +22 -0
  58. package/src/editor/core/a11y/contrastFixes.ts +41 -0
  59. package/src/editor/core/components/contrastPairRegistry.ts +18 -0
  60. package/src/editor/core/palettes/contrast.ts +44 -0
  61. package/src/editor/core/palettes/contrastPairs.ts +82 -0
  62. package/src/editor/core/palettes/resolveColor.ts +77 -0
  63. package/src/editor/core/store/editorStore.ts +4 -0
  64. package/src/editor/core/store/editorViewStore.ts +2 -2
  65. package/src/editor/core/store/unsavedEdits.ts +10 -0
  66. package/src/editor/core/themes/liveStateStream.ts +7 -1
  67. package/src/editor/core/themes/parsers/gradient.ts +5 -2
  68. package/src/editor/docs/chapters.ts +1 -0
  69. package/src/editor/docs/content/accessibility.md +411 -0
  70. package/src/editor/docs/content/editing-tokens.md +32 -1
  71. package/src/editor/docs/content.generated.ts +2 -1
  72. package/src/editor/pages/ComponentEditorPage.svelte +11 -1
  73. package/src/editor/pages/EditorShell.svelte +98 -10
  74. package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
  75. package/src/editor/skill-atlas/trees/check-compliance.ts +3 -3
  76. package/src/editor/skill-atlas/trees/create-component.ts +12 -12
  77. package/src/editor/styles/ui-editor.css +2 -0
  78. package/src/editor/ui/EditorViewSwitcher.svelte +15 -2
  79. package/src/editor/ui/GradientEditor.svelte +9 -1
  80. package/src/editor/ui/ThemePanel.svelte +4 -4
  81. package/src/editor/ui/UIContrastReadout.svelte +186 -0
  82. package/src/editor/ui/UIInfoPopover.svelte +16 -2
  83. package/src/editor/ui/UIPaletteSelector.svelte +9 -0
  84. package/src/editor/ui/UITokenSelector.svelte +26 -2
  85. package/src/editor/ui/a11y/A11yByEye.svelte +264 -0
  86. package/src/editor/ui/a11y/A11yComponentIssues.svelte +121 -0
  87. package/src/editor/ui/a11y/A11yFindingRow.svelte +119 -0
  88. package/src/editor/ui/a11y/A11yIssueRow.svelte +152 -0
  89. package/src/editor/ui/a11y/A11yOverview.svelte +246 -0
  90. package/src/editor/ui/a11y/A11yPanel.svelte +153 -0
  91. package/src/editor/ui/a11y/A11yPropertyMark.svelte +84 -0
  92. package/src/editor/ui/a11y/A11yRailMark.svelte +41 -0
  93. package/src/editor/ui/a11y/AccessibilityTab.svelte +321 -0
  94. package/src/editor/ui/a11y/a11yView.ts +257 -0
  95. package/src/editor/ui/contrastReadout.ts +45 -0
  96. package/src/live-tokens/data/colors-and-type/halloween.json +1 -1
  97. package/src/live-tokens/data/themes/autumn.json +35 -35
  98. package/src/live-tokens/data/themes/halloween.json +36 -36
  99. package/src/live-tokens/data/themes/midnight-study.json +35 -35
  100. package/src/live-tokens/data/themes/ocean.json +35 -35
  101. package/src/live-tokens/data/themes/royal-velvet.json +35 -35
  102. package/src/live-tokens/data/themes/sketchy.json +35 -35
  103. package/src/live-tokens/data/themes/spring-meadow.json +35 -35
  104. package/src/live-tokens/data/themes/sunset.json +35 -35
  105. package/src/system/components/Badge.svelte +3 -3
  106. package/src/system/components/Button.svelte +12 -6
  107. package/src/system/components/CodeSnippet.svelte +8 -1
  108. package/src/system/components/CornerBadge.svelte +3 -3
  109. package/src/system/components/Dialog.svelte +43 -14
  110. package/src/system/components/IconButton.svelte +1 -1
  111. package/src/system/components/Image.svelte +7 -0
  112. package/src/system/components/ImageLightbox.svelte +18 -25
  113. package/src/system/components/InlineEditActions.svelte +1 -1
  114. package/src/system/components/Input.svelte +9 -2
  115. package/src/system/components/MenuSelect.svelte +31 -3
  116. package/src/system/components/Notification.svelte +1 -1
  117. package/src/system/components/ProgressBar.svelte +7 -1
  118. package/src/system/components/RadioButton.svelte +36 -3
  119. package/src/system/components/SegmentedControl.svelte +36 -2
  120. package/src/system/components/SlidePager.svelte +3 -3
  121. package/src/system/components/Slider.svelte +19 -15
  122. package/src/system/components/TabBar.svelte +33 -3
  123. package/src/system/components/Toggle.svelte +3 -3
  124. package/src/system/components/VideoFrame.svelte +1 -1
  125. package/src/system/components/VideoLightbox.svelte +2 -2
  126. package/src/system/internal/focusTrap.ts +35 -0
  127. package/src/testing-js/__aria__/badge.yml +40 -0
  128. package/src/testing-js/__aria__/button.yml +88 -0
  129. package/src/testing-js/__aria__/callout.yml +12 -0
  130. package/src/testing-js/__aria__/card.yml +8 -0
  131. package/src/testing-js/__aria__/codesnippet.yml +21 -0
  132. package/src/testing-js/__aria__/collapsiblesection.yml +23 -0
  133. package/src/testing-js/__aria__/cornerbadge.yml +24 -0
  134. package/src/testing-js/__aria__/dialog.yml +42 -0
  135. package/src/testing-js/__aria__/iconbutton.yml +57 -0
  136. package/src/testing-js/__aria__/image.yml +10 -0
  137. package/src/testing-js/__aria__/imagelightbox.yml +50 -0
  138. package/src/testing-js/__aria__/inlineeditactions.yml +16 -0
  139. package/src/testing-js/__aria__/input.yml +61 -0
  140. package/src/testing-js/__aria__/menuselect.yml +30 -0
  141. package/src/testing-js/__aria__/notification.yml +30 -0
  142. package/src/testing-js/__aria__/panel.yml +2 -0
  143. package/src/testing-js/__aria__/progressbar.yml +2 -0
  144. package/src/testing-js/__aria__/radiobutton.yml +20 -0
  145. package/src/testing-js/__aria__/sectiondivider.yml +9 -0
  146. package/src/testing-js/__aria__/segmentedcontrol.yml +35 -0
  147. package/src/testing-js/__aria__/sidenavigation.yml +304 -0
  148. package/src/testing-js/__aria__/slidepager.yml +72 -0
  149. package/src/testing-js/__aria__/slider.yml +28 -0
  150. package/src/testing-js/__aria__/tabbar.yml +35 -0
  151. package/src/testing-js/__aria__/table.yml +144 -0
  152. package/src/testing-js/__aria__/toggle.yml +12 -0
  153. package/src/testing-js/__aria__/tooltip.yml +2 -0
  154. package/src/testing-js/__aria__/videoframe.yml +6 -0
  155. package/src/testing-js/__aria__/videolightbox.yml +12 -0
  156. package/src/testing-js/{chunk-WIZ6W7UT.js → chunk-3WCLMOM7.js} +2 -2
  157. package/src/testing-js/{chunk-NB3NZRBM.js → chunk-7CM5D7PF.js} +5 -1
  158. package/src/testing-js/chunk-7CM5D7PF.js.map +1 -0
  159. package/src/testing-js/{chunk-X6KHEVIG.js → chunk-7QXUJRSO.js} +97 -29
  160. package/src/testing-js/chunk-7QXUJRSO.js.map +1 -0
  161. package/src/testing-js/chunk-GZKUYLDW.js +193 -0
  162. package/src/testing-js/chunk-GZKUYLDW.js.map +1 -0
  163. package/src/testing-js/chunk-RTZQLW3F.js +649 -0
  164. package/src/testing-js/chunk-RTZQLW3F.js.map +1 -0
  165. package/src/testing-js/{chunk-SHBYZUPD.js → chunk-RWOT2XDB.js} +212 -15
  166. package/src/testing-js/chunk-RWOT2XDB.js.map +1 -0
  167. package/src/testing-js/{chunk-GKO27NGN.js → chunk-THWGMSRH.js} +2 -2
  168. package/src/testing-js/{chunk-3UKGXCDL.js → chunk-YVVR3GJZ.js} +1 -1
  169. package/src/testing-js/chunk-YVVR3GJZ.js.map +1 -0
  170. package/src/testing-js/component-a11y.contract.js +313 -0
  171. package/src/testing-js/component-a11y.contract.js.map +1 -0
  172. package/src/testing-js/component-alias.contract.js +2 -2
  173. package/src/testing-js/component-behavior.contract.js +2 -2
  174. package/src/testing-js/component-editor.contract.js +6 -5
  175. package/src/testing-js/component-editor.contract.js.map +1 -1
  176. package/src/testing-js/component-render.contract.js +10 -6
  177. package/src/testing-js/component-render.contract.js.map +1 -1
  178. package/src/testing-js/index.d.ts +44 -16
  179. package/src/testing-js/index.js +11 -11
  180. package/src/testing-js/index.js.map +1 -1
  181. package/src/testing-js/page-compliance.contract.js +132 -13
  182. package/src/testing-js/page-compliance.contract.js.map +1 -1
  183. package/src/testing-js/vitest.js +3 -3
  184. package/template/_gitignore +3 -0
  185. package/template/package.json +9 -0
  186. package/src/testing-js/chunk-3UKGXCDL.js.map +0 -1
  187. package/src/testing-js/chunk-NB3NZRBM.js.map +0 -1
  188. package/src/testing-js/chunk-SHBYZUPD.js.map +0 -1
  189. package/src/testing-js/chunk-X6KHEVIG.js.map +0 -1
  190. /package/src/testing-js/{chunk-WIZ6W7UT.js.map → chunk-3WCLMOM7.js.map} +0 -0
  191. /package/src/testing-js/{chunk-GKO27NGN.js.map → chunk-THWGMSRH.js.map} +0 -0
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildSiblings } from './scaffolding/siblings';
3
3
  import type { Token } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'inlineeditactions';
6
7
 
@@ -38,6 +39,17 @@
38
39
  [`--inlineeditactions-${btn}-default-padding`, btn] as const,
39
40
  [`--inlineeditactions-${btn}-default-icon-size`, btn] as const,
40
41
  ]));
42
+
43
+ // Each button holds a glyph and no text, so its color is a non-text pair.
44
+ function buttonContrastPairs(btn: Button): ContrastPair[] {
45
+ return (['default', 'hover'] as const).map((state) => ({
46
+ foreground: `--inlineeditactions-${btn}-${state}-text`,
47
+ backdrop: [`--inlineeditactions-${btn}-${state}-surface`],
48
+ role: 'non-text',
49
+ }));
50
+ }
51
+
52
+ export const contrastPairs: ContrastPair[] = buttons.flatMap(buttonContrastPairs);
41
53
  </script>
42
54
 
43
55
  <script lang="ts">
@@ -66,7 +78,7 @@
66
78
  {#snippet children({ activeState })}
67
79
  {@const forceClass = activeState === 'hover' ? 'force-hover' : ''}
68
80
  <div class="inline-edit-demo-row">
69
- <span style="color: var(--text-secondary);">Editing value...</span>
81
+ <span style="color: var(--text-secondary);" data-preview-demo>Editing value...</span>
70
82
  <InlineEditActions
71
83
  onsave={() => {}}
72
84
  oncancel={() => {}}
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupTokens, buildTypeGroupShareableContexts } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'input';
6
7
 
@@ -146,6 +147,28 @@
146
147
  // Silence unused-import lint without changing behavior; buildTypeGroupTokens stays available
147
148
  // for editors that want auto-derived color rows, but we keep ours hand-built for slot-prefixed groupKeys.
148
149
  void buildTypeGroupTokens;
150
+
151
+ function textPair(foreground: string, state: string, backdrop: string[]): ContrastPair {
152
+ const g = typeGroups[state][0];
153
+ return { foreground, backdrop, role: 'text', fontSize: g.sizeVariable, fontWeight: g.weightVariable };
154
+ }
155
+
156
+ // The placeholder paints on each state's surface at that state's text size.
157
+ // The icon button takes the focused icon color on hover and focus while the
158
+ // field itself stays unfocused. Label, hint and error sit on no surface.
159
+ export const contrastPairs: ContrastPair[] = [
160
+ textPair('--input-default-text', 'default', ['--input-default-surface']),
161
+ textPair('--input-focused-text', 'focused', ['--input-focused-surface']),
162
+ { ...textPair('--input-disabled-text', 'disabled', ['--input-disabled-surface']), inactive: true },
163
+ { ...textPair('--input-default-placeholder', 'default', ['--input-default-surface']), context: 'default' },
164
+ { ...textPair('--input-default-placeholder', 'focused', ['--input-focused-surface']), context: 'focused' },
165
+ { ...textPair('--input-default-placeholder', 'disabled', ['--input-disabled-surface']), context: 'disabled', inactive: true },
166
+ { foreground: '--input-default-icon', backdrop: ['--input-default-surface'], role: 'non-text' },
167
+ { foreground: '--input-focused-icon', backdrop: ['--input-focused-surface'], role: 'non-text', context: 'focused' },
168
+ { foreground: '--input-focused-icon', backdrop: ['--input-default-surface'], role: 'non-text', context: 'icon button' },
169
+ { foreground: '--input-disabled-icon', backdrop: ['--input-disabled-surface'], role: 'non-text', inactive: true },
170
+ ...(['label', 'hint', 'error'] as const).map((slot) => textPair(`--input-${slot}`, slot, [])),
171
+ ];
149
172
  </script>
150
173
 
151
174
  <script lang="ts">
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupTokens, buildTypeGroupShareableContexts } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig, IntrinsicSpec } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'menuselect';
6
7
 
@@ -106,6 +107,29 @@
106
107
  default: { default: 'var(--color-transparent)' },
107
108
  },
108
109
  ];
110
+
111
+ // Every item sits on the menu surface. The check mark paints on the selected item alone.
112
+ const menu = '--menuselect-menu-surface';
113
+ function itemContrastPairs(s: 'default' | 'hover' | 'selected' | 'disabled'): ContrastPair[] {
114
+ const backdrop = s === 'hover'
115
+ ? ['--menuselect-hover-tint-enabled', '--menuselect-hover-surface', menu]
116
+ : [`--menuselect-${s}-surface`, menu];
117
+ const inactive = s === 'disabled' ? { inactive: true } : {};
118
+ const marks = s === 'selected' ? ['--menuselect-selected-icon', '--menuselect-selected-indicator'] : [`--menuselect-${s}-icon`];
119
+ return [
120
+ ...typeGroups[`${s} item`].map((g): ContrastPair => ({
121
+ foreground: g.colorVariable,
122
+ backdrop,
123
+ role: 'text',
124
+ fontSize: g.sizeVariable,
125
+ fontWeight: g.weightVariable,
126
+ ...inactive,
127
+ })),
128
+ ...marks.map((foreground): ContrastPair => ({ foreground, backdrop, role: 'non-text', ...inactive })),
129
+ ];
130
+ }
131
+
132
+ export const contrastPairs: ContrastPair[] = (['default', 'hover', 'selected', 'disabled'] as const).flatMap(itemContrastPairs);
109
133
  </script>
110
134
 
111
135
  <script lang="ts">
@@ -115,6 +139,7 @@
115
139
  import { editorState, setComponentAlias, clearComponentAlias } from '../core/store/editorStore';
116
140
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
117
141
  import Toggle from '../ui/Toggle.svelte';
142
+ import A11yPropertyMark from '../ui/a11y/A11yPropertyMark.svelte';
118
143
  import UIPaletteSelector from '../ui/UIPaletteSelector.svelte';
119
144
 
120
145
  // One switch. Turning the tint on also stands each hover surface down to its
@@ -178,7 +203,7 @@
178
203
  {#snippet extraPropertyRows(stateName)}
179
204
  {#if stateName === 'hover item'}
180
205
  <div class="property-row" data-token-variables="--menuselect-hover-tint-enabled">
181
- <span class="property-label">tint layer</span>
206
+ <span class="property-label">tint layer<A11yPropertyMark variables={['--menuselect-hover-tint-enabled']} /></span>
182
207
  <Toggle checked={tintOn} onchange={setTintOn} />
183
208
  </div>
184
209
  {#if tintOn}
@@ -203,6 +228,7 @@
203
228
  items={previewItems}
204
229
  value={previewValue}
205
230
  forceHoverValue={previewForceHover}
231
+ ariaLabel="Options"
206
232
  />
207
233
  {/snippet}
208
234
  </VariantGroup>
@@ -1,5 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import type { Token } from './scaffolding/types';
3
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
3
4
 
4
5
  export const component = 'notification';
5
6
  const variants = ['info', 'success', 'warning', 'danger'] as const;
@@ -67,6 +68,20 @@
67
68
  }
68
69
 
69
70
  const variantOptions = variants.map((v) => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }));
71
+
72
+ // The body color also paints the dismiss button on the header. The body has
73
+ // no surface, and size="small" sets its description in --font-size-sm.
74
+ function variantContrastPairs(v: Variant): ContrastPair[] {
75
+ const header = [`--notification-${v}-surface`];
76
+ return [
77
+ { foreground: `--notification-${v}-title`, backdrop: header, role: 'text', fontSize: `--notification-${v}-title-font-size`, fontWeight: `--notification-${v}-title-font-weight` },
78
+ { foreground: `--notification-${v}-icon`, backdrop: header, role: 'non-text' },
79
+ { foreground: `--notification-${v}-text`, backdrop: header, role: 'non-text', context: 'dismiss button' },
80
+ { foreground: `--notification-${v}-text`, backdrop: [], role: 'text', fontSize: '--font-size-sm', fontWeight: `--notification-${v}-text-font-weight`, context: 'body' },
81
+ ];
82
+ }
83
+
84
+ export const contrastPairs: ContrastPair[] = variants.flatMap(variantContrastPairs);
70
85
  </script>
71
86
 
72
87
  <script lang="ts">
@@ -58,7 +58,7 @@
58
58
  {#snippet children()}
59
59
  <div class="panel-demo">
60
60
  <Panel minHeight="6rem">
61
- <div class="content-placeholder">Content Placeholder</div>
61
+ <div class="content-placeholder" data-preview-demo>Content Placeholder</div>
62
62
  </Panel>
63
63
  </div>
64
64
  {/snippet}
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'progressbar';
6
7
 
@@ -71,6 +72,18 @@
71
72
  ...buildTypeGroupColorTokens(typeGroups, { component }),
72
73
  ...typeGroupTokens,
73
74
  ];
75
+
76
+ // The label and value sit on no surface. The fill paints on the track.
77
+ export const contrastPairs: ContrastPair[] = [
78
+ ...typeGroups.default.map((g): ContrastPair => ({
79
+ foreground: g.colorVariable,
80
+ backdrop: [],
81
+ role: 'text',
82
+ fontSize: g.sizeVariable,
83
+ fontWeight: g.weightVariable,
84
+ })),
85
+ { foreground: '--progressbar-fill', backdrop: ['--progressbar-track-surface'], role: 'non-text' },
86
+ ];
74
87
  </script>
75
88
 
76
89
  <script lang="ts">
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'radiobutton';
6
7
 
@@ -79,6 +80,24 @@
79
80
  ...buildTypeGroupColorTokens(typeGroups, { component, variants: Object.keys(states) }),
80
81
  ...typeGroupTokens,
81
82
  ];
83
+
84
+ // The button paints no surface without the `color` prop, and the default dot
85
+ // stays hidden.
86
+ function stateContrastPairs(s: 'default' | 'hover' | 'selected'): ContrastPair[] {
87
+ const marks = s === 'default' ? ['--radiobutton-default-dot-border-color'] : [`--radiobutton-${s}-dot-border-color`, `--radiobutton-${s}-dot-fill`];
88
+ return [
89
+ ...typeGroups[s].map((g): ContrastPair => ({
90
+ foreground: g.colorVariable,
91
+ backdrop: [],
92
+ role: 'text',
93
+ fontSize: g.sizeVariable,
94
+ fontWeight: g.weightVariable,
95
+ })),
96
+ ...marks.map((foreground): ContrastPair => ({ foreground, backdrop: [], role: 'non-text' })),
97
+ ];
98
+ }
99
+
100
+ export const contrastPairs: ContrastPair[] = (['default', 'hover', 'selected'] as const).flatMap(stateContrastPairs);
82
101
  </script>
83
102
 
84
103
  <script lang="ts">
@@ -109,7 +128,7 @@
109
128
  {#snippet children({ activeState })}
110
129
  {@const forceClass = activeState === 'hover' ? 'force-hover' : ''}
111
130
  {@const forceSelected = activeState === 'selected'}
112
- <div class="radio-demo-row">
131
+ <div class="radio-demo-row" role="radiogroup" aria-label="Priority">
113
132
  <RadioButton
114
133
  label="Defense"
115
134
  selected={forceSelected || selectedRadio === 'option-a'}
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildSiblings } from './scaffolding/siblings';
3
3
  import type { Token, TypeGroupConfig, IntrinsicSpec } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'sectiondivider';
6
7
 
@@ -193,6 +194,21 @@
193
194
  },
194
195
  ];
195
196
  const INTRINSIC_BY_KEY = new Map(intrinsics.map((i) => [i.key, i]));
197
+
198
+ // The hairline is decorative: the heading carries the section break, so
199
+ // WCAG 2.2 SC 1.4.11 sets no floor on it and it has no pair.
200
+ function variantContrastPairs(v: Variant): ContrastPair[] {
201
+ const backdrop = [`--sectiondivider-${v}-surface`];
202
+ return variantTypeGroups(v).map((g): ContrastPair => ({
203
+ foreground: g.colorVariable,
204
+ backdrop,
205
+ role: 'text',
206
+ fontSize: g.sizeVariable,
207
+ fontWeight: g.weightVariable,
208
+ }));
209
+ }
210
+
211
+ export const contrastPairs: ContrastPair[] = variants.flatMap((v) => variantContrastPairs(v.key));
196
212
  </script>
197
213
 
198
214
  <script lang="ts">
@@ -200,6 +216,7 @@
200
216
  import VariantGroup from './scaffolding/VariantGroup.svelte';
201
217
  import ComponentEditorBase from './scaffolding/ComponentEditorBase.svelte';
202
218
  import GradientEditor from '../ui/GradientEditor.svelte';
219
+ import A11yPropertyMark from '../ui/a11y/A11yPropertyMark.svelte';
203
220
  import { editorState, mutate, setComponentAlias, setComponentConfig } from '../core/store/editorStore';
204
221
  import { componentGradientSource } from '../core/store/gradientSource';
205
222
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
@@ -376,16 +393,19 @@
376
393
  description: {
377
394
  checked: getShowDescription(v),
378
395
  label: 'Show description',
396
+ variables: [`--sectiondivider-${v}-description-display`],
379
397
  onchange: (c: boolean) => setIntrinsic(v, 'description-display', c ? 'flex' : 'none'),
380
398
  },
381
399
  eyebrow: {
382
400
  checked: getShowEyebrow(v),
383
401
  label: 'Show eyebrow',
402
+ variables: [`--sectiondivider-${v}-eyebrow-display`],
384
403
  onchange: (c: boolean) => setIntrinsic(v, 'eyebrow-display', c ? 'block' : 'none'),
385
404
  },
386
405
  hairline: {
387
406
  checked: getShowHairline(v),
388
407
  label: 'Show',
408
+ variables: [`--sectiondivider-${v}-hairline`],
389
409
  onchange: (c: boolean) => setIntrinsic(v, 'hairline', c ? getHairlinePosition(v) : 'none'),
390
410
  },
391
411
  };
@@ -431,8 +451,8 @@
431
451
  </div>
432
452
  {/snippet}
433
453
  {#snippet extraPropertyRowsTop(_stateName)}
434
- <div class="property-row sd-intrinsic-row">
435
- <span class="property-label">alignment</span>
454
+ <div class="property-row sd-intrinsic-row" data-token-variables={`--sectiondivider-${v.key}-align`}>
455
+ <span class="property-label">alignment<A11yPropertyMark variables={[`--sectiondivider-${v.key}-align`]} /></span>
436
456
  <select
437
457
  class="sd-intrinsic-select"
438
458
  value={getAlign(v.key)}
@@ -462,18 +482,21 @@
462
482
  {/snippet}
463
483
  {#snippet elementExtras(elementName)}
464
484
  {#if elementName === 'eyebrow'}
465
- <label class="sd-element-check">
466
- <input
467
- type="checkbox"
468
- checked={getEyebrowUppercase(v.key)}
469
- onchange={(e) => setIntrinsic(v.key, 'eyebrow-text-transform', (e.currentTarget as HTMLInputElement).checked ? 'uppercase' : 'none')}
470
- />
471
- <span>All caps</span>
472
- </label>
485
+ <span class="sd-element-check-host" data-token-variables={`--sectiondivider-${v.key}-eyebrow-text-transform`}>
486
+ <label class="sd-element-check">
487
+ <input
488
+ type="checkbox"
489
+ checked={getEyebrowUppercase(v.key)}
490
+ onchange={(e) => setIntrinsic(v.key, 'eyebrow-text-transform', (e.currentTarget as HTMLInputElement).checked ? 'uppercase' : 'none')}
491
+ />
492
+ <span>All caps</span>
493
+ </label>
494
+ <A11yPropertyMark variables={[`--sectiondivider-${v.key}-eyebrow-text-transform`]} />
495
+ </span>
473
496
  {/if}
474
497
  {#if elementName === 'hairline'}
475
- <div class="property-row sd-intrinsic-row sd-hairline-position-row">
476
- <span class="property-label">position</span>
498
+ <div class="property-row sd-intrinsic-row sd-hairline-position-row" data-token-variables={`--sectiondivider-${v.key}-hairline`}>
499
+ <span class="property-label">position<A11yPropertyMark variables={[`--sectiondivider-${v.key}-hairline`]} /></span>
477
500
  <select
478
501
  class="sd-intrinsic-select"
479
502
  value={getHairlinePosition(v.key)}
@@ -551,6 +574,11 @@
551
574
  /* Inline boolean control inside an element-section (e.g. "All caps" under
552
575
  the eyebrow heading). Sits flush-left so it reads as a section property
553
576
  rather than aligning with the token grid below. */
577
+ :global(.sd-element-check-host) {
578
+ display: inline-flex;
579
+ align-items: center;
580
+ margin-bottom: var(--ui-space-4);
581
+ }
554
582
  :global(.sd-element-check) {
555
583
  display: inline-flex;
556
584
  align-items: center;
@@ -559,7 +587,6 @@
559
587
  color: var(--ui-text-secondary);
560
588
  cursor: pointer;
561
589
  user-select: none;
562
- margin-bottom: var(--ui-space-4);
563
590
  }
564
591
  :global(.sd-element-check:hover) { color: var(--ui-text-primary); }
565
592
  :global(.sd-element-check input) { margin: 0; cursor: pointer; }
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupTokens, buildTypeGroupShareableContexts } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig, IntrinsicSpec } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'segmentedcontrol';
6
7
 
@@ -140,6 +141,32 @@
140
141
  default: { default: 'var(--color-transparent)' },
141
142
  },
142
143
  ];
144
+
145
+ // Every option sits on the bar, and the default option paints no surface of
146
+ // its own. The small size sets every option's text in
147
+ // --segmentedcontrol-option-small-text-font-size, the smaller of its two sizes.
148
+ function optionContrastPairs(state: string, icon: string, backdrop: string[]): ContrastPair[] {
149
+ const inactive = state === 'disabled option' ? { inactive: true } : {};
150
+ return [
151
+ ...typeGroups[state].map((g): ContrastPair => ({
152
+ foreground: g.colorVariable,
153
+ backdrop,
154
+ role: 'text',
155
+ fontSize: '--segmentedcontrol-option-small-text-font-size',
156
+ fontWeight: g.weightVariable,
157
+ ...inactive,
158
+ })),
159
+ { foreground: icon, backdrop, role: 'non-text', ...inactive },
160
+ ];
161
+ }
162
+
163
+ const bar = '--segmentedcontrol-bar-surface';
164
+ export const contrastPairs: ContrastPair[] = [
165
+ ...optionContrastPairs('default option', '--segmentedcontrol-option-icon', [bar]),
166
+ ...optionContrastPairs('hover option', '--segmentedcontrol-option-hover-icon', ['--segmentedcontrol-hover-tint-enabled', '--segmentedcontrol-option-hover-surface', bar]),
167
+ ...optionContrastPairs('selected option', '--segmentedcontrol-selected-icon', ['--segmentedcontrol-selected-surface', bar]),
168
+ ...optionContrastPairs('disabled option', '--segmentedcontrol-disabled-icon', ['--segmentedcontrol-disabled-surface', bar]),
169
+ ];
143
170
  </script>
144
171
 
145
172
  <script lang="ts">
@@ -149,6 +176,7 @@
149
176
  import { editorState, setComponentAlias, clearComponentAlias } from '../core/store/editorStore';
150
177
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
151
178
  import Toggle from '../ui/Toggle.svelte';
179
+ import A11yPropertyMark from '../ui/a11y/A11yPropertyMark.svelte';
152
180
  import UIPaletteSelector from '../ui/UIPaletteSelector.svelte';
153
181
 
154
182
  // One switch. Turning the tint on also stands each hover surface down to its
@@ -214,7 +242,7 @@
214
242
  {#snippet extraPropertyRows(stateName)}
215
243
  {#if stateName === 'hover option'}
216
244
  <div class="property-row" data-token-variables="--segmentedcontrol-hover-tint-enabled">
217
- <span class="property-label">tint layer</span>
245
+ <span class="property-label">tint layer<A11yPropertyMark variables={['--segmentedcontrol-hover-tint-enabled']} /></span>
218
246
  <Toggle checked={tintOn} onchange={setTintOn} />
219
247
  </div>
220
248
  {#if tintOn}
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig, IntrinsicSpec } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'sidenavigation';
6
7
 
@@ -271,6 +272,43 @@
271
272
  default: { default: 'var(--color-transparent)' },
272
273
  },
273
274
  ];
275
+
276
+ // Every part sits on the panel, and a hover rule lays the tint over its
277
+ // surface. The section header's own CollapsibleSection surface belongs to
278
+ // that component and stays out of the stack.
279
+ const panel = '--sidenavigation-panel-surface';
280
+ const tint = '--sidenavigation-hover-tint-enabled';
281
+ const partStack = (part: string, s: string): string[] =>
282
+ s === 'hover' ? [tint, `--sidenavigation-${part}-hover-surface`] : [`--sidenavigation-${part}-${s}-surface`];
283
+ const textPairs = (groups: TypeGroupConfig[], backdrop: string[]): ContrastPair[] =>
284
+ groups.map((g) => ({ foreground: g.colorVariable, backdrop, role: 'text', fontSize: g.sizeVariable, fontWeight: g.weightVariable }));
285
+
286
+ // The toggle sits on the title, which a pointer on the toggle also hovers.
287
+ const toggleContrastPairs: ContrastPair[] = [
288
+ ...STATEFUL_STATES.map((title): ContrastPair => ({
289
+ foreground: '--sidenavigation-toggle-default-icon',
290
+ backdrop: ['--sidenavigation-toggle-default-surface', ...partStack('title', title), panel],
291
+ role: 'non-text',
292
+ context: `title ${title}`,
293
+ })),
294
+ ...(['hover', 'selected'] as const).map((title): ContrastPair => ({
295
+ foreground: '--sidenavigation-toggle-hover-icon',
296
+ backdrop: [...partStack('toggle', 'hover'), ...partStack('title', title), panel],
297
+ role: 'non-text',
298
+ context: `title ${title}`,
299
+ })),
300
+ ];
301
+
302
+ export const contrastPairs: ContrastPair[] = [
303
+ ...STATEFUL_STATES.flatMap((s): ContrastPair[] => [
304
+ ...textPairs(titleStateTypeGroups(s), ['--sidenavigation-title-label-surface', ...partStack('title', s), panel]),
305
+ ...textPairs(sectionStateTypeGroups(s), [...partStack('section', s), panel]),
306
+ ...textPairs(itemStateTypeGroups(s), [...partStack('item', s), panel]),
307
+ ...textPairs(footerStateTypeGroups(s), [...partStack('footer', s), panel]),
308
+ { foreground: `--sidenavigation-footer-${s}-icon`, backdrop: [...partStack('footer', s), panel], role: 'non-text' },
309
+ ]),
310
+ ...toggleContrastPairs,
311
+ ];
274
312
  </script>
275
313
 
276
314
  <script lang="ts">
@@ -280,6 +318,7 @@
280
318
  import { editorState, setComponentAlias, clearComponentAlias } from '../core/store/editorStore';
281
319
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
282
320
  import Toggle from '../ui/Toggle.svelte';
321
+ import A11yPropertyMark from '../ui/a11y/A11yPropertyMark.svelte';
283
322
  import UIPaletteSelector from '../ui/UIPaletteSelector.svelte';
284
323
 
285
324
  // One switch. Turning the tint on also stands each hover surface down to its
@@ -384,7 +423,7 @@
384
423
  {#snippet extraPropertyRows(stateName)}
385
424
  {#if stateName.endsWith(' / Hover')}
386
425
  <div class="property-row" data-token-variables="--sidenavigation-hover-tint-enabled">
387
- <span class="property-label">tint layer</span>
426
+ <span class="property-label">tint layer<A11yPropertyMark variables={['--sidenavigation-hover-tint-enabled']} /></span>
388
427
  <Toggle checked={tintOn} onchange={setTintOn} />
389
428
  </div>
390
429
  {#if tintOn}
@@ -1,5 +1,6 @@
1
1
  <script module lang="ts">
2
2
  import type { Token } from './scaffolding/types';
3
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
3
4
 
4
5
  export const component = 'slidepager';
5
6
 
@@ -46,6 +47,25 @@
46
47
  };
47
48
 
48
49
  export const allTokens: Token[] = Object.values(states).flat();
50
+
51
+ // The bar sits on the page inline and on the scrim when zoomed.
52
+ const scrim = '--slidepager-scrim-surface';
53
+ function controlContrastPairs(foreground: string, surface: string, inactive: boolean): ContrastPair[] {
54
+ const flag = inactive ? { inactive } : {};
55
+ return [
56
+ { foreground, backdrop: [surface], role: 'non-text', context: 'inline', ...flag },
57
+ { foreground, backdrop: [surface, scrim], role: 'non-text', context: 'zoomed', ...flag },
58
+ ];
59
+ }
60
+ const counter = { foreground: '--slidepager-counter-text', role: 'text', fontSize: '--slidepager-counter-font-size', fontWeight: '--slidepager-counter-font-weight' } as const;
61
+
62
+ export const contrastPairs: ContrastPair[] = [
63
+ ...controlContrastPairs('--slidepager-control-icon', '--slidepager-control-surface', false),
64
+ ...controlContrastPairs('--slidepager-control-hover-icon', '--slidepager-control-hover-surface', false),
65
+ ...controlContrastPairs('--slidepager-control-disabled-icon', '--slidepager-control-disabled-surface', true),
66
+ { ...counter, backdrop: [], context: 'inline' },
67
+ { ...counter, backdrop: [scrim], context: 'zoomed' },
68
+ ];
49
69
  </script>
50
70
 
51
71
  <script lang="ts">
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
  import { sliderVariants } from '../../system/components/Slider.svelte';
5
6
 
6
7
  export const component = 'slider';
@@ -109,6 +110,38 @@
109
110
  ]);
110
111
 
111
112
  const variantOptions = variants.map((v) => ({ value: v, label: v.charAt(0).toUpperCase() + v.slice(1) }));
113
+
114
+ // The fill paints on the track, and the thumb straddles the fill and the track.
115
+ function variantContrastPairs(v: Variant): ContrastPair[] {
116
+ const thumbPairs = (thumb: string, fill: string, track: string, inactive: boolean): ContrastPair[] => {
117
+ const flag = inactive ? { inactive } : {};
118
+ return [
119
+ { foreground: thumb, backdrop: [fill, track], role: 'non-text', context: 'fill', ...flag },
120
+ { foreground: thumb, backdrop: [track], role: 'non-text', context: 'track', ...flag },
121
+ ];
122
+ };
123
+ const track = `--slider-${v}-track-surface`;
124
+ const disabledTrack = `--slider-${v}-disabled-track-surface`;
125
+ return [
126
+ { foreground: `--slider-${v}-fill`, backdrop: [track], role: 'non-text' },
127
+ ...thumbPairs(`--slider-${v}-thumb-surface`, `--slider-${v}-fill`, track, false),
128
+ ...thumbPairs(`--slider-${v}-hover-thumb-surface`, `--slider-${v}-fill`, track, false),
129
+ { foreground: `--slider-${v}-disabled-fill`, backdrop: [disabledTrack], role: 'non-text', inactive: true },
130
+ ...thumbPairs(`--slider-${v}-disabled-thumb-surface`, `--slider-${v}-disabled-fill`, disabledTrack, true),
131
+ ];
132
+ }
133
+
134
+ // Both variants share the label and value, which sit on no surface.
135
+ export const contrastPairs: ContrastPair[] = [
136
+ ...typeGroups.default.map((g): ContrastPair => ({
137
+ foreground: g.colorVariable,
138
+ backdrop: [],
139
+ role: 'text',
140
+ fontSize: g.sizeVariable,
141
+ fontWeight: g.weightVariable,
142
+ })),
143
+ ...variants.flatMap(variantContrastPairs),
144
+ ];
112
145
  </script>
113
146
 
114
147
  <script lang="ts">
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig, IntrinsicSpec } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'tabbar';
6
7
 
@@ -93,6 +94,25 @@
93
94
  default: { default: 'var(--color-transparent)' },
94
95
  },
95
96
  ];
97
+
98
+ // The bottom indicator paints over its own tab's surface.
99
+ function tabContrastPairs(s: TabState): ContrastPair[] {
100
+ const backdrop = s === 'hover' ? ['--tabbar-hover-tint-enabled', '--tabbar-hover-surface'] : [`--tabbar-${s}-surface`];
101
+ const inactive = s === 'disabled' ? { inactive: true } : {};
102
+ return [
103
+ ...tabStateTypeGroups(s).map((g): ContrastPair => ({
104
+ foreground: g.colorVariable,
105
+ backdrop,
106
+ role: 'text',
107
+ fontSize: g.sizeVariable,
108
+ fontWeight: g.weightVariable,
109
+ ...inactive,
110
+ })),
111
+ { foreground: `--tabbar-${s}-border`, backdrop, role: 'non-text', ...inactive },
112
+ ];
113
+ }
114
+
115
+ export const contrastPairs: ContrastPair[] = tabStateNames.flatMap(tabContrastPairs);
96
116
  </script>
97
117
 
98
118
  <script lang="ts">
@@ -102,6 +122,7 @@
102
122
  import { editorState, setComponentAlias, clearComponentAlias } from '../core/store/editorStore';
103
123
  import { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
104
124
  import Toggle from '../ui/Toggle.svelte';
125
+ import A11yPropertyMark from '../ui/a11y/A11yPropertyMark.svelte';
105
126
  import UIPaletteSelector from '../ui/UIPaletteSelector.svelte';
106
127
 
107
128
  // One switch. Turning the tint on also stands each hover surface down to its
@@ -163,7 +184,7 @@
163
184
  {#snippet extraPropertyRows(stateName)}
164
185
  {#if stateName === 'hover tab'}
165
186
  <div class="property-row" data-token-variables="--tabbar-hover-tint-enabled">
166
- <span class="property-label">tint layer</span>
187
+ <span class="property-label">tint layer<A11yPropertyMark variables={['--tabbar-hover-tint-enabled']} /></span>
167
188
  <Toggle checked={tintOn} onchange={setTintOn} />
168
189
  </div>
169
190
  {#if tintOn}
@@ -177,7 +198,7 @@
177
198
  {#snippet children({ activeState })}
178
199
  {@const forceClass = activeState === 'hover tab' ? 'force-hover' : ''}
179
200
  <TabBar tabs={demoTabs} value={selectedDemoTab} class={forceClass} onchange={(id) => (selectedDemoTab = id)} />
180
- <div class="tab-content-demo">
201
+ <div class="tab-content-demo" data-preview-demo>
181
202
  <p style="margin: 0;">placeholder tab content</p>
182
203
  </div>
183
204
  {/snippet}
@@ -1,6 +1,7 @@
1
1
  <script module lang="ts">
2
2
  import { buildTypeGroupColorTokens, buildTypeGroupFontTokens } from './scaffolding/buildTypeGroupTokens';
3
3
  import type { Token, TypeGroupConfig } from './scaffolding/types';
4
+ import type { ContrastPair } from '../core/palettes/contrastPairs';
4
5
 
5
6
  export const component = 'table';
6
7
 
@@ -72,6 +73,19 @@
72
73
  ...buildTypeGroupColorTokens(typeGroups, derivation),
73
74
  ...buildTypeGroupFontTokens(typeGroups, derivation),
74
75
  ];
76
+
77
+ // Every cell sits on the wrapper. Even rows lay the stripe over the row surface.
78
+ const wrapper = '--table-default-surface';
79
+ const textPair = (state: string, backdrop: string[]): ContrastPair => {
80
+ const g = typeGroups[state][0];
81
+ return { foreground: g.colorVariable, backdrop, role: 'text', fontSize: g.sizeVariable, fontWeight: g.weightVariable };
82
+ };
83
+
84
+ export const contrastPairs: ContrastPair[] = [
85
+ textPair('header', ['--table-default-header-surface', wrapper]),
86
+ { ...textPair('cell', ['--table-default-row-surface', wrapper]), context: 'odd rows' },
87
+ { ...textPair('cell', ['--table-default-row-stripe-surface', '--table-default-row-surface', wrapper]), context: 'even rows' },
88
+ ];
75
89
  </script>
76
90
 
77
91
  <script lang="ts">