@teamblind-chorus/ui 1.1.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 (148) 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 +10 -8
  6. package/agents/components/avatar-rail/avatar-rail.md +2 -4
  7. package/agents/components/avatar-rail/avatar-rail.spec.json +27 -12
  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.family.json +3 -1
  13. package/agents/components/banner/banner.md +66 -15
  14. package/agents/components/banner/banner.spec.json +37 -14
  15. package/agents/components/bottom-sheet/bottom-sheet.md +4 -6
  16. package/agents/components/bottom-sheet/bottom-sheet.spec.json +5 -5
  17. package/agents/components/bubble/bubble.md +8 -10
  18. package/agents/components/bubble/bubble.spec.json +11 -11
  19. package/agents/components/button/button.md +1 -1
  20. package/agents/components/button/check.md +9 -11
  21. package/agents/components/button/check.spec.json +25 -8
  22. package/agents/components/button/fab.md +7 -9
  23. package/agents/components/button/fab.spec.json +27 -10
  24. package/agents/components/button/group.spec.json +4 -4
  25. package/agents/components/button/icon.md +21 -23
  26. package/agents/components/button/icon.spec.json +29 -12
  27. package/agents/components/button/standard.md +40 -42
  28. package/agents/components/button/standard.spec.json +37 -20
  29. package/agents/components/button/text.md +21 -23
  30. package/agents/components/button/text.spec.json +30 -13
  31. package/agents/components/button/toggle.md +7 -9
  32. package/agents/components/button/toggle.spec.json +27 -10
  33. package/agents/components/button/toolbar.md +24 -26
  34. package/agents/components/button/toolbar.spec.json +10 -12
  35. package/agents/components/carousel/carousel.md +1 -1
  36. package/agents/components/carousel/post.md +15 -21
  37. package/agents/components/carousel/post.spec.json +17 -17
  38. package/agents/components/carousel/profile.md +9 -45
  39. package/agents/components/carousel/profile.spec.json +17 -17
  40. package/agents/components/chip/chip.md +1 -1
  41. package/agents/components/chip/filter.md +22 -24
  42. package/agents/components/chip/filter.spec.json +34 -11
  43. package/agents/components/chip/tag.md +22 -24
  44. package/agents/components/chip/tag.spec.json +36 -13
  45. package/agents/components/dialog/dialog.md +1 -3
  46. package/agents/components/dialog/dialog.spec.json +3 -3
  47. package/agents/components/directory-list/directory-list.md +1 -3
  48. package/agents/components/directory-list/directory-list.spec.json +2 -2
  49. package/agents/components/divider/divider.family.json +1 -1
  50. package/agents/components/divider/divider.md +12 -14
  51. package/agents/components/divider/divider.spec.json +8 -8
  52. package/agents/components/empty-state/empty-state.family.json +28 -0
  53. package/agents/components/empty-state/empty-state.md +69 -0
  54. package/agents/components/empty-state/empty-state.spec.json +87 -0
  55. package/agents/components/feed/ad.md +2 -4
  56. package/agents/components/feed/ad.spec.json +10 -10
  57. package/agents/components/feed/post.md +41 -43
  58. package/agents/components/feed/post.spec.json +35 -39
  59. package/agents/components/form-field/form-field.md +1 -1
  60. package/agents/components/form-field/input.md +32 -34
  61. package/agents/components/form-field/input.spec.json +39 -31
  62. package/agents/components/form-field/search.md +2 -4
  63. package/agents/components/form-field/search.spec.json +24 -16
  64. package/agents/components/form-field/select.md +18 -20
  65. package/agents/components/form-field/select.spec.json +36 -27
  66. package/agents/components/form-field/textarea.md +3 -5
  67. package/agents/components/form-field/textarea.spec.json +37 -29
  68. package/agents/components/header/main.md +4 -6
  69. package/agents/components/header/main.spec.json +3 -3
  70. package/agents/components/header/sub.md +6 -8
  71. package/agents/components/header/sub.spec.json +3 -3
  72. package/agents/components/list/accordion.md +34 -45
  73. package/agents/components/list/accordion.spec.json +26 -17
  74. package/agents/components/list/entry.md +59 -81
  75. package/agents/components/list/entry.spec.json +37 -21
  76. package/agents/components/list/list.md +2 -2
  77. package/agents/components/list/radio.md +13 -20
  78. package/agents/components/list/radio.spec.json +33 -18
  79. package/agents/components/list/standard.md +88 -64
  80. package/agents/components/list/standard.spec.json +52 -20
  81. package/agents/components/metadata/compact.md +4 -6
  82. package/agents/components/metadata/compact.spec.json +6 -6
  83. package/agents/components/metadata/metadata.md +1 -1
  84. package/agents/components/metadata/standard.md +12 -14
  85. package/agents/components/metadata/standard.spec.json +10 -10
  86. package/agents/components/nav-card/nav-card.md +25 -27
  87. package/agents/components/nav-card/nav-card.spec.json +25 -16
  88. package/agents/components/nav-list/nav-list.md +2 -8
  89. package/agents/components/nav-list/nav-list.spec.json +3 -3
  90. package/agents/components/navigation-bar/main.md +9 -11
  91. package/agents/components/navigation-bar/main.spec.json +6 -6
  92. package/agents/components/navigation-bar/search.md +6 -8
  93. package/agents/components/navigation-bar/search.spec.json +9 -9
  94. package/agents/components/navigation-bar/sub.md +9 -11
  95. package/agents/components/navigation-bar/sub.spec.json +7 -7
  96. package/agents/components/page-shell/page-shell.family.json +1 -1
  97. package/agents/components/page-shell/page-shell.md +33 -0
  98. package/agents/components/page-shell/page-shell.spec.json +85 -0
  99. package/agents/components/pagination/pagination.family.json +1 -1
  100. package/agents/components/pagination/pagination.md +3 -3
  101. package/agents/components/pagination/pagination.spec.json +5 -5
  102. package/agents/components/profile-header/profile-header.md +9 -11
  103. package/agents/components/profile-header/profile-header.spec.json +9 -9
  104. package/agents/components/progress/progress.family.json +1 -1
  105. package/agents/components/progress/progress.md +5 -5
  106. package/agents/components/progress/progress.spec.json +8 -8
  107. package/agents/components/side-sheet/side-sheet.md +11 -13
  108. package/agents/components/side-sheet/side-sheet.spec.json +3 -3
  109. package/agents/components/skeleton/skeleton.md +7 -9
  110. package/agents/components/skeleton/skeleton.spec.json +5 -5
  111. package/agents/components/spinner/spinner.family.json +27 -0
  112. package/agents/components/spinner/spinner.md +96 -0
  113. package/agents/components/spinner/spinner.spec.json +82 -0
  114. package/agents/components/status-tag/status-tag.md +7 -9
  115. package/agents/components/status-tag/status-tag.spec.json +5 -5
  116. package/agents/components/suggestion-list/suggestion-list.md +3 -7
  117. package/agents/components/suggestion-list/suggestion-list.spec.json +8 -12
  118. package/agents/components/switch/switch.md +12 -14
  119. package/agents/components/switch/switch.spec.json +23 -15
  120. package/agents/components/tab-bar/tab-bar.md +9 -11
  121. package/agents/components/tab-bar/tab-bar.spec.json +37 -23
  122. package/agents/components/tabs/rounded.md +6 -8
  123. package/agents/components/tabs/rounded.spec.json +34 -13
  124. package/agents/components/tabs/segmented.md +4 -6
  125. package/agents/components/tabs/segmented.spec.json +4 -8
  126. package/agents/components/tabs/underline.md +9 -11
  127. package/agents/components/tabs/underline.spec.json +31 -14
  128. package/agents/components/thumbnail/thumbnail.md +5 -7
  129. package/agents/components/thumbnail/thumbnail.spec.json +8 -8
  130. package/agents/components/toast/toast.md +5 -7
  131. package/agents/components/toast/toast.spec.json +3 -3
  132. package/agents/components/tooltip/tooltip.md +6 -8
  133. package/agents/components/tooltip/tooltip.spec.json +4 -4
  134. package/agents/manifest.json +8 -6
  135. package/agents/tokens.usage.json +71 -226
  136. package/agents/usage.json +12 -0
  137. package/dist/index.cjs +531 -262
  138. package/dist/index.cjs.map +1 -1
  139. package/dist/index.d.cts +57 -13
  140. package/dist/index.d.ts +57 -13
  141. package/dist/index.js +530 -263
  142. package/dist/index.js.map +1 -1
  143. package/dist/styles.css +560 -379
  144. package/eslint/rules.js +7 -7
  145. package/package.json +2 -3
  146. package/agents/anti-patterns.md +0 -533
  147. package/agents/compose.md +0 -240
  148. package/agents/images.md +0 -66
