@teamblind-chorus/ui 1.2.0 → 2.0.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 (141) hide show
  1. package/README.md +3 -3
  2. package/agents/AGENTS.md +6 -6
  3. package/agents/DESIGN.md +245 -244
  4. package/agents/LOVABLE.md +40 -11
  5. package/agents/catalog.md +4 -4
  6. package/agents/components/avatar-rail/avatar-rail.md +2 -4
  7. package/agents/components/avatar-rail/avatar-rail.spec.json +10 -14
  8. package/agents/components/badge/role.md +7 -9
  9. package/agents/components/badge/role.spec.json +6 -6
  10. package/agents/components/badge/update.md +6 -8
  11. package/agents/components/badge/update.spec.json +5 -5
  12. package/agents/components/banner/banner.md +16 -18
  13. package/agents/components/banner/banner.spec.json +14 -14
  14. package/agents/components/bottom-sheet/bottom-sheet.md +4 -6
  15. package/agents/components/bottom-sheet/bottom-sheet.spec.json +5 -5
  16. package/agents/components/bubble/bubble.md +8 -10
  17. package/agents/components/bubble/bubble.spec.json +11 -11
  18. package/agents/components/button/button.md +1 -1
  19. package/agents/components/button/check.md +9 -11
  20. package/agents/components/button/check.spec.json +8 -10
  21. package/agents/components/button/fab.md +7 -9
  22. package/agents/components/button/fab.spec.json +10 -12
  23. package/agents/components/button/group.spec.json +4 -4
  24. package/agents/components/button/icon.md +21 -23
  25. package/agents/components/button/icon.spec.json +12 -14
  26. package/agents/components/button/standard.md +40 -42
  27. package/agents/components/button/standard.spec.json +20 -22
  28. package/agents/components/button/text.md +21 -23
  29. package/agents/components/button/text.spec.json +13 -15
  30. package/agents/components/button/toggle.md +7 -9
  31. package/agents/components/button/toggle.spec.json +10 -12
  32. package/agents/components/button/toolbar.md +24 -26
  33. package/agents/components/button/toolbar.spec.json +10 -12
  34. package/agents/components/carousel/carousel.md +1 -1
  35. package/agents/components/carousel/post.md +15 -21
  36. package/agents/components/carousel/post.spec.json +17 -17
  37. package/agents/components/carousel/profile.md +9 -45
  38. package/agents/components/carousel/profile.spec.json +17 -17
  39. package/agents/components/chip/chip.md +1 -1
  40. package/agents/components/chip/filter.md +22 -24
  41. package/agents/components/chip/filter.spec.json +17 -13
  42. package/agents/components/chip/tag.md +22 -24
  43. package/agents/components/chip/tag.spec.json +19 -15
  44. package/agents/components/dialog/dialog.md +1 -3
  45. package/agents/components/dialog/dialog.spec.json +3 -3
  46. package/agents/components/directory-list/directory-list.md +1 -3
  47. package/agents/components/directory-list/directory-list.spec.json +2 -2
  48. package/agents/components/divider/divider.family.json +1 -1
  49. package/agents/components/divider/divider.md +12 -14
  50. package/agents/components/divider/divider.spec.json +8 -8
  51. package/agents/components/empty-state/empty-state.md +9 -9
  52. package/agents/components/empty-state/empty-state.spec.json +14 -14
  53. package/agents/components/feed/ad.md +2 -4
  54. package/agents/components/feed/ad.spec.json +10 -10
  55. package/agents/components/feed/post.md +41 -43
  56. package/agents/components/feed/post.spec.json +35 -39
  57. package/agents/components/form-field/form-field.md +1 -1
  58. package/agents/components/form-field/input.md +32 -34
  59. package/agents/components/form-field/input.spec.json +34 -33
  60. package/agents/components/form-field/search.md +2 -4
  61. package/agents/components/form-field/search.spec.json +19 -18
  62. package/agents/components/form-field/select.md +18 -20
  63. package/agents/components/form-field/select.spec.json +30 -29
  64. package/agents/components/form-field/textarea.md +3 -5
  65. package/agents/components/form-field/textarea.spec.json +32 -31
  66. package/agents/components/header/main.md +4 -6
  67. package/agents/components/header/main.spec.json +3 -3
  68. package/agents/components/header/sub.md +6 -8
  69. package/agents/components/header/sub.spec.json +3 -3
  70. package/agents/components/list/accordion.md +34 -45
  71. package/agents/components/list/accordion.spec.json +20 -20
  72. package/agents/components/list/entry.md +59 -81
  73. package/agents/components/list/entry.spec.json +20 -23
  74. package/agents/components/list/list.md +2 -2
  75. package/agents/components/list/radio.md +13 -20
  76. package/agents/components/list/radio.spec.json +16 -20
  77. package/agents/components/list/standard.md +50 -72
  78. package/agents/components/list/standard.spec.json +18 -21
  79. package/agents/components/metadata/compact.md +4 -6
  80. package/agents/components/metadata/compact.spec.json +6 -6
  81. package/agents/components/metadata/metadata.md +1 -1
  82. package/agents/components/metadata/standard.md +12 -14
  83. package/agents/components/metadata/standard.spec.json +10 -10
  84. package/agents/components/nav-card/nav-card.md +25 -27
  85. package/agents/components/nav-card/nav-card.spec.json +19 -19
  86. package/agents/components/nav-list/nav-list.md +2 -8
  87. package/agents/components/nav-list/nav-list.spec.json +3 -3
  88. package/agents/components/navigation-bar/main.md +9 -11
  89. package/agents/components/navigation-bar/main.spec.json +6 -6
  90. package/agents/components/navigation-bar/search.md +6 -8
  91. package/agents/components/navigation-bar/search.spec.json +9 -9
  92. package/agents/components/navigation-bar/sub.md +9 -11
  93. package/agents/components/navigation-bar/sub.spec.json +7 -7
  94. package/agents/components/pagination/pagination.family.json +1 -1
  95. package/agents/components/pagination/pagination.md +3 -3
  96. package/agents/components/pagination/pagination.spec.json +5 -5
  97. package/agents/components/profile-header/profile-header.md +9 -11
  98. package/agents/components/profile-header/profile-header.spec.json +9 -9
  99. package/agents/components/progress/progress.family.json +1 -1
  100. package/agents/components/progress/progress.md +5 -5
  101. package/agents/components/progress/progress.spec.json +8 -8
  102. package/agents/components/side-sheet/side-sheet.md +11 -13
  103. package/agents/components/side-sheet/side-sheet.spec.json +3 -3
  104. package/agents/components/skeleton/skeleton.md +7 -9
  105. package/agents/components/skeleton/skeleton.spec.json +5 -5
  106. package/agents/components/spinner/spinner.family.json +1 -1
  107. package/agents/components/spinner/spinner.md +8 -10
  108. package/agents/components/spinner/spinner.spec.json +9 -9
  109. package/agents/components/status-tag/status-tag.md +7 -9
  110. package/agents/components/status-tag/status-tag.spec.json +5 -5
  111. package/agents/components/suggestion-list/suggestion-list.md +3 -7
  112. package/agents/components/suggestion-list/suggestion-list.spec.json +8 -12
  113. package/agents/components/switch/switch.md +12 -14
  114. package/agents/components/switch/switch.spec.json +17 -18
  115. package/agents/components/tab-bar/tab-bar.md +9 -11
  116. package/agents/components/tab-bar/tab-bar.spec.json +25 -27
  117. package/agents/components/tabs/rounded.md +6 -8
  118. package/agents/components/tabs/rounded.spec.json +17 -15
  119. package/agents/components/tabs/segmented.md +4 -6
  120. package/agents/components/tabs/segmented.spec.json +4 -8
  121. package/agents/components/tabs/underline.md +9 -11
  122. package/agents/components/tabs/underline.spec.json +14 -16
  123. package/agents/components/thumbnail/thumbnail.md +5 -7
  124. package/agents/components/thumbnail/thumbnail.spec.json +8 -8
  125. package/agents/components/toast/toast.md +5 -7
  126. package/agents/components/toast/toast.spec.json +3 -3
  127. package/agents/components/tooltip/tooltip.md +6 -8
  128. package/agents/components/tooltip/tooltip.spec.json +4 -4
  129. package/agents/tokens.usage.json +71 -226
  130. package/dist/index.cjs +212 -223
  131. package/dist/index.cjs.map +1 -1
  132. package/dist/index.d.cts +16 -16
  133. package/dist/index.d.ts +16 -16
  134. package/dist/index.js +212 -223
  135. package/dist/index.js.map +1 -1
  136. package/dist/styles.css +386 -387
  137. package/eslint/rules.js +7 -7
  138. package/package.json +2 -3
  139. package/agents/anti-patterns.md +0 -533
  140. package/agents/compose.md +0 -240
  141. package/agents/images.md +0 -66
@@ -75,24 +75,24 @@
75
75
  "default": {
76
76
  "background": "transparent",
77
77
  "border": null,
78
- "icon": "sys.color.onSurface",
78
+ "icon": "sys.color.icon.default",
79
79
  "note": "Transparent capsule with the glyph in `onSurface`. The canonical Icon Button chrome, used on every regular page surface."
80
80
  },
81
81
  "inverse": {
82
82
  "background": "transparent",
83
83
  "border": null,
84
- "icon": "sys.color.inverseOnSurface",
84
+ "icon": "sys.color.text.inverse",
85
85
  "note": "Mirror of `default` for use inside an inverse host (Toast, coach-mark, snackbar). Glyph paints in `inverseOnSurface` so it reads against the host's `inverseSurface` fill; state overlays mix from the same token so the recipe carries over without per-host tuning."
86
86
  }
87
87
  },
88
88
  "customGlyphColor": {
89
89
  "allowed": true,
90
- "description": "Outside the named `default` / `inverse` appearances, the glyph colour is open — the icon inherits `currentColor`, so any Chorus icon-paint token can be applied via inline `color` (e.g. `style={{ color: 'var(--sys-color-icon-yellow)' }}` for an active favorite star, `var(--sys-color-success)` for a confirm check, `var(--sys-color-icon-muted)` for the unpressed partner). State overlays still mix from the same token at the standard `hovered`/`pressed`/`focused` state opacities, so hover / pressed / focus carry over without per-host tuning.",
90
+ "description": "Outside the named `default` / `inverse` appearances, the glyph colour is open — the icon inherits `currentColor`, so any Chorus icon-paint token can be applied via inline `color` (e.g. `style={{ color: 'var(--sys-color-icon-accent-yellow-default)' }}` for an active favorite star, `var(--sys-color-icon-success)` for a confirm check, `var(--sys-color-icon-subtle)` for the unpressed partner). State overlays still mix from the same token at the standard `hovered`/`pressed`/`focused` state opacities, so hover / pressed / focus carry over without per-host tuning.",
91
91
  "constraints": [
92
- "Pick a Chorus colour token. Two valid sources: (a) a status-pair role (`sys.color.primary`, `sys.color.success`, `sys.color.error`, etc.) when the glyph travels with its own background; (b) the dedicated icon palette (`sys.color.icon.muted`, `sys.color.icon.yellow`, `sys.color.icon.red`, `sys.color.icon.blue`, `sys.color.icon.green`, `sys.color.icon.purple`) for standalone semantic glyphs. Reaching past sys into the raw palette (`ref.palette.yellow.500` etc.), raw hex, and Tailwind colour utilities are all forbidden.",
92
+ "Pick a Chorus colour token. Two valid sources: (a) a status-pair role (`sys.color.background.primary`, `sys.color.text.success`, `sys.color.text.danger`, etc.) when the glyph travels with its own background; (b) the dedicated icon palette (`sys.color.icon.subtle`, `sys.color.icon.accent.yellow.default`, `sys.color.icon.accent.red.default`, `sys.color.icon.accent.blue.default`, `sys.color.icon.accent.green.default`, `sys.color.icon.accent.purple.default`) for standalone semantic glyphs. Reaching past sys into the raw palette (`ref.palette.yellow.500` etc.), raw hex, and Tailwind colour utilities are all forbidden.",
93
93
  "Override the icon's `color` only — never `background`, never wrap in another element to restyle.",
94
94
  "Reach for a custom colour when the glyph itself carries semantic weight (favorite, success, warning, brand-tinted host). Otherwise stick to `default` / `inverse`.",
95
- "The `destructive` flavor (`sys.color.error`) is a named convenience for the same pattern."
95
+ "The `destructive` flavor (`sys.color.text.danger`) is a named convenience for the same pattern."
96
96
  ]
97
97
  },