@@ -26,12 +26,12 @@
26
26
  },
27
27
  "card": {
28
28
  "required": true,
29
- "description": "One compact post card per page. `flex: 0 0 calc(100% - sys.layout.inline.md - ref.space.500)` so a 40px peek of the next card always surfaces at the trailing edge. `scroll-snap-align: start`. Inner padding `sys.layout.container.md`, `sys.color.surface` fill, `sys.radius.md` corner, `sys.borderWidth.hairline` outline in `sys.color.outlineVariant`.",
29
+ "description": "One compact post card per page. `flex: 0 0 calc(100% - sys.layout.inline.md - ref.space.500)` so a 40px peek of the next card always surfaces at the trailing edge. `scroll-snap-align: start`. Inner padding `sys.layout.container.md`, `sys.color.surface.default` fill, `sys.radius.md` corner, `sys.borderWidth.hairline` outline in `sys.color.border.default`.",
30
30
  "intrinsic": true
31
31
  },
32
32
  "header": {
33
33
  "required": true,
34
- "description": "Top row of the card: avatar (Thumbnail size 40) + channel name + optional VerifiedIcon (size sys.icon.md, sys.color.primary) + trailing follow affordance. Rendered by the **shared `EntryRow` atom** — the same component [List/entry](../list/entry.md) rows render — wrapped in a carousel-specific `chorus-post-carousel__card-header`. See `behavior.headerReusesEntryCombo`.",
34
+ "description": "Top row of the card: avatar (Thumbnail size 40) + channel name + optional VerifiedIcon (size sys.icon.md, sys.color.background.primary) + trailing follow affordance. Rendered by the **shared `EntryRow` atom** — the same component [List/entry](../list/entry.md) rows render — wrapped in a carousel-specific `chorus-post-carousel__card-header`. See `behavior.headerReusesEntryCombo`.",
35
35
  "intrinsic": true
36
36
  },
37
37
  "avatar": {
@@ -44,16 +44,16 @@
44
44
  },
45
45
  "verified": {
46
46
  "required": false,
47
- "description": "Inline `VerifiedFillIcon` at `sys.icon.md`, painted in `sys.color.primary` (resolves to `ref.palette.blue.500`). Sits to the **LEFT** of the channel name so the reader's eye lands on the trust signal first, then the name. Decorative — pair with a textual affordance elsewhere if the verified status itself is meaningful.",
47
+ "description": "Inline `VerifiedFillIcon` at `sys.icon.md`, painted in `sys.color.background.primary` (resolves to `ref.palette.blue.500`). Sits to the **LEFT** of the channel name so the reader's eye lands on the trust signal first, then the name. Decorative — pair with a textual affordance elsewhere if the verified status itself is meaningful.",
48
48
  "accepts": [
49
49
  "icon"
50
50
  ],
51
51
  "defaultIcon": "VerifiedFillIcon",
52
- "defaultIconColor": "sys.color.primary"
52
+ "defaultIconColor": "sys.color.icon.accent.blue.default"
53
53
  },
54
54
  "channel": {
55
55
  "required": true,
56
- "description": "Channel / author name. `sys.typo.label.md` / Semibold / `sys.color.onSurface`. Single line; truncates. Sits to the right of the optional `verified` mark inside the channel-row sub-container.",
56
+ "description": "Channel / author name. `sys.typo.label.md` / Semibold / `sys.color.text.default`. Single line; truncates. Sits to the right of the optional `verified` mark inside the channel-row sub-container.",
57
57
  "accepts": [
58
58
  "text"
59
59
  ]
@@ -68,28 +68,28 @@
68
68
  },
69
69
  "title": {
70
70
  "required": true,
71
- "description": "Post title. `sys.typo.label.md` / Semibold / `sys.color.onSurface`. One line; truncates with ellipsis.",
71
+ "description": "Post title. `sys.typo.label.md` / Semibold / `sys.color.text.default`. One line; truncates with ellipsis.",
72
72
  "accepts": [
73
73
  "text"
74
74
  ]
75
75
  },