98
98
  "flavors": {
@@ -102,12 +102,12 @@
102
102
  "default": {
103
103
  "background": "transparent",
104
104
  "border": null,
105
- "icon": "sys.color.error"
105
+ "icon": "sys.color.icon.danger"
106
106
  },
107
107
  "inverse": {
108
108
  "background": "transparent",
109
109
  "border": null,
110
- "icon": "sys.color.error"
110
+ "icon": "sys.color.icon.danger"
111
111
  }
112
112
  }
113
113
  }
@@ -138,11 +138,11 @@
138
138
  "layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
139
139
  "innerCounterRing": {
140
140
  "width": "sys.borderWidth.hairline",
141
- "color": "sys.color.focusInset"
141
+ "color": "sys.color.border.focused"
142
142
  },
143
143
  "outerRing": {
144
144
  "width": "sys.borderWidth.thin",
145
- "color": "sys.color.focus"
145
+ "color": "sys.color.border.focused"
146
146
  }
147
147
  },
148
148
  "note": "Keyboard-focus (:focus-visible) visual. Mirrors the `focusIndicator` block (the external-reader contract); kept here so spec-only renderers see focus in the states map. Composes over the lifecycle state the button is in; never via plain mouse click."
@@ -157,16 +157,14 @@
157
157
  "focusIndicator": {
158
158
  "description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the button is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
159
159
  "composition": "outward",
160
- "compositionReason": "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
160
+ "compositionReason": "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
161
161
  "overlay": {
162
162
  "color": "icon",
163
163
  "opacity": "sys.state.focus"
164
164
  },
165
165
  "ring": {
166
- "outerWidth": "sys.borderWidth.thin",
167
- "outerColor": "sys.color.focus",
168
- "insetWidth": "sys.borderWidth.hairline",
169
- "insetColor": "sys.color.focusInset"
166
+ "width": "sys.borderWidth.hairline",
167
+ "color": "sys.color.border.focused"
170
168
  },
171
169
  "trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
172
170
  },
@@ -22,9 +22,7 @@ import { Button } from '@teamblind-chorus/ui';
22
22
  </Button>
23
23
  ```
24
24
 
25
- ## Use cases
26
-
27
- ### Secondary
25
+ ## Secondary
28
26
 
29
27
  Lower-emphasis tier paired against `primary` — opposing actions (Cancel beside Save) or quieter alternatives. Safe to repeat on a single view.
30
28
 
@@ -38,7 +36,7 @@ import { Button } from '@teamblind-chorus/ui';
38
36
  </Button>
39
37
  ```
40
38
 
41
- ### Outlined
39
+ ## Outlined
42
40
 
43
41
  Bordered blue-on-transparent — supplementary option beside `primary` (*See more*, *Learn more*, *Skip for now*). For opposing paths use `secondary`.
44
42
 
@@ -52,9 +50,9 @@ import { Button } from '@teamblind-chorus/ui';
52
50
  </Button>
53
51
  ```
54
52
 
55
- ### Tertiary
53
+ ## Tertiary
56
54
 
57
- Neutral grey ghost — transparent at rest, label in `sys.color.onSurfaceVariant`. Reads as a button only on hover.
55
+ Neutral grey ghost — transparent at rest, label in `sys.color.text.subtle`. Reads as a button only on hover.
58
56
 
59
57
  ```preview
60
58
  button/standard/tertiary
@@ -66,7 +64,7 @@ import { Button } from '@teamblind-chorus/ui';
66
64
  </Button>
67
65
  ```
68
66
 
69
- ### With leading icon
67
+ ## Leading icon
70
68
 
71
69
  Optional context glyph before the label. Inherits label color via `currentColor` (`sys.icon.lg` on `large`, `sys.icon.md` on `medium`/`small`).
72
70
 
@@ -85,7 +83,7 @@ import { PlusIcon } from '@teamblind-chorus/ui/icons';
85
83
  </Button>
86
84
  ```