76
76
  "body": {
77
77
  "required": true,
78
- "description": "Post excerpt. `sys.typo.body.sm` / Regular / `sys.color.onSurfaceVariant`. Three-line clamp with trailing ellipsis.",
78
+ "description": "Post excerpt. `sys.typo.body.sm` / Regular / `sys.color.text.subtle`. Three-line clamp with trailing ellipsis.",
79
79
  "accepts": [
80
80
  "text"
81
81
  ]
82
82
  },
83
83
  "mention": {
84
84
  "required": false,
85
- "description": "Tap-anywhere mention / tag line below the body. `sys.typo.body.sm` / `sys.color.primary` (no italic — the carousel card reads tags as part of its body block, unlike the Feed · Post card which italicises a single mention).",
85
+ "description": "Tap-anywhere mention / tag line below the body. `sys.typo.body.sm` / `sys.color.background.primary` (no italic — the carousel card reads tags as part of its body block, unlike the Feed · Post card which italicises a single mention).",
86
86
  "accepts": [
87
87
  "text"
88
88
  ]
89
89
  },
90
90
  "footer": {
91
91
  "required": true,
92
- "description": "Bottom row: leading `more` affordance + trailing view count. The 'See more' label renders as a [Text Button](../button/text.md) (`size={'xsmall'}`, `appearance={'secondary'}`) — same family as the card-header follow action — so both card affordances share one state contract. The view count renders as a non-interactive `<span>` matching the same `xsmall` rhythm (`EyeIcon` at `sys.icon.md` + `sys.typo.label.sm` / `sys.color.onSurfaceVariant`).",
92
+ "description": "Bottom row: leading `more` affordance + trailing view count. The 'See more' label renders as a [Text Button](../button/text.md) (`size={'xsmall'}`, `appearance={'secondary'}`) — same family as the card-header follow action — so both card affordances share one state contract. The view count renders as a non-interactive `<span>` matching the same `xsmall` rhythm (`EyeIcon` at `sys.icon.md` + `sys.typo.label.sm` / `sys.color.text.subtle`).",
93
93
  "intrinsic": true
94
94
  },
95
95
  "pagination": {
@@ -106,8 +106,8 @@
106
106
  "pagePeek": "ref.space.500",
107
107
  "pagePeekNote": "Guaranteed minimum visibility of the next card at the trailing edge. Pinned to `ref.space.500` (40px) — a raw ref step rather than a responsive sys-layout rung — because the carousel wants a fixed-pixel visibility floor independent of the responsive sys-layout shift. The card's flex-basis is `calc(100% - sys.layout.inline.md - ref.space.500)`, so the inter-card gap plus the peek subtract from the pager's inline space in lock-step.",
108
108
  "pageSnapAnchor": "Cards stick to the leading edge of the pager (the pager's own `padding-left: sys.layout.container.md`, the 16 rail). `scroll-snap-align: start` on each card plus the pager's `scroll-padding-left: sys.layout.container.md` together guarantee that, after every swipe, the snapped card aligns flush with the 16 rail — and the trailing edge holds the 40px peek of the next card.",
109
- "cardFill": "sys.color.surface",
110
- "cardOutline": "sys.borderWidth.hairline sys.color.outlineVariant",
109
+ "cardFill": "sys.color.surface.default",
110
+ "cardOutline": "sys.borderWidth.hairline sys.color.border.default",
111
111
  "cardRadius": "sys.radius.md",
112
112
  "cardPadding": "sys.layout.container.md",
113
113
  "cardStackGap": "sys.layout.stack.sm",
@@ -115,23 +115,23 @@
115
115
  "cardAvatarSize": 40,
116
116
  "cardAvatarRendersAs": "Thumbnail at size 40 — the carousel does not paint its own circular crop or fallback; both come from the Thumbnail family.",
117
117
  "cardChannelTypo": "sys.typo.label.md",
118
- "cardChannelColor": "sys.color.onSurface",
118
+ "cardChannelColor": "sys.color.text.default",
119
119
  "cardVerifiedIcon": "VerifiedFillIcon",
120
120
  "cardVerifiedSize": "sys.icon.md",
121
- "cardVerifiedColor": "sys.color.primary",
121
+ "cardVerifiedColor": "sys.color.text.link",
122
122
  "cardVerifiedColorResolved": "ref.palette.blue.500",
123
123
  "cardVerifiedPosition": "Leading — sits to the LEFT of the channel name inside the channel-row sub-container.",
124
124
  "cardFollowActionRendersAs": "Button variant='text' size='xsmall' appearance='accent' (inactive — link-affordance rule) → appearance='default' (active — followed state recedes). All state tokens delegate to the Text Button family.",
125
125
  "cardMoreActionRendersAs": "Button variant='text' size='xsmall' appearance='secondary'. Same Text Button rung as the follow action so the card's two affordances share one state contract.",
126
126
  "cardTitleTypo": "sys.typo.label.md",
127
- "cardTitleColor": "sys.color.onSurface",
127
+ "cardTitleColor": "sys.color.text.default",
128
128
  "cardBodyTypo": "sys.typo.body.sm",
129
- "cardBodyColor": "sys.color.onSurfaceVariant",
129
+ "cardBodyColor": "sys.color.text.subtle",
130
130
  "cardBodyLineClamp": 3,
131
131
  "cardMentionTypo": "sys.typo.body.sm",
132
- "cardMentionColor": "sys.color.primary",
132
+ "cardMentionColor": "sys.color.text.mention",
133
133
  "cardFooterTypo": "sys.typo.label.sm",
134
- "cardFooterColor": "sys.color.onSurfaceVariant",
134
+ "cardFooterColor": "sys.color.text.subtle",
135
135
  "cardFooterIcon": "EyeIcon",
136
136
  "cardFooterIconSize": "sys.icon.md",
137
137
  "paginationRendersAs": "Pagination component — dot size / gap / radius and active / inactive colors all delegate to the [Pagination](../pagination/pagination.md) spec.",
@@ -56,9 +56,7 @@ import { Carousel, ProfileCarousel } from '@teamblind-chorus/ui';
56
56
  </Carousel>
57
57
  ```
58
58
 
59
- ## Use cases
60
-
61
- ### With header action
59
+ ## Header action
62
60
 
63
61
  Extend the header with a trailing `accent` Text Button when there's an index page to route to. Lifts the `headerAction` prop on the `<Carousel>` wrapper.
64
62
 
@@ -80,33 +78,12 @@ import { Carousel, ProfileCarousel } from '@teamblind-chorus/ui';
80
78
  { icon: 'heart', value: '81%' },
81
79
  ],
82
80
  },
83
- {
84
- avatar: { src: '/placeholder.png', alt: 'Tesla' },
85
- name: 'Tesla',
86
- followers: '1.4K followers',
87
- metrics: [
88
- { icon: 'star', value: '4.7' },
89
- { icon: 'pulse', value: '86' },
90
- { icon: 'heart', value: '85.3%' },
91
- ],
92
- followed: true,
93
- },
94
- {
95
- avatar: { src: '/placeholder.png', alt: 'Stripe' },
96
- name: 'Stripe',
97
- followers: '2.1K followers',
98
- metrics: [
99
- { icon: 'star', value: '4.5' },
100
- { icon: 'pulse', value: '92.4' },
101
- { icon: 'heart', value: '88%' },
102
- ],
103
- },
104
81
  ]}
105
82
  />
106
83
  </Carousel>
107
84
  ```
108
85
 
109
- ### With description
86
+ ## With description
110
87
 
111
88
  The metrics row swaps out for a two-line description. Use for editorial collections where the value of each profile is best explained in copy (channel topic, hot pitch) rather than numeric signals. The description block is fixed to the same two-line height as the metrics row, so cards stay flush across both modes even when description copy clamps with an ellipsis.
112
89
 
@@ -124,19 +101,6 @@ import { Carousel, ProfileCarousel } from '@teamblind-chorus/ui';
124
101
  followers: '12.4K followers',
125
102
  description: 'Hands-on threads about systems, infra, and the work behind the launch.',
126
103
  },