87
85
 
88
- ### Full width
86
+ ## Full width
89
87
 
90
88
  Stretched to fill the column (`width: 100%`). Default mobile shape for hero surfaces, empty states, onboarding, login. On wider surfaces, fall back to content-sized.
91
89
 
@@ -103,33 +101,7 @@ import { Button } from '@teamblind-chorus/ui';
103
101
  </Button>
104
102
  ```
105
103
 
106
- ### Group
107
-
108
- Compose adjacent Buttons with **`ButtonGroup`** instead of a hand-rolled wrapper — it owns the family's **8px** gap (`sys.layout.inline.md`). Horizontal (default): outlined left, primary right. Vertical (`orientation="vertical"`): primary top, secondary below.
109
-
110
- ```preview
111
- button/standard/group
112
- ---
113
- import { Button, ButtonGroup } from '@teamblind-chorus/ui';
114
-
115
- <ButtonGroup aria-label="Group example">
116
- <Button appearance="outlined" size="large">See more</Button>
117
- <Button appearance="primary" size="large">Confirm</Button>
118
- </ButtonGroup>
119
- ```
120
-
121
- ```preview
122
- button/standard/group-vertical
123
- ---
124
- import { Button, ButtonGroup } from '@teamblind-chorus/ui';
125
-
126
- <ButtonGroup orientation="vertical" aria-label="Group example">
127
- <Button appearance="primary" size="large" fullWidth>Save</Button>
128
- <Button appearance="secondary" size="large" fullWidth>Cancel</Button>
129
- </ButtonGroup>
130
- ```
131
-
132
- ### Docked action bar
104
+ ## Docked action bar
133
105
 
134
106
  `ButtonGroup` with **`variant="docked"`** — two **Large** Buttons combined into a footer bar pinned to the bottom of the app, the screen-level commit row for a flow (a picker, a form, a detail view). Full-bleed `sys.color.surface` with a **16px** inset (`sys.layout.container.md`); the two Buttons split the row equally at the family's 8px gap (`sys.layout.inline.md`). No top stroke — instead an upward `sys.elevation.sheet` shadow (the same one [Bottom Sheet](../bottom-sheet/bottom-sheet.md) / [Side Sheet](../side-sheet/side-sheet.md) cast) lifts it off the scrolling body, so content passing behind reads as a separate region. Supplementary **outlined** left, **primary** commit right. Renders in flow — like the other bars it must **not** self-pin (`position: sticky`/`fixed`); [Page Shell](../page-shell/page-shell.md) owns the pinning.
135
107
 
@@ -144,7 +116,7 @@ import { Button, ButtonGroup } from '@teamblind-chorus/ui';
144
116
  </ButtonGroup>
145
117
  ```
146
118
 
147
- An **optional text label** sits above the row — pass it via the `label` prop. It's a caption echoing what the Buttons act on (the current selection, a running total), rendered in `sys.typo.body.md` (**16px**) / `sys.color.onSurfaceVariant`, centered, separated from the row by `sys.layout.stack.md` (16px); an inline `<strong>` reads as the emphasized value in the full-strength on-surface tone.
119
+ An **optional text label** sits above the row — pass it via the `label` prop. It's a caption echoing what the Buttons act on (the current selection, a running total), rendered in `sys.typo.body.md` (**16px**) / `sys.color.text.subtle`, centered, separated from the row by `sys.layout.stack.md` (16px); an inline `<strong>` reads as the emphasized value in the full-strength on-surface tone.
148
120
 
149
121
  ```preview
150
122
  button/standard/docked-bar-labeled
@@ -161,7 +133,7 @@ import { Button, ButtonGroup } from '@teamblind-chorus/ui';
161
133
  </ButtonGroup>
162
134
  ```
163
135
 
164
- ### Truncation
136
+ ## Truncation
165
137
 
166
138
  When the column is narrower than the label, it clips with an ellipsis — Buttons are single-line by contract.
167
139
 
@@ -180,7 +152,7 @@ import { Button } from '@teamblind-chorus/ui';
180
152
  </Button>
181
153
  ```
182
154
 
183
- ### Focus indicator
155
+ ## Focus ring
184
156
 
185
157
  Standard keyboard-focus ring (see [Focus ring composition](../../DESIGN.md#focus-ring-composition)).
186
158
 
@@ -194,6 +166,32 @@ import { Button } from '@teamblind-chorus/ui';
194
166
  </Button>
195
167
  ```
196
168
 
169
+ ## Group
170
+
171
+ Compose adjacent Buttons with **`ButtonGroup`** instead of a hand-rolled wrapper — it owns the family's **8px** gap (`sys.layout.inline.md`). Horizontal (default): outlined left, primary right. Vertical (`orientation="vertical"`): primary top, secondary below.
172
+
173
+ ```preview
174
+ button/standard/group
175
+ ---
176
+ import { Button, ButtonGroup } from '@teamblind-chorus/ui';
177
+
178
+ <ButtonGroup aria-label="Group example">
179
+ <Button appearance="outlined" size="large">See more</Button>
180
+ <Button appearance="primary" size="large">Confirm</Button>
181
+ </ButtonGroup>
182
+ ```
183
+
184
+ ```preview
185
+ button/standard/group-vertical
186
+ ---
187
+ import { Button, ButtonGroup } from '@teamblind-chorus/ui';
188
+
189
+ <ButtonGroup orientation="vertical" aria-label="Group example">
190
+ <Button appearance="primary" size="large" fullWidth>Save</Button>
191
+ <Button appearance="secondary" size="large" fullWidth>Cancel</Button>
192
+ </ButtonGroup>
193
+ ```
194
+
197
195
  ## Slots
198
196
 
199
197
  - **label** — accessible name. Required, single line; long labels truncate.
@@ -207,10 +205,10 @@ A **destructive** flavor swaps `primary` → `error` across any appearance; rese
207
205
 
208
206
  | Appearance | Background | Border (1px) | Label color | Notes |
209
207
  |-------------|---------------|--------------------------|-----------------------------------|-----------------------------------------------------------------------|
210
- | `primary` | `sys.color.primary` | — | `sys.color.onPrimary` | Single highest-emphasis action; one per view. |
211
- | `secondary` | `sys.color.secondaryContainer` | — | `sys.color.onSecondaryContainer` | Lower-emphasis tier; opposing-action and quieter-alternative roles. |
212
- | `outlined` | `transparent` | `sys.color.primary` (`sys.borderWidth.hairline`) | `sys.color.primary` | Supplementary option beside `primary`. |
213
- | `tertiary` | `transparent` | — | `sys.color.onSurfaceVariant` | Lowest-emphasis neutral ghost. |
208
+ | `primary` | `sys.color.background.primary` | — | `sys.color.text.onFill` | Single highest-emphasis action; one per view. |
209
+ | `secondary` | `sys.color.background.neutral` | — | `sys.color.text.default` | Lower-emphasis tier; opposing-action and quieter-alternative roles. |
210
+ | `outlined` | `transparent` | `sys.color.background.primary` (`sys.borderWidth.hairline`) | `sys.color.background.primary` | Supplementary option beside `primary`. |
211
+ | `tertiary` | `transparent` | — | `sys.color.text.subtle` | Lowest-emphasis neutral ghost. |
214
212
 
215
213
  ## Sizes
216
214
 
@@ -92,55 +92,55 @@
92
92
  },
93
93
  "appearances": {
94
94
  "primary": {
95
- "background": "sys.color.primary",
95
+ "background": "sys.color.background.primary",
96
96
  "border": null,
97
- "label": "sys.color.onPrimary"
97
+ "label": "sys.color.text.onFill"
98
98
  },
99
99
  "secondary": {
100
- "background": "sys.color.secondaryContainer",
100
+ "background": "sys.color.background.neutral",
101
101
  "border": null,
102
- "label": "sys.color.onSecondaryContainer"
102
+ "label": "sys.color.text.default"
103
103
  },
104
104
  "outlined": {
105
105
  "background": "transparent",
106
106
  "border": {
107
107
  "width": "sys.borderWidth.hairline",
108
- "color": "sys.color.primary"
108
+ "color": "sys.color.text.link"
109
109
  },
110
- "label": "sys.color.primary"
110
+ "label": "sys.color.text.link"
111
111
  },
112
112
  "tertiary": {
113
113
  "background": "transparent",
114
114
  "border": null,
115
- "label": "sys.color.onSurfaceVariant"
115
+ "label": "sys.color.text.subtle"
116
116
  }
117
117
  },
118
118
  "flavors": {
119
119
  "destructive": {
120
- "description": "Swaps the primary family error family across every appearance. Reserved for irreversible commits (Delete, Remove, Discard).",
120
+ "description": "Error-family appearances for irreversible commits (Delete, Remove, Discard). Destructive is a TONAL form, not a solid red fill or an outline — a soft danger-toned fill with a danger label and NO border, matching Blind's tonal button system (a tonal red sits beside a tonal neutral). It reads as destructive without a loud red block dominating the (monochrome) UI. A solid red fill is reserved for a future high-stakes escalation only.",
121
121
  "appearances": {
122
122
  "primary": {
123
- "background": "sys.color.error",
123
+ "background": "sys.color.background.danger",
124
124
  "border": null,
125
- "label": "sys.color.onError"
125
+ "label": "sys.color.text.danger"
126
126
  },
127
127
  "secondary": {
128
- "background": "sys.color.errorContainer",
128
+ "background": "sys.color.background.danger",
129
129
  "border": null,
130
- "label": "sys.color.onErrorContainer"
130
+ "label": "sys.color.text.danger"
131
131
  },
132
132
  "outlined": {
133
133
  "background": "transparent",
134
134
  "border": {
135
135
  "width": "sys.borderWidth.hairline",
136
- "color": "sys.color.error"
136
+ "color": "sys.color.text.danger"
137
137
  },
138
- "label": "sys.color.error"
138
+ "label": "sys.color.text.danger"
139
139
  },
140
140
  "tertiary": {
141
141
  "background": "transparent",
142
142
  "border": null,
143
- "label": "sys.color.error"
143
+ "label": "sys.color.text.danger"
144
144
  }
145
145
  }
146
146
  }
@@ -171,11 +171,11 @@
171
171
  "layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
172
172
  "innerCounterRing": {
173
173
  "width": "sys.borderWidth.hairline",
174
- "color": "sys.color.focusInset"
174
+ "color": "sys.color.border.focused"
175
175
  },
176
176
  "outerRing": {
177
177
  "width": "sys.borderWidth.thin",
178
- "color": "sys.color.focus"
178
+ "color": "sys.color.border.focused"
179
179
  }
180
180
  },