127
- {
128
- avatar: { src: '/placeholder.png', alt: 'Compensation' },
129
- name: 'Compensation',
130
- followers: '8.1K followers',
131
- description: 'Salary checks, offer evaluations, and the quiet math of staying versus leaving — the channel that runs longer than any single conversation can.',
132
- followed: true,
133
- },
134
- {
135
- avatar: { src: '/placeholder.png', alt: 'Career' },
136
- name: 'Career',
137
- followers: '5.3K followers',
138
- description: 'Promotion packets, scope debates, and the rewrites that actually cleared.',
139
- },
140
104
  ]}
141
105
  />
142
106
  </Carousel>
@@ -149,9 +113,9 @@ import { Carousel, ProfileCarousel } from '@teamblind-chorus/ui';
149
113
  - **card** — one profile card per page; fixed at **176px** wide.
150
114
  - **cover** — top band; 88px tall image-area slot. Renders an `<img>` defaulting to `/placeholder.png` (universal Chorus placeholder). `object-fit: cover` crops to fill the band; `sys.color.surfaceContainerHigh` underlies as the no-image fallback. Consumers override via `items[i].cover.src`.
151
115
  - **avatar** — [Thumbnail](../thumbnail/thumbnail.md) `size={64}` with [`outlined={true}`](../thumbnail/thumbnail.md#with-surface-outline), centered and overlapping the cover band's bottom edge. The 2-token (`sys.borderWidth.thin`) `surface`-tone outset halo separating the circle from the cover image is owned by Thumbnail's outlined case — the carousel forwards the prop instead of painting a halo on its own wrapper.
152
- - **name** — entity name; `sys.typo.label.md` / Semibold / `sys.color.onSurface`; centered, single line truncate.
153
- - **followers** — follower count; `sys.typo.label.sm` / `sys.color.onSurfaceVariant`; centered.
154
- - **metrics** *(optional)* — row of `icon + value` chips: `star → StarFillIcon (sys.color.icon.yellow)`, `pulse → PulseFillIcon (sys.color.success)`, `heart → HeartFillIcon (sys.color.icon.red)`. Mutually exclusive with `description`.
116
+ - **name** — entity name; `sys.typo.label.md` / Semibold / `sys.color.text.default`; centered, single line truncate.
117
+ - **followers** — follower count; `sys.typo.label.sm` / `sys.color.text.subtle`; centered.
118
+ - **metrics** *(optional)* — row of `icon + value` chips: `star → StarFillIcon (sys.color.icon.accent.yellow.default)`, `pulse → PulseFillIcon (sys.color.text.success)`, `heart → HeartFillIcon (sys.color.icon.accent.red.default)`. Mutually exclusive with `description`.
155
119
  - **description** *(optional)* — two-line clamped paragraph that replaces the metrics row when present. Block height fixed to two lines of `sys.typo.label.sm` regardless of copy length, so card height stays consistent across metrics-carrying and copy-carrying cards.
156
120
  - **followAction** — full-width [Toggle Button](../button/text.md) (`variant={'toggle'}`); `Follow` (inactive) / `Following` (active).
157
121
  - **pagination** — [Pagination](../pagination/pagination.md) component, one dot per card (`count` = card count, `activeIndex` from scroll position). Decorative — dot tokens and the `aria-hidden` contract live on its spec; the carousel centers the intrinsic-width row (`align-self: center`).
@@ -164,11 +128,11 @@ import { Carousel, ProfileCarousel } from '@teamblind-chorus/ui';
164
128
  | card | Fixed `width: 176px`, `sys.color.surface` fill, `sys.radius.md`, inset hairline outline, `scroll-snap-align: start` |
165
129
  | cover | 88px tall image-area slot. Default `src` = `/placeholder.png` (universal image placeholder), `object-fit: cover`, `sys.color.surfaceContainerHigh` underlay |
166
130
  | avatar | [Thumbnail](../thumbnail/thumbnail.md) `size={64}` `outlined`, vertical center on cover's bottom edge. The 2-token `surface`-tone halo separating the circle from the cover image is painted by Thumbnail's `outlined` case (outset `box-shadow: 0 0 0 sys.borderWidth.thin sys.color.surface`) — wrapper has no halo of its own |
167
- | name | `sys.typo.label.md`, `sys.color.onSurface`, centered |
168
- | followers | `sys.typo.label.sm`, `sys.color.onSurfaceVariant`, centered |
131
+ | name | `sys.typo.label.md`, `sys.color.text.default`, centered |
132
+ | followers | `sys.typo.label.sm`, `sys.color.text.subtle`, centered |
169
133
  | metrics row | `sys.layout.inline.md` gap, centered. Fixed-height slot — `calc(sys.typo.label.sm.size * sys.typo.label.sm.line * 2)` so the row always reserves two lines of `label.sm` regardless of content. |
170
- | metric chip | `sys.icon.md` glyph + `sys.typo.label.sm` value; star → `StarFillIcon` (`sys.color.icon.yellow`), pulse → `PulseFillIcon` (`sys.color.success`), heart → `HeartFillIcon` (`sys.color.icon.red`) |
171
- | description | `sys.typo.label.sm` / `sys.color.onSurfaceVariant`, centered, two-line clamp with trailing ellipsis. Two-layer DOM — outer container owns the same fixed-height slot as `metrics row` (min/max-height = 2 label.sm lines); inner `<p>` owns the `-webkit-line-clamp: 2` truncation. Split sidesteps a Chrome quirk where `display: -webkit-box` and explicit `height` on one element break the third-line clip. |
134
+ | metric chip | `sys.icon.md` glyph + `sys.typo.label.sm` value; star → `StarFillIcon` (`sys.color.icon.accent.yellow.default`), pulse → `PulseFillIcon` (`sys.color.text.success`), heart → `HeartFillIcon` (`sys.color.icon.accent.red.default`) |
135
+ | description | `sys.typo.label.sm` / `sys.color.text.subtle`, centered, two-line clamp with trailing ellipsis. Two-layer DOM — outer container owns the same fixed-height slot as `metrics row` (min/max-height = 2 label.sm lines); inner `<p>` owns the `-webkit-line-clamp: 2` truncation. Split sidesteps a Chrome quirk where `display: -webkit-box` and explicit `height` on one element break the third-line clip. |
172
136
  | followAction | [Toggle Button](../button/text.md) (Chip-toggle anatomy), stretched to full card width |
173
137
  | pagination dot | [Pagination](../pagination/pagination.md) component delegated verbatim — 6 × 6 dots, active/inactive colors, and row gap bind on its spec |
174
138
 
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "card": {
28
28
  "required": true,
29
- "description": "One profile card per page. **Width is fixed at 176px** — every card paints the same footprint regardless of how many entries the rail carries. Vertical stack: cover band → avatar (overlapping) → name → followers → metrics row → follow toggle. `sys.color.surface` fill, `sys.radius.md` corner, `sys.borderWidth.hairline sys.color.outlineVariant` inset outline. `scroll-snap-align: start`.",
29
+ "description": "One profile card per page. **Width is fixed at 176px** — every card paints the same footprint regardless of how many entries the rail carries. Vertical stack: cover band → avatar (overlapping) → name → followers → metrics row → follow toggle. `sys.color.surface.default` fill, `sys.radius.md` corner, `sys.borderWidth.hairline sys.color.border.default` inset outline. `scroll-snap-align: start`.",
30
30
  "intrinsic": true
31
31
  },
32
32
  "cover": {
@@ -46,33 +46,33 @@
46
46
  },
47
47
  "name": {
48
48
  "required": true,
49
- "description": "Entity name (channel / profile / company). `sys.typo.label.md` / Semibold / `sys.color.onSurface`. Centered; single line, truncates with ellipsis.",
49
+ "description": "Entity name (channel / profile / company). `sys.typo.label.md` / Semibold / `sys.color.text.default`. Centered; single line, truncates with ellipsis.",
50
50
  "accepts": [
51
51
  "text"
52
52
  ]
53
53
  },
54
54
  "followers": {
55
55
  "required": true,
56
- "description": "Follower count line. `sys.typo.label.sm` / Semibold / `sys.color.onSurfaceVariant`. Centered; single line.",
56
+ "description": "Follower count line. `sys.typo.label.sm` / Semibold / `sys.color.text.subtle`. Centered; single line.",
57
57
  "accepts": [
58
58
  "text"
59
59
  ]
60
60
  },
61
61
  "metrics": {
62
62
  "required": false,
63
- "description": "Row of metric chips below the name / followers stack. Each chip is `icon + value` painted in `sys.typo.label.sm` / `sys.color.onSurface`. Three default kinds: { icon: 'star', … } → `StarFillIcon` in `sys.color.icon.yellow`; { icon: 'pulse', … } → `PulseFillIcon` in `sys.color.success`; { icon: 'heart', … } → `HeartFillIcon` in `sys.color.icon.red`. Consumers may pass custom { icon: <ReactNode>, value, color? } entries for other shapes. Mutually exclusive with `description` — when both are present `description` wins.",
63
+ "description": "Row of metric chips below the name / followers stack. Each chip is `icon + value` painted in `sys.typo.label.sm` / `sys.color.text.default`. Three default kinds: { icon: 'star', … } → `StarFillIcon` in `sys.color.icon.accent.yellow.default`; { icon: 'pulse', … } → `PulseFillIcon` in `sys.color.text.success`; { icon: 'heart', … } → `HeartFillIcon` in `sys.color.icon.accent.red.default`. Consumers may pass custom { icon: <ReactNode>, value, color? } entries for other shapes. Mutually exclusive with `description` — when both are present `description` wins.",
64
64
  "intrinsic": true
65
65
  },
66
66
  "description": {
67
67
  "required": false,
68
- "description": "Two-line clamped descriptive paragraph that replaces the metrics row when present. `sys.typo.label.sm` / `sys.color.onSurfaceVariant`, centered. The slot reserves a fixed height of two `label.sm` lines so card height stays consistent across cards that carry metrics and cards that carry copy — extra content clamps with a trailing ellipsis.",
68
+ "description": "Two-line clamped descriptive paragraph that replaces the metrics row when present. `sys.typo.label.sm` / `sys.color.text.subtle`, centered. The slot reserves a fixed height of two `label.sm` lines so card height stays consistent across cards that carry metrics and cards that carry copy — extra content clamps with a trailing ellipsis.",
69
69
  "accepts": [
70
70
  "text"
71
71
  ]
72
72
  },
73
73
  "followAction": {
74
74
  "required": true,
75
- "description": "Trailing full-width [Toggle Button](../button/text.md) at the foot of the card. `Follow` (inactive) → `Following` (active). The carousel does not paint its own follow chrome — every state binding lives on the Toggle Button (Chip-toggle) contract, which paints `sys.color.primary` while inactive and a `transparent` fill with a hairline outline while active so the followed state recedes against the card's own `surface` tier without re-painting a tone that would clash.",
75
+ "description": "Trailing full-width [Toggle Button](../button/text.md) at the foot of the card. `Follow` (inactive) → `Following` (active). The carousel does not paint its own follow chrome — every state binding lives on the Toggle Button (Chip-toggle) contract, which paints `sys.color.background.primary` while inactive and a `transparent` fill with a hairline outline while active so the followed state recedes against the card's own `surface` tier without re-painting a tone that would clash.",
76
76
  "accepts": [
77
77
  "button"
78
78
  ],
@@ -90,8 +90,8 @@
90
90
  "pagePeek": "ref.space.500",
91
91
  "pagePeekNote": "Guaranteed minimum visibility of the next card at the trailing edge — pinned to 40px (ref.space.500). Identical contract to PostCarousel.",
92
92
  "pageSnapAnchor": "Cards stick to the leading edge of the pager (the pager's own `padding-left`, the 16 rail). `scroll-snap-align: start` plus the pager's `scroll-padding-left: sys.layout.container.md` produce this anchor.",
93
- "cardFill": "sys.color.surface",
94
- "cardOutline": "sys.borderWidth.hairline sys.color.outlineVariant",
93
+ "cardFill": "sys.color.surface.default",
94
+ "cardOutline": "sys.borderWidth.hairline sys.color.border.default",
95
95
  "cardOutlineComposition": "overlay",
96
96
  "cardOutlineCompositionNote": "Painted on a dedicated `::after` overlay layer (`position: absolute; inset: 0; z-index: 2; pointer-events: none`), not as an inset `box-shadow` on the card box itself. The card hosts an opaque full-bleed cover band at the top; an inset shadow on the card would be masked by that fill at the top edge. Same overlay idiom DESIGN.md prescribes for the focus ring — so the stroke renders above every child regardless of edge-painting content.",
97
97
  "cardRadius": "sys.radius.md",
@@ -99,25 +99,25 @@
99
99
  "cardWidthNote": "Fixed pixel width — every profile card paints the same 176px footprint regardless of card count or screen width. The pager scrolls horizontally so the 176px footprint never reflows.",
100
100
  "cardStackGap": "sys.layout.stack.sm",
101
101
  "coverHeight": "88px",
102
- "coverFill": "sys.color.surfaceContainerHigh (background underlay — the placeholder image paints on top via `object-fit: cover`)",
102
+ "coverFill": "sys.color.surface.default (background underlay — the placeholder image paints on top via `object-fit: cover`)",
103
103
  "coverImageSource": "Same `/placeholder.png` asset every Chorus image-area slot falls back to. Cropped to fill the 88px band via `object-fit: cover` — the placeholder's centered 'blind' wordmark stays visually centered. Decorative — `aria-hidden`. Consumers can override per-item via `items[i].cover.src` (any image URL preserving the same `object-fit: cover` crop).",
104
104
  "avatarSize": 64,
105
- "avatarOverlap": "Avatar's vertical center sits on the cover band's bottom edge — the bottom half of the avatar bleeds onto the card surface. The 2-token (`sys.borderWidth.thin`) `sys.color.surface` halo separating the avatar from the cover band is owned by Thumbnail's `outlined={true}` case (see [Thumbnail § With surface outline](../thumbnail/thumbnail.md#with-surface-outline)) — outset `box-shadow`, not a wrapper border. Same contract as ProfileHeader's avatar.",
105
+ "avatarOverlap": "Avatar's vertical center sits on the cover band's bottom edge — the bottom half of the avatar bleeds onto the card surface. The 2-token (`sys.borderWidth.thin`) `sys.color.surface.default` halo separating the avatar from the cover band is owned by Thumbnail's `outlined={true}` case (see [Thumbnail § With surface outline](../thumbnail/thumbnail.md#with-surface-outline)) — outset `box-shadow`, not a wrapper border. Same contract as ProfileHeader's avatar.",
106
106
  "nameTypo": "sys.typo.label.md",
107
- "nameColor": "sys.color.onSurface",
107
+ "nameColor": "sys.color.text.default",
108
108
  "followersTypo": "sys.typo.label.sm",
109
- "followersColor": "sys.color.onSurfaceVariant",
109
+ "followersColor": "sys.color.text.subtle",
110
110
  "metricsRowGap": "sys.layout.inline.md",
111
111
  "metricChipTypo": "sys.typo.label.sm",
112
- "metricChipColor": "sys.color.onSurface",
112
+ "metricChipColor": "sys.color.text.default",
113
113
  "metricIconSize": "sys.icon.md",
114
- "metricIconStar": "StarFillIcon — sys.color.icon.yellow",
115
- "metricIconPulse": "PulseFillIcon — sys.color.success",
116
- "metricIconHeart": "HeartFillIcon — sys.color.icon.red",
114
+ "metricIconStar": "StarFillIcon — sys.color.icon.accent.yellow.default",
115
+ "metricIconPulse": "PulseFillIcon — sys.color.text.success",
116
+ "metricIconHeart": "HeartFillIcon — sys.color.icon.accent.red.default",
117
117
  "midSlotHeight": "calc(sys.typo.label.sm.size * sys.typo.label.sm.line * 2)",
118
118
  "midSlotHeightNote": "Shared fixed height for the metrics row and the description slot — exactly two `label.sm` lines (12 × 1.5 × 2 ≈ 36px). Cards that carry metrics and cards that carry a description paint the same outer height.",
119
119
  "descriptionTypo": "sys.typo.label.sm",
120
- "descriptionColor": "sys.color.onSurfaceVariant",
120
+ "descriptionColor": "sys.color.text.subtle",
121
121
  "descriptionLineClamp": 2,
122
122
  "descriptionOverflow": "Two-line clamp with trailing ellipsis; copy exceeding two lines truncates so the slot height stays fixed.",
123
123
  "followActionRendersAs": "Button variant='toggle' — Chip-toggle anatomy. Stretched to full card width via a wrapper rule. All state tokens delegate to the Toggle Button (Chip) family.",
@@ -8,5 +8,5 @@ A small, content-shaped control or label — chip-shaped affordance for two anch
8
8
 
9
9
  ## Sub-components
10
10
 
11
- - **[Filter](./filter.md)** — Selectable filter chip. Capsule (`radius.full`), `surfaceContainerHigh` raised tone with a hairline `outlineVariant` stroke at rest, swaps to an inverse fill when selected. Optional leading and trailing icons.
11
+ - **[Filter](./filter.md)** — Selectable filter chip. Capsule (`radius.full`), `surfaceContainerHigh` raised tone with a hairline `border.default` stroke at rest, swaps to an inverse fill when selected. Optional leading and trailing icons.
12
12
  - **[Tag](./tag.md)** — Informational tag chip. Square-cornered (`radius.sm`), `secondaryContainer` fill that sits one tonal step *below* the lifted Filter so the label reads as attached metadata rather than a tappable choice. Optional trailing icon only — typically the dismiss "×" for opt-out flows.
@@ -10,7 +10,7 @@ The selectable chip — a capsule-shaped toggle for refining a set. Unselected i
10
10
 
11
11
  ## Default
12
12
 
13
- At-rest — transparent fill with a hairline `outlineVariant` stroke so the chip sits on any surface without colliding with the surface ladder.
13
+ At-rest — transparent fill with a hairline `border.default` stroke so the chip sits on any surface without colliding with the surface ladder.
14
14
 
15
15
  ```preview
16
16
  chip/filter/unselected
@@ -22,9 +22,7 @@ import { Chip } from '@teamblind-chorus/ui';
22
22
  </Chip>
23
23
  ```
24
24
 
25
- ## Use cases
26
-
27
- ### Selected
25
+ ## Selected
28
26
 
29
27
  Active — inverse-toned fill. Toggle the `selected` flag on the same chip element.
30
28
 
@@ -41,7 +39,7 @@ import { Chip } from '@teamblind-chorus/ui';
41
39
  </Chip>
42
40
  ```
43
41
 
44
- ### With icon
42
+ ## Leading icon
45
43
 
46
44
  Facet glyph before the label — tag for category, magnifier for search, check on selection.
47
45
 
@@ -59,7 +57,7 @@ import { CheckedIcon } from '@teamblind-chorus/ui/icons';
59
57
  </Chip>
60
58
  ```
61
59
 
62
- ### With trailing icon
60
+ ## Trailing icon
63
61
 
64
62
  Directional/dismiss glyph after the label — chevron-down to expand, *×* to clear.
65
63
 
@@ -77,7 +75,7 @@ import { XIcon } from '@teamblind-chorus/ui/icons';
77
75
  </Chip>
78
76
  ```
79
77
 
80
- ### With trailing action
78
+ ## Trailing action
81
79
 
82
80
  Pair the chip rail with a trailing accent [Text Button](../button/text.md) (`size='small'`, `appearance='accent'`) for a destination outside the filter axis — managing the set, opening keyword settings. The button is *not* a filter toggle; the chip track scrolls horizontally with a trailing 48px `mask-image` fade painted only while overflowing, and the button stays pinned outside the scroll viewport at `sys.layout.inline.xl` gap.
83
81
 
@@ -128,7 +126,21 @@ import { ArrowDownIcon } from '@teamblind-chorus/ui/icons';
128
126
  </div>
129
127
  ```
130
128
 
131
- ### Group
129
+ ## Focused
130
+
131
+ Both selection states take the same standard ring; the case below shows unselected.
132
+
133
+ ```preview
134
+ chip/filter/focused
135
+ ---
136
+ import { Chip } from '@teamblind-chorus/ui';
137
+
138
+ <Chip variant="filter" state="focused">
139
+ All
140
+ </Chip>
141
+ ```
142
+
143
+ ## Group
132
144
 
133
145
  Adjacent filter chips share a 4px gap (`sys.layout.inline.sm`), left-to-right; selection is independent per chip — Filter does not enforce single-select.
134
146
 
@@ -153,20 +165,6 @@ import { Chip } from '@teamblind-chorus/ui';
153
165
  </div>
154
166
  ```
155
167
 
156
- ### Focus indicator
157
-
158
- Both selection states take the same standard ring; the case below shows unselected.
159
-
160
- ```preview
161
- chip/filter/focused
162
- ---
163
- import { Chip } from '@teamblind-chorus/ui';
164
-
165
- <Chip variant="filter" state="focused">
166
- All
167
- </Chip>
168
- ```
169
-
170
168
  ## Slots
171
169
 
172
170
  - **label** — accessible name. Required, single line.
@@ -197,8 +195,8 @@ Single visual variant; the selected/unselected toggle swaps the container/label
197
195
 
198
196
  | State | Background | Border (1px `sys.borderWidth.hairline`) | Label / icon color |
199
197
  |--------------|-------------------------------------|---------------------------------------------------------|-----------------------------------|
200
- | unselected | `transparent` | `sys.color.outlineVariant` | `sys.color.onSurface` |
201
- | selected | `sys.color.inverseSurface` | `transparent` | `sys.color.inverseOnSurface` |
198
+ | unselected | `transparent` | `sys.color.border.default` | `sys.color.text.default` |
199
+ | selected | `sys.color.background.inverse` | `transparent` | `sys.color.text.inverse` |
202
200
 
203
201
  ## States
204
202
 
@@ -63,16 +63,16 @@
63
63
  "selectionStates": {
64
64
  "unselected": {
65
65
  "background": "transparent",
66
- "label": "sys.color.onSurface",
66
+ "label": "sys.color.text.default",
67
67
  "border": {
68
68
  "width": "sys.borderWidth.hairline",
69
- "color": "sys.color.outlineVariant"
69
+ "color": "sys.color.border.default"
70
70
  },
71
71
  "note": "Transparent fill so the chip adopts whatever surface sits behind it (page, raised card, sheet) without pinning to a fixed neutral step."
72
72
  },
73
73
  "selected": {
74
- "background": "sys.color.inverseSurface",
75
- "label": "sys.color.inverseOnSurface",
74
+ "background": "sys.color.background.inverse",
75
+ "label": "sys.color.text.inverse",
76
76
  "border": null
77
77
  }
78
78
  },
@@ -92,26 +92,49 @@
92
92
  "opacity": "sys.state.pressed"
93
93
  }
94
94
  },
95
+ "focused": {
96
+ "overlay": {
97
+ "color": "label",
98
+ "opacity": "sys.state.focus"
99
+ },
100
+ "focusRing": {
101
+ "composition": "outward",
102
+ "layer": "::after overlay — position:absolute, inset:0, no reflow (DESIGN.md Focus ring composition)",
103
+ "innerCounterRing": {
104
+ "width": "sys.borderWidth.hairline",
105
+ "color": "sys.color.border.focused"
106
+ },
107
+ "outerRing": {
108
+ "width": "sys.borderWidth.thin",
109
+ "color": "sys.color.border.focused"
110
+ }
111
+ },
112
+ "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 chip is in; never via plain mouse click."
113
+ },
95
114
  "disabled": {
96
115
  "overlay": null,
97
- "containerOpacity": "sys.state.disabled",
116
+ "background": "sys.color.background.disabled",
117
+ "label": "sys.color.text.disabled",
118
+ "border": {
119
+ "width": "sys.borderWidth.hairline",
120
+ "color": "sys.color.border.bold"
121
+ },
98
122
  "suppressFocusRing": true,
99
- "cursor": "not-allowed"
123
+ "cursor": "not-allowed",
124
+ "note": "Explicit disabled (no opacity): neutral disabled fill + bold border so the shape reads on any surface, plus disabled label. Overrides the rest/selected appearance."
100
125
  }
101
126
  },
102
127
  "focusIndicator": {
103
128
  "description": "Keyboard-focus visual — an accessibility indicator, not a lifecycle state. Composes over whichever lifecycle state the chip is in. The `states.focused` block above is kept for JSX runtime consumers; this block is the parallel external-reader contract.",
104
129
  "composition": "outward",
105
- "compositionReason": "Action affordance with breathing room around it; the 3px outward extent is reserved by the surrounding layout.",
130
+ "compositionReason": "Action affordance with breathing room around it; the 1px outward ring is reserved by the surrounding layout.",
106
131
  "overlay": {
107
132
  "color": "label",
108
133
  "opacity": "sys.state.focus"
109
134
  },
110
135
  "ring": {
111
- "outerWidth": "sys.borderWidth.thin",
112
- "outerColor": "sys.color.focus",
113
- "insetWidth": "sys.borderWidth.hairline",
114
- "insetColor": "sys.color.focusInset"
136
+ "width": "sys.borderWidth.hairline",
137
+ "color": "sys.color.border.focused"
115
138
  },
116
139
  "trigger": ":focus-visible (keyboard / programmatic focus, never plain mouse click)"
117
140
  },
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 🇰🇷 한국어: [`i18n/ko/schema/components/chip/tag.md`](../../../i18n/ko/schema/components/chip/tag.md)
4
4
 
5
- The informational chip — square-cornered label naming attached metadata (categories, statuses, content labels). Shorter than Filter (24 vs 32 min-height) with `sys.radius.sm` corners. Two appearances: `default` paints a translucent `sys.color.scrimSubtle` overlay (~8% inverse-tone, adopts whatever surface sits behind it); `accent` paints a tonal pale-primary container with primary label.
5
+ The informational chip — square-cornered label naming attached metadata (categories, statuses, content labels). Shorter than Filter (24 vs 32 min-height) with `sys.radius.sm` corners. Two appearances: `default` paints a translucent `sys.color.background.neutral` overlay (~8% inverse-tone, adopts whatever surface sits behind it); `accent` paints a tonal pale-primary container with primary label.
6
6
 
7
7
  **Reach for this when** you're naming attached metadata on rows, cards, or profiles. **Skip when** the marker signals unread / new activity on a host rather than describing it — use [Badge](../badge/badge.md) instead.
8
8
 
@@ -22,11 +22,9 @@ import { Chip } from '@teamblind-chorus/ui';
22
22
  </Chip>
23
23
  ```
24
24
 
25
- ## Use cases
25
+ ## Accent
26
26
 
27
- ### Accent
28
-
29
- Tonal pale-primary fill — `sys.color.primaryContainer` background, `sys.color.primary` label. Use when the tag should pop against the surface (Popular Tags in compose, highlighted hashtags); the default overlay is too quiet there.
27
+ Tonal pale-primary fill — `sys.color.background.selected` background, `sys.color.background.primary` label. Use when the tag should pop against the surface (Popular Tags in compose, highlighted hashtags); the default overlay is too quiet there.
30
28
 
31
29
  ```preview
32
30
  chip/tag/accent
@@ -38,7 +36,7 @@ import { Chip } from '@teamblind-chorus/ui';
38
36
  </Chip>
39
37
  ```
40
38
 
41
- ### Dismissable
39
+ ## Dismissable
42
40
 
43
41
  Opt-out — same chip with a trailing *×* to remove the tag. Trailing icon inherits label color via `currentColor`.
44
42
 
@@ -56,7 +54,22 @@ import { XIcon } from '@teamblind-chorus/ui/icons';
56
54
  </Chip>
57
55
  ```
58
56
 
59
- ### Group
57
+ ## Focused
58
+
59
+ Only the dismissable tag is focusable; the case below shows that form. See [Focus ring composition](../../DESIGN.md#focus-ring-composition).
60
+
61
+ ```preview
62
+ chip/tag/focused
63
+ ---
64
+ import { Chip } from '@teamblind-chorus/ui';
65
+ import { XIcon } from '@teamblind-chorus/ui/icons';
66
+
67
+ <Chip variant="tag" state="focused" trailingIcon={<XIcon />}>
68
+ Newsletter
69
+ </Chip>
70
+ ```
71
+
72
+ ## Group
60
73
 
61
74
  Adjacent tag chips share a 4px gap on both axes — `sys.layout.inline.sm` between siblings, `sys.layout.stack.2xs` between rows on wrap. Mixing with Filter is allowed — Tag's square + sunken tone vs Filter's pill + raised tone keeps roles legible. Tags are passive metadata, so collections exceeding the container's width **wrap** rather than scroll or truncate (set `display: flex; flex-wrap: wrap` on the container; do not use `overflow-x: auto` — horizontal scrolling belongs to tappable affordances).
62
75
 
@@ -76,21 +89,6 @@ import { Chip } from '@teamblind-chorus/ui';
76
89
  </div>
77
90
  ```
78
91
 
79
- ### Focus indicator
80
-
81
- Only the dismissable tag is focusable; the case below shows that form. See [Focus ring composition](../../DESIGN.md#focus-ring-composition).
82
-
83
- ```preview
84
- chip/tag/focused
85
- ---
86
- import { Chip } from '@teamblind-chorus/ui';
87
- import { XIcon } from '@teamblind-chorus/ui/icons';
88
-
89
- <Chip variant="tag" state="focused" trailingIcon={<XIcon />}>
90
- Newsletter
91
- </Chip>
92
- ```
93
-
94
92
  ## Slots
95
93
 
96
94
  - **label** — accessible name. Required, single line.
@@ -120,8 +118,8 @@ Two appearances; Tag never toggles.
120
118
 
121
119
  | Appearance | Background | Label / icon color |
122
120
  |------------|---------------------------------------------------------------------------------------------|------------------------------|
123
- | `default` | `sys.color.scrimSubtle` (translucent inverse-tone overlay — black 8% light / white 8% dark) | `sys.color.onSurface` |
124
- | `accent` | `sys.color.primaryContainer` (theme-aware) | `sys.color.primary` |
121
+ | `default` | `sys.color.background.neutral` (translucent inverse-tone overlay — black 8% light / white 8% dark) | `sys.color.text.default` |
122
+ | `accent` | `sys.color.background.selected` (theme-aware) | `sys.color.background.primary` |
125
123
 
126
124
  ## States
127
125