181
181
  "note": "Keyboard-focus (:focus-visible) visual. Mirrors the `focusIndicator` block (the external-reader contract); kept here so spec-only renderers see focus in the states map. Composes over the lifecycle state the button is in; never via plain mouse click."
@@ -190,16 +190,14 @@
190
190
  "focusIndicator": {
191
191
  "description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the button is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
192
192
  "composition": "outward",
193
- "compositionReason": "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
193
+ "compositionReason": "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
194
194
  "overlay": {
195
195
  "color": "label",
196
196
  "opacity": "sys.state.focus"
197
197
  },
198
198
  "ring": {
199
- "outerWidth": "sys.borderWidth.thin",
200
- "outerColor": "sys.color.focus",
201
- "insetWidth": "sys.borderWidth.hairline",
202
- "insetColor": "sys.color.focusInset"
199
+ "width": "sys.borderWidth.hairline",
200
+ "color": "sys.color.border.focused"
203
201
  },
204
202
  "trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
205
203
  },
@@ -20,9 +20,7 @@ import { Button } from '@teamblind-chorus/ui';
20
20
  <Button variant="text">Not now</Button>
21
21
  ```
22
22
 
23
- ## Use cases
24
-
25
- ### Accent
23
+ ## Accent
26
24
 
27
25
  Brand-blue label (`primary`) — the inline commit. Reach for `accent` when the button reads as a navigational link (*See all*, *Follow*, *View details*).
28
26
 
@@ -34,7 +32,7 @@ import { Button } from '@teamblind-chorus/ui';
34
32
  <Button variant="text" appearance="accent">Skip</Button>
35
33
  ```
36
34
 
37
- ### On primary
35
+ ## On primary
38
36
 
39
37
  Always-white label on top of a `primary`-filled host (Tooltip `default`). Theme-stable.
40
38
 
@@ -46,7 +44,7 @@ import { Button } from '@teamblind-chorus/ui';
46
44
  <Button variant="text" appearance="onPrimary">Got it</Button>
47
45
  ```
48
46
 
49
- ### Inverse
47
+ ## Inverse
50
48
 
51
49
  For inverse hosts (Toast, coach-mark, snackbar). Label paints `inverseOnSurface`; tokens flip with theme.
52
50
 
@@ -58,7 +56,7 @@ import { Button } from '@teamblind-chorus/ui';
58
56
  <Button variant="text" appearance="inverse">Undo</Button>
59
57
  ```
60
58
 
61
- ### With leading icon
59
+ ## Leading icon
62
60
 
63
61
  16px (`sys.icon.md`) glyph before the label at 4px gap — fixed across rungs.
64
62
 
@@ -70,7 +68,7 @@ import { Button, ChevronLeftIcon } from '@teamblind-chorus/ui';
70
68
  <Button variant="text" leadingIcon={<ChevronLeftIcon />}>Back</Button>
71
69
  ```
72
70
 
73
- ### With trailing icon
71
+ ## Trailing icon
74
72
 
75
73
  Destination glyph after the label — chevron-right *Continue*, external-link *Open in new tab*.
76
74
 
@@ -82,7 +80,7 @@ import { Button, ChevronRightIcon } from '@teamblind-chorus/ui';
82
80
  <Button variant="text" trailingIcon={<ChevronRightIcon />}>Continue</Button>
83
81
  ```
84
82
 
85
- ### Dropdown
83
+ ## Dropdown
86
84
 
87
85
  Disclosure trigger: the label reads as the **current value**, the trailing chevron flips with state — `ChevronDownIcon` at rest, `ChevronUpIcon` while the menu is open. Pair `aria-haspopup` + `aria-expanded` on the trigger and portal the menu so it escapes any clipping ancestor; never freeze the chevron when the menu is open.
88
86
 
@@ -112,31 +110,31 @@ function Example() {
112
110
  }
113
111
  ```
114
112
 
115
- ### Group
113
+ ## Focus ring
116
114
 
117
- Optical alignment means chrome-to-chrome gap **is** the visible label-to-label distance. Row gap: `medium`/`small` → 16px (`sys.layout.inline.xl`); `xsmall` → 12px (`sys.layout.inline.lg`).
115
+ Standard ring.
118
116
 
119
117
  ```preview
120
- button/text/group
118
+ button/text/focused
121
119
  ---
122
120
  import { Button } from '@teamblind-chorus/ui';
123
121
 
124
- <div style={{ display: 'inline-flex', gap: 'var(--sys-layout-inline-md)' }}>
125
- <Button variant="text">Cancel</Button>
126
- <Button variant="text" appearance="accent">Save</Button>
127
- </div>
122
+ <Button variant="text" state="focused">Skip</Button>
128
123
  ```
129
124
 
130
- ### Focus indicator
125
+ ## Group
131
126
 
132
- Standard ring.
127
+ Optical alignment means chrome-to-chrome gap **is** the visible label-to-label distance. Row gap: `medium`/`small` → 16px (`sys.layout.inline.xl`); `xsmall` → 12px (`sys.layout.inline.lg`).
133
128
 
134
129
  ```preview
135
- button/text/focused
130
+ button/text/group
136
131
  ---
137
132
  import { Button } from '@teamblind-chorus/ui';
138
133
 
139
- <Button variant="text" state="focused">Skip</Button>
134
+ <div style={{ display: 'inline-flex', gap: 'var(--sys-layout-inline-md)' }}>
135
+ <Button variant="text">Cancel</Button>
136
+ <Button variant="text" appearance="accent">Save</Button>
137
+ </div>
140
138
  ```
141
139
 
142
140
  ## Slots
@@ -151,10 +149,10 @@ A **destructive** flavor swaps the label to `error` across every appearance.
151
149
 
152
150
  | Appearance | Background (rest) | Label color | When to reach for it |
153
151
  |-------------|-------------------|-----------------------------------|--------------------------------------------------------------------------------------|
154
- | `default` | `transparent` | `sys.color.onSurfaceVariant` | Base inline action — "Not now", secondary inline trail commits. |
155
- | `accent` | `transparent` | `sys.color.primary` | Brand-blue inline commit — "Skip", "See all". One per row. |
156
- | `onPrimary` | `transparent` | `sys.color.onPrimary` | On a `primary`-filled host (Tooltip `default`). Theme-stable. |
157
- | `inverse` | `transparent` | `sys.color.inverseOnSurface` | Inside an inverse host (Toast, coach-mark). |
152
+ | `default` | `transparent` | `sys.color.text.subtle` | Base inline action — "Not now", secondary inline trail commits. |
153
+ | `accent` | `transparent` | `sys.color.background.primary` | Brand-blue inline commit — "Skip", "See all". One per row. |
154
+ | `onPrimary` | `transparent` | `sys.color.text.onFill` | On a `primary`-filled host (Tooltip `default`). Theme-stable. |
155
+ | `inverse` | `transparent` | `sys.color.text.inverse` | Inside an inverse host (Toast, coach-mark). |
158
156
 
159
157
  ## Sizes
160
158
 
@@ -100,26 +100,26 @@
100
100
  "default": {
101
101
  "background": "transparent",
102
102
  "border": null,
103
- "label": "sys.color.onSurfaceVariant",
103
+ "label": "sys.color.text.subtle",
104
104
  "note": "The base neutral inline action — the canonical Text Button. Quiet enough to live next to typographic content without claiming commit-rank attention."
105
105
  },
106
106
  "accent": {
107
107
  "background": "transparent",
108
108
  "border": null,
109
- "label": "sys.color.primary",
109
+ "label": "sys.color.text.link",
110
110
  "note": "Brand-blue label for the inline commit affordance. Use sparingly — never two `accent` Text Buttons in the same row.",
111
111
  "linkAffordanceRecommendation": "Prefer `accent` whenever the Text Button reads as a **link affordance** — a section header's trailing 'See all' / 'See more', a card-header 'Follow', an inline 'View details' next to a body paragraph. Link-like affordances should carry chromatic emphasis so the navigational intent is unambiguous; `default` (onSurfaceVariant) is for quieter inline commits that should recede into the body copy. Override to `default` only when a parent surface (e.g. a busy chrome bar) already carries enough chromatic weight that an `accent` label would compete."
112
112
  },
113
113
  "onPrimary": {
114
114
  "background": "transparent",
115
115
  "border": null,
116
- "label": "sys.color.onPrimary",
116
+ "label": "sys.color.text.onFill",
117
117
  "note": "Always-white label for use on top of a `primary`-filled host (e.g. the Tooltip `default` appearance). Both `primary` and `onPrimary` are theme-stable (blue / white in light and dark mode), so the label reads as white against the brand-blue fill in either theme — unlike `inverse`, which flips with the theme."
118
118
  },
119
119
  "inverse": {
120
120
  "background": "transparent",
121
121
  "border": null,
122
- "label": "sys.color.inverseOnSurface",
122
+ "label": "sys.color.text.inverse",
123
123
  "note": "Mirror for use inside an inverse host (Toast, coach-mark, snackbar). Label paints in `inverseOnSurface` so it reads against the host's `inverseSurface` fill; state overlays mix from the same token so the recipe carries over without per-host tuning. The `inverseOnSurface` token FLIPS with the theme (white in light mode, dark in dark mode) — that's correct against the also-flipping `inverseSurface` host. When the host fill does NOT flip (e.g. a `primary`-filled Tooltip in `default` appearance), reach for `onPrimary` instead so the label stays white in both themes."
124
124
  }
125
125
  },
@@ -130,22 +130,22 @@
130
130
  "default": {
131
131
  "background": "transparent",
132
132
  "border": null,
133
- "label": "sys.color.error"
133
+ "label": "sys.color.text.danger"
134
134
  },
135
135
  "accent": {
136
136
  "background": "transparent",
137
137
  "border": null,
138
- "label": "sys.color.error"
138
+ "label": "sys.color.text.danger"
139
139
  },
140
140
  "onPrimary": {
141
141
  "background": "transparent",
142
142
  "border": null,
143
- "label": "sys.color.error"
143
+ "label": "sys.color.text.danger"
144
144
  },
145
145
  "inverse": {
146
146
  "background": "transparent",
147
147
  "border": null,
148
- "label": "sys.color.error"
148
+ "label": "sys.color.text.danger"
149
149
  }
150
150
  }
151
151
  }
@@ -176,11 +176,11 @@
176
176
  "layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
177
177
  "innerCounterRing": {
178
178
  "width": "sys.borderWidth.hairline",
179
- "color": "sys.color.focusInset"
179
+ "color": "sys.color.border.focused"
180
180
  },
181
181
  "outerRing": {
182
182
  "width": "sys.borderWidth.thin",
183
- "color": "sys.color.focus"
183
+ "color": "sys.color.border.focused"
184
184
  }
185
185
  },
186
186
  "note": "Keyboard-focus (:focus-visible) visual. Mirrors the `focusIndicator` block (the external-reader contract); kept here so spec-only renderers see focus in the states map. Composes over the lifecycle state the button is in; never via plain mouse click."
@@ -195,16 +195,14 @@
195
195
  "focusIndicator": {
196
196
  "description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the button is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
197
197
  "composition": "outward",
198
- "compositionReason": "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
198
+ "compositionReason": "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
199
199
  "overlay": {
200
200
  "color": "label",
201
201
  "opacity": "sys.state.focus"
202
202
  },
203
203
  "ring": {
204
- "outerWidth": "sys.borderWidth.thin",
205
- "outerColor": "sys.color.focus",
206
- "insetWidth": "sys.borderWidth.hairline",
207
- "insetColor": "sys.color.focusInset"
204
+ "width": "sys.borderWidth.hairline",
205
+ "color": "sys.color.border.focused"
208
206
  },
209
207
  "trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
210
208
  },
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 🇰🇷 한국어: [`i18n/ko/schema/components/button/toggle.md`](../../../i18n/ko/schema/components/button/toggle.md)
4
4
 
5
- Commit-and-record action — a Toolbar-footprint button with two states. **Inactive** invites the commit (`primary` fill); **active** records it (`transparent` fill + hairline `outlineVariant` outline).
5
+ Commit-and-record action — a Toolbar-footprint button with two states. **Inactive** invites the commit (`primary` fill); **active** records it (`transparent` fill + hairline `border.default` outline).
6
6
 
7
7
  **Reach for this when** you need a reversible commit that persists across views — *Follow / Following*, *Subscribe / Subscribed*, *Join / Joined*. **Skip when** the action is one-shot ([Standard Button](./standard.md)), the row is a dense toolbar ([Toolbar Button](./toolbar.md)), or the toggle belongs to a filter set ([Filter Chip](../chip/filter.md)).
8
8
 
@@ -24,7 +24,7 @@ import { Button } from '@teamblind-chorus/ui';
24
24
 
25
25
  ## Active
26
26
 
27
- The committed form — `transparent` fill with hairline `outlineVariant` stroke. The transparent fill lets the button sit on any host surface tier (page `surface`, card `surfaceContainer`, raised `surfaceContainerHigh`) without re-painting a background that would clash with the host. Use the same element across both states and toggle the `active` flag; the consumer swaps the label text. Reports state via `aria-pressed`.
27
+ The committed form — `transparent` fill with hairline `border.default` stroke. The transparent fill lets the button sit on any host surface tier (page `surface`, card `surfaceContainer`, raised `surfaceContainerHigh`) without re-painting a background that would clash with the host. Use the same element across both states and toggle the `active` flag; the consumer swaps the label text. Reports state via `aria-pressed`.
28
28
 
29
29
  ```preview
30
30
  button/toggle/active
@@ -36,9 +36,7 @@ import { Button } from '@teamblind-chorus/ui';
36
36
  </Button>
37
37
  ```
38
38
 
39
- ## Use cases
40
-
41
- ### With icon
39
+ ## Leading icon
42
40
 
43
41
  A check glyph on commit reinforces the active read. Inactive form stays glyph-less.
44
42
 
@@ -53,7 +51,7 @@ import { CheckedIcon } from '@teamblind-chorus/ui/icons';
53
51
  </Button>
54
52
  ```
55
53
 
56
- ### Focus indicator
54
+ ## Focus ring
57
55
 
58
56
  Both forms take the same standard ring; below shows inactive. See [Focus ring composition](../../DESIGN.md#focus-ring-composition).
59
57
 
@@ -71,7 +69,7 @@ import { Button } from '@teamblind-chorus/ui';
71
69
 
72
70
  - **label** — accessible name. Required, single line. Consumer swaps the verb between states ("Follow" → "Following"); no auto-rewrite.
73
71
  - **leadingIcon** (optional) — context glyph before the label. Inherits colour via `currentColor` per the [family rule](./button.md#icon-colour-inheritance-family-wide).
74
- - **trailingIcon** (optional) — directional/destination glyph after the label. Same contract as [Toolbar Button](./toolbar.md#with-trailing-icon).
72
+ - **trailingIcon** (optional) — directional/destination glyph after the label. Same contract as [Toolbar Button](./toolbar.md#trailing-icon).
75
73
 
76
74
  ## Sizes
77
75
 
@@ -93,8 +91,8 @@ A single visual variant — inactive/active is expressed as a state on the same
93
91
 
94
92
  | State | Background | Border (always 1px `sys.borderWidth.hairline`) | Label / icon color | Notes |
95
93
  |--------------|-------------------------------------|---------------------------------------------------------|-----------------------------------|----------------------------------------------------------------------|
96
- | inactive | `sys.color.primary` | `transparent` | `sys.color.onPrimary` | Brand-loud fill inviting commit. Border `transparent` but 1px width held so footprint never changes between states. |
97
- | active | `transparent` | `sys.color.outlineVariant` | `sys.color.onSurface` | Committed form — hairline-outlined ghost over whatever host surface the button sits on. Transparent fill records state without claiming attention or clashing with the host tier. |
94
+ | inactive | `sys.color.background.primary` | `transparent` | `sys.color.text.onFill` | Brand-loud fill inviting commit. Border `transparent` but 1px width held so footprint never changes between states. |
95
+ | active | `transparent` | `sys.color.border.default` | `sys.color.text.default` | Committed form — hairline-outlined ghost over whatever host surface the button sits on. Transparent fill records state without claiming attention or clashing with the host tier. |
98
96
 
99
97
  ## States
100
98