banhaten 0.4.0 → 0.6.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 (214) hide show
  1. package/CHANGELOG.md +78 -1
  2. package/CONTRIBUTING.md +6 -4
  3. package/README.md +4 -2
  4. package/docs/design-spacing.md +13 -0
  5. package/docs/design-system/README.md +2 -0
  6. package/docs/design-system/appearances/default.md +1 -1
  7. package/docs/design-system/appearances/rounded.md +1 -1
  8. package/docs/design-system/appearances/sharp.md +1 -1
  9. package/docs/design-system/component-showcase-consistency-report.md +5 -5
  10. package/docs/design-system/components/README.md +15 -0
  11. package/docs/design-system/components/accordion.md +1 -1
  12. package/docs/design-system/components/activity-feed.md +1 -1
  13. package/docs/design-system/components/activity-heatmap.md +1 -1
  14. package/docs/design-system/components/agent-input.md +85 -0
  15. package/docs/design-system/components/alert-dialog.md +1 -1
  16. package/docs/design-system/components/alert.md +10 -4
  17. package/docs/design-system/components/app-header.md +97 -0
  18. package/docs/design-system/components/approval-card.md +134 -0
  19. package/docs/design-system/components/attribute.md +14 -2
  20. package/docs/design-system/components/avatar.md +1 -1
  21. package/docs/design-system/components/badge.md +1 -1
  22. package/docs/design-system/components/banner.md +1 -1
  23. package/docs/design-system/components/breadcrumbs.md +8 -3
  24. package/docs/design-system/components/button-group.md +1 -1
  25. package/docs/design-system/components/button.md +8 -5
  26. package/docs/design-system/components/card.md +15 -1
  27. package/docs/design-system/components/catalog-components.md +1 -1
  28. package/docs/design-system/components/chart.md +95 -24
  29. package/docs/design-system/components/checkbox.md +1 -1
  30. package/docs/design-system/components/collapsible.md +20 -6
  31. package/docs/design-system/components/command.md +1 -1
  32. package/docs/design-system/components/context-menu.md +1 -1
  33. package/docs/design-system/components/date-picker.md +1 -1
  34. package/docs/design-system/components/file-upload.md +1 -1
  35. package/docs/design-system/components/hover-card.md +1 -1
  36. package/docs/design-system/components/inline-citations.md +93 -0
  37. package/docs/design-system/components/input-group.md +1 -1
  38. package/docs/design-system/components/input.md +59 -3
  39. package/docs/design-system/components/item.md +1 -1
  40. package/docs/design-system/components/kbd.md +6 -4
  41. package/docs/design-system/components/menu.md +35 -4
  42. package/docs/design-system/components/menubar.md +1 -1
  43. package/docs/design-system/components/modal.md +2 -2
  44. package/docs/design-system/components/native-select.md +1 -1
  45. package/docs/design-system/components/navigation-menu.md +1 -1
  46. package/docs/design-system/components/onboarding-step-list-item.md +1 -1
  47. package/docs/design-system/components/page-header.md +58 -17
  48. package/docs/design-system/components/pagination.md +2 -2
  49. package/docs/design-system/components/popover.md +1 -1
  50. package/docs/design-system/components/progress-slider.md +1 -1
  51. package/docs/design-system/components/radio-card.md +1 -1
  52. package/docs/design-system/components/radio-group.md +1 -1
  53. package/docs/design-system/components/resizable.md +1 -1
  54. package/docs/design-system/components/rolling-count.md +61 -0
  55. package/docs/design-system/components/scroll-area.md +1 -1
  56. package/docs/design-system/components/select.md +12 -2
  57. package/docs/design-system/components/sheet.md +1 -1
  58. package/docs/design-system/components/shimmer-text.md +67 -0
  59. package/docs/design-system/components/sidebar.md +75 -8
  60. package/docs/design-system/components/skeleton.md +2 -2
  61. package/docs/design-system/components/slideout.md +1 -1
  62. package/docs/design-system/components/slider.md +2 -2
  63. package/docs/design-system/components/social-button.md +2 -2
  64. package/docs/design-system/components/streaming-text.md +82 -0
  65. package/docs/design-system/components/table.md +103 -21
  66. package/docs/design-system/components/tabs.md +25 -6
  67. package/docs/design-system/components/tag.md +1 -1
  68. package/docs/design-system/components/task-list.md +95 -0
  69. package/docs/design-system/components/textarea.md +1 -1
  70. package/docs/design-system/components/thinking-reasoning.md +72 -0
  71. package/docs/design-system/components/thinking-state.md +67 -0
  72. package/docs/design-system/components/timeline.md +1 -1
  73. package/docs/design-system/components/toast.md +1 -1
  74. package/docs/design-system/components/toggle.md +2 -2
  75. package/docs/design-system/components/toolbar.md +11 -6
  76. package/docs/design-system/components/tooltip.md +6 -4
  77. package/docs/design-system/density-and-elevation.md +16 -16
  78. package/docs/design-system/design-system-test-missing-items.md +8 -8
  79. package/docs/design-system/installation.md +2 -0
  80. package/docs/design-system/tabs.md +2 -1
  81. package/docs/design-system/template-gap-audit.md +32 -0
  82. package/docs/design-system/templates.md +59 -0
  83. package/docs/design-system/token-governance.md +15 -2
  84. package/package.json +2 -2
  85. package/registry/components/accordion.tsx +2 -2
  86. package/registry/components/agent-input-parts.tsx +552 -0
  87. package/registry/components/agent-input-state.ts +156 -0
  88. package/registry/components/agent-input.css +307 -0
  89. package/registry/components/agent-input.tsx +382 -0
  90. package/registry/components/alert-dialog.tsx +2 -2
  91. package/registry/components/alert.tsx +23 -7
  92. package/registry/components/app-header.tsx +151 -0
  93. package/registry/components/approval-card.css +409 -0
  94. package/registry/components/approval-card.tsx +401 -0
  95. package/registry/components/approval-frame.tsx +108 -0
  96. package/registry/components/approval-questions.tsx +559 -0
  97. package/registry/components/attribute.tsx +48 -13
  98. package/registry/components/autocomplete.tsx +15 -68
  99. package/registry/components/avatar.tsx +2 -2
  100. package/registry/components/badge.tsx +8 -7
  101. package/registry/components/button-group.tsx +2 -2
  102. package/registry/components/button.tsx +19 -14
  103. package/registry/components/card.tsx +18 -5
  104. package/registry/components/chart-block-data.ts +177 -0
  105. package/registry/components/chart-block.css +171 -0
  106. package/registry/components/chart-block.tsx +260 -0
  107. package/registry/components/chart-interactions.ts +123 -0
  108. package/registry/components/chart-options.cartesian.ts +162 -0
  109. package/registry/components/chart-options.paint.ts +99 -0
  110. package/registry/components/chart-options.parts.ts +72 -0
  111. package/registry/components/chart-options.radial.ts +109 -0
  112. package/registry/components/chart-options.ts +44 -0
  113. package/registry/components/chart-options.types.ts +67 -0
  114. package/registry/components/chart.tsx +1 -1
  115. package/registry/components/checkbox.tsx +6 -6
  116. package/registry/components/collapsible.tsx +43 -3
  117. package/registry/components/command.tsx +1 -1
  118. package/registry/components/context-menu.tsx +1 -1
  119. package/registry/components/data-chart.tsx +40 -0
  120. package/registry/components/date-picker.tsx +9 -9
  121. package/registry/components/echart-theme.ts +246 -0
  122. package/registry/components/echart.css +49 -0
  123. package/registry/components/echart.tsx +178 -0
  124. package/registry/components/expanded/Breadcrumbs.tsx +5 -1
  125. package/registry/components/expanded/PageHeader.tsx +67 -24
  126. package/registry/components/expanded/Table.tsx +60 -22
  127. package/registry/components/expanded/activityFeed.css +2 -2
  128. package/registry/components/expanded/activityHeatmap.css +1 -1
  129. package/registry/components/expanded/banner.css +2 -2
  130. package/registry/components/expanded/breadcrumbs.css +19 -2
  131. package/registry/components/expanded/catalogComponentsShowcase.css +1 -1
  132. package/registry/components/expanded/fileUpload.css +7 -7
  133. package/registry/components/expanded/onboardingStepListItem.css +2 -2
  134. package/registry/components/expanded/pageHeader.css +38 -44
  135. package/registry/components/expanded/slideout.css +4 -4
  136. package/registry/components/expanded/table.css +58 -9
  137. package/registry/components/expanded/timeline.css +1 -1
  138. package/registry/components/hover-card.tsx +1 -1
  139. package/registry/components/inline-citations.css +126 -0
  140. package/registry/components/inline-citations.tsx +292 -0
  141. package/registry/components/input-group.tsx +4 -4
  142. package/registry/components/input.tsx +150 -137
  143. package/registry/components/item.tsx +1 -1
  144. package/registry/components/kbd.tsx +3 -2
  145. package/registry/components/menu.tsx +117 -12
  146. package/registry/components/menubar.tsx +2 -2
  147. package/registry/components/modal.tsx +3 -3
  148. package/registry/components/native-select.tsx +4 -4
  149. package/registry/components/navigation-menu.tsx +2 -2
  150. package/registry/components/pagination.tsx +4 -4
  151. package/registry/components/popover.tsx +3 -3
  152. package/registry/components/progress-slider.tsx +1 -1
  153. package/registry/components/radio-card.tsx +7 -7
  154. package/registry/components/radio-group.tsx +2 -2
  155. package/registry/components/resizable.tsx +2 -2
  156. package/registry/components/rolling-count.css +37 -0
  157. package/registry/components/rolling-count.tsx +110 -0
  158. package/registry/components/scroll-area.tsx +1 -1
  159. package/registry/components/select.tsx +15 -10
  160. package/registry/components/sheet.tsx +2 -2
  161. package/registry/components/shimmer-text.css +69 -0
  162. package/registry/components/shimmer-text.tsx +43 -0
  163. package/registry/components/sidebar.tsx +528 -223
  164. package/registry/components/skeleton.tsx +1 -1
  165. package/registry/components/slider.tsx +2 -2
  166. package/registry/components/social-button.tsx +1 -1
  167. package/registry/components/streaming-text.css +45 -0
  168. package/registry/components/streaming-text.tsx +165 -0
  169. package/registry/components/table-elements.tsx +164 -5
  170. package/registry/components/tabs.css +32 -4
  171. package/registry/components/tabs.tsx +5 -1
  172. package/registry/components/tag.tsx +1 -1
  173. package/registry/components/task-list.css +328 -0
  174. package/registry/components/task-list.tsx +429 -0
  175. package/registry/components/textarea.tsx +4 -4
  176. package/registry/components/thinking-reasoning.css +178 -0
  177. package/registry/components/thinking-reasoning.tsx +253 -0
  178. package/registry/components/thinking-state.css +8 -0
  179. package/registry/components/thinking-state.tsx +46 -0
  180. package/registry/components/toast.tsx +1 -1
  181. package/registry/components/toggle.tsx +2 -2
  182. package/registry/components/toolbar.tsx +6 -6
  183. package/registry/components/tooltip.tsx +6 -4
  184. package/registry/examples/agent-input-demo.tsx +90 -0
  185. package/registry/examples/app-header-demo.tsx +40 -0
  186. package/registry/examples/approval-card-demo.tsx +83 -0
  187. package/registry/examples/button-demo.tsx +3 -0
  188. package/registry/examples/chart-block-demo.tsx +5 -0
  189. package/registry/examples/chart-demo.tsx +6 -21
  190. package/registry/examples/collection-filter-bar-demo.tsx +1 -1
  191. package/registry/examples/control-density-contract-demo.tsx +5 -1
  192. package/registry/examples/inline-citations-demo.tsx +31 -0
  193. package/registry/examples/input-actions-demo.tsx +36 -0
  194. package/registry/examples/menu-demo.tsx +1 -1
  195. package/registry/examples/popover-demo.tsx +1 -0
  196. package/registry/examples/rolling-count-demo.tsx +39 -0
  197. package/registry/examples/shimmer-text-demo.tsx +20 -0
  198. package/registry/examples/streaming-text-demo.tsx +28 -0
  199. package/registry/examples/task-list-demo.tsx +74 -0
  200. package/registry/examples/thinking-reasoning-demo.tsx +74 -0
  201. package/registry/examples/thinking-state-demo.tsx +13 -0
  202. package/registry/guidance/charts.LICENSE.txt +24 -0
  203. package/registry/hooks/use-controllable-state.ts +33 -0
  204. package/registry/hooks/use-listbox-navigation.ts +73 -0
  205. package/registry/hooks/use-motion-end.ts +55 -0
  206. package/registry/hooks/use-reduced-motion.ts +28 -0
  207. package/registry/index.json +1808 -190
  208. package/registry/styles/globals.css +992 -1088
  209. package/registry.json +385 -10
  210. package/schema/registry.schema.json +5 -1
  211. package/schema/tokens.schema.json +8 -4
  212. package/src/cli/file-transaction.js +15 -8
  213. package/src/cli/index.js +22 -29
  214. package/tokens/banhaten.tokens.json +490 -192
@@ -0,0 +1,134 @@
1
+ # Approval Card
2
+
3
+ Approval cards pause the agent for a human decision. `ApprovalQuestions` walks through up to a few single-choice questions with a free-text "Something else" option, `ApprovalCommand` asks to run a shell command, and `ApprovalPlan` presents a short plan as a pending to-do list with an optional auto-approve countdown. All three compose `ApprovalFrame`, the shared shell: a tinted icon header, the body, and an action row. Compose it yourself for another kind of decision.
4
+
5
+ Questions slide vertically inside a viewport sized to the active one, step badges and countdown seconds roll their digits, and the plan composes Task List parts: a well root, a static header with the step count, and a preview fold.
6
+
7
+ ## API
8
+
9
+ ```tsx
10
+ import {
11
+ ApprovalCommand,
12
+ ApprovalPlan,
13
+ ApprovalQuestions,
14
+ } from "@/components/ui/approval-card"
15
+
16
+ <ApprovalQuestions
17
+ onApprove={({ answers }) => submit(answers)}
18
+ onReject={skip}
19
+ questions={questions}
20
+ />
21
+
22
+ <ApprovalCommand command="pnpm db:migrate && pnpm build" cwd="~/acme" onApprove={run} onReject={skip} />
23
+
24
+ <ApprovalPlan onApprove={({ source }) => approve(source)} onViewPlan={openPlan} plan={plan} planTitle="Session auth migration" />
25
+ ```
26
+
27
+ Every card also takes `title` and `approveLabel`, each with its own default, plus native `div` props.
28
+
29
+ ### ApprovalQuestions
30
+
31
+ | Prop | Type | Default | Description |
32
+ | --- | --- | --- | --- |
33
+ | `questions` | `readonly ApprovalQuestion[]` | – | `{ id, prompt, options }` answered one at a time. |
34
+ | `onApprove` | `(payload: { answers }) => void` | – | Fires once every question has an answer. |
35
+ | `onReject` / `rejectLabel` | `() => void` / `string` | – / `"Skip"` | The skip action. |
36
+ | `labels` | `Partial<ApprovalQuestionsLabels>` | English | Navigation and free-text copy. |
37
+
38
+ ### ApprovalCommand
39
+
40
+ | Prop | Type | Default | Description |
41
+ | --- | --- | --- | --- |
42
+ | `command` / `cwd` | `string` | – | Command block content and the working directory above it. |
43
+ | `onApprove` / `onReject` | `() => void` | – | Run and Skip actions. |
44
+ | `rejectLabel` | `string` | `"Skip"` | Skip action copy. |
45
+
46
+ ### ApprovalPlan
47
+
48
+ | Prop | Type | Default | Description |
49
+ | --- | --- | --- | --- |
50
+ | `plan` | `readonly ApprovalPlanStep[]` | – | Steps as `{ id, title }`, shown as pending to-do rows. Keep step details for the full plan that `onViewPlan` opens. |
51
+ | `planTitle` / `planSummary` | `React.ReactNode` | – | Intro copy above the steps. |
52
+ | `planPreviewCount` | `number` | `3` | Steps shown before the "more" fold. |
53
+ | `autoApproveSeconds` | `number` | `0` | Opt-in countdown. At zero `onApprove` fires with `source: "timeout"`; choosing either action cancels it, and a new value restarts it. |
54
+ | `onApprove` | `(payload: { source }) => void` | – | `source` is `"user"` for a click or `"timeout"` for the countdown. |
55
+ | `onViewPlan` / `viewPlanLabel` | `() => void` / `string` | – / `"View plan"` | Secondary action that opens the full plan. |
56
+ | `onDownload` / `onExpand` | `() => void` | – | Adds header actions. |
57
+ | `labels` | `Partial<ApprovalPlanLabels>` | English | Countdown, fold, and header action copy. |
58
+
59
+ ### ApprovalFrame
60
+
61
+ ```tsx
62
+ import { ApprovalFrame } from "@/components/ui/approval-card"
63
+
64
+ <ApprovalFrame
65
+ approveLabel="Apply"
66
+ icon={<GitCompareIcon />}
67
+ onApprove={apply}
68
+ onSecondary={review}
69
+ secondaryLabel="Review"
70
+ title="Apply this change?"
71
+ tone="warning"
72
+ >
73
+ <DiffSummary files={files} />
74
+ </ApprovalFrame>
75
+ ```
76
+
77
+ | Prop | Type | Default | Description |
78
+ | --- | --- | --- | --- |
79
+ | `icon` / `tone` | `React.ReactNode` / `"info" \| "warning" \| "success"` | – | Header glyph in a tile tinted by `tone`; the tile sets the glyph's size and stroke. The ready-made cards use info for questions, warning for commands, and success for plans. |
80
+ | `title` | `React.ReactNode` | – | Header copy. |
81
+ | `approveLabel` / `onApprove` / `approveDisabled` | `string` / `() => void` / `boolean` | – / – / `false` | Primary action, disabled until the decision is complete. |
82
+ | `secondaryLabel` / `onSecondary` | `string` / `() => void` | – | Secondary action. |
83
+ | `headerActions` / `footerStart` | `React.ReactNode` | – | Controls at the end of the header, and content at the start of the action row. |
84
+
85
+ ## Install
86
+
87
+ ```bash
88
+ npx banhaten add approval-card
89
+ ```
90
+
91
+ ## Examples
92
+
93
+ Primary registry example: `examples/approval-card-demo.tsx`.
94
+
95
+ ## Token Contract
96
+
97
+ The card uses `--bh-bg-raised`, `--bh-radius-2xl-12`, `--elevation-02`, and `--bh-space-xl-12` padding. Icon tiles map the info, warning, and success tones to `--bh-bg-info-subtle`, `--bh-bg-warning-subtle`, and `--bh-bg-success-subtle` with the matching content tokens, and stroke their glyph with `--bh-icon-stroke-200`. Options use `--bh-border-subtle`, `--bh-radius-lg-8`, and lift to `--elevation-02` when selected while their Kbd fills with `--bh-bg-inverse`. The command block uses `--bh-font-family-mono` on `--bh-bg-subtle`. Slides and fades use the `--bh-duration-slow` and `--bh-duration-moderate` foundation tokens with `--bh-easing-emphasized`. The countdown ring drains linearly over each one-second tick, and the countdown unmounts when its fade ends.
98
+
99
+ Covered source files: `components/approval-card.tsx`, the internal `components/approval-frame.tsx` and `components/approval-questions.tsx`, `components/approval-card.css`, and the `button`, `kbd`, `rolling-count`, `task-list`, and `tooltip` registry dependencies.
100
+
101
+ ## RTL Rules
102
+
103
+ - Header, options, and actions inherit direction and use logical alignment.
104
+ - The command block and working directory stay LTR; the submit arrow mirrors.
105
+
106
+ ## Accessibility
107
+
108
+ In `ApprovalQuestions`, each question is a group of native radio inputs labelled by its prompt: Tab reaches the group once, arrow keys move the choice without skipping ahead, the letter shown on each option picks it, and Enter confirms the answer. "Something else" is its own radio next to the text field, and Enter in the field commits the typed answer unless it is confirming an IME composition; inactive questions are hidden from assistive technology and removed from the tab order. Enter on the last answered question approves. The previous and next controls form a group named by `questionOf`, such as "Question 2 of 3". The countdown is a polite live region whose ring is also a labelled cancel button with a Tooltip. Slides, rolls, and fades collapse to instant changes under `prefers-reduced-motion`.
109
+
110
+ <!-- registry-contract:start -->
111
+ ## Registry Contract
112
+
113
+ This table is generated from `registry/index.json`; edit the component-owned source under `registry/catalog/components/`, not this table or the compiled index.
114
+ `supportedExports` is the exhaustive runtime import contract grouped by source module. `parts` is an ordered, non-exhaustive anatomy list.
115
+
116
+ | Field | Values |
117
+ | --- | --- |
118
+ | `supportedExports` | `components/approval-card.tsx: ApprovalCommand, ApprovalFrame, ApprovalPlan, ApprovalQuestions` |
119
+ | `parts` | `ApprovalQuestions`, `ApprovalCommand`, `ApprovalPlan`, `ApprovalFrame` |
120
+ | `registryDependencies` | `button`, `kbd`, `rolling-count`, `task-list`, `tooltip` |
121
+ | `props.questions` | `readonly ApprovalQuestion[]; default null; ApprovalQuestions: single-choice questions, each with a free-text Something else option.` |
122
+ | `props.command` | `string; default null; ApprovalCommand: shell command shown in the mono block.` |
123
+ | `props.cwd` | `string; default null; ApprovalCommand: working directory shown above the command.` |
124
+ | `props.plan` | `readonly ApprovalPlanStep[]; default null; ApprovalPlan: steps with an id and a title, rendered as a pending to-do list. Step details belong in the full plan.` |
125
+ | `props.planPreviewCount` | `number; default "3"; ApprovalPlan: steps shown before the more fold.` |
126
+ | `props.autoApproveSeconds` | `number; default "0"; ApprovalPlan: opt-in countdown; at zero onApprove fires with source timeout. Either action cancels it. A new value restarts it.` |
127
+ | `props.icon` | `React.ReactNode; default null; ApprovalFrame: header glyph, sized and stroked by the tinted icon tile.` |
128
+ | `props.tone` | `"info" \| "warning" \| "success"; default null; ApprovalFrame: tint of the icon tile. Questions use info, commands warning, and plans success.` |
129
+ | `props.onApprove` | `callback; default null; Primary action. ApprovalQuestions passes { answers }, ApprovalPlan passes { source: user \| timeout }, and ApprovalCommand passes nothing.` |
130
+ | `props.onReject` | `() => void; default null; Skip action on ApprovalQuestions and ApprovalCommand.` |
131
+ | `props.onViewPlan` | `() => void; default null; ApprovalPlan secondary action that opens the full plan; it also stops the countdown.` |
132
+ | `props.labels` | `Partial<ApprovalQuestionsLabels> \| Partial<ApprovalPlanLabels>; default "English"; Localized navigation, countdown, and helper copy for the card that uses them.` |
133
+ | `tokens` | `--bh-bg-info-subtle`, `--bh-bg-inverse`, `--bh-bg-raised`, `--bh-bg-subtle`, `--bh-bg-success-subtle`, `--bh-bg-warning-subtle`, `--bh-border-subtle`, `--bh-border-success-subtle`, `--bh-border-width-default`, `--bh-content-default`, `--bh-content-information-default`, `--bh-content-inverse`, `--bh-content-muted`, `--bh-content-subtle`, `--bh-content-success-default`, `--bh-content-warning-default`, `--bh-duration-default`, `--bh-duration-fast`, `--bh-duration-moderate`, `--bh-duration-slow`, `--bh-easing-emphasized`, `--bh-easing-standard`, `--bh-font-family`, `--bh-font-family-mono`, `--bh-icon-stroke-180`, `--bh-icon-stroke-200`, `--bh-icon-stroke-250`, `--bh-opacity-0`, `--bh-opacity-100`, `--bh-radius-2xl-12`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-radius-md-6`, `--bh-space-2xl-14`, `--bh-space-3xl-16`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-text-body-2xs-medium-font-size`, `--bh-text-body-2xs-medium-font-weight`, `--bh-text-body-2xs-regular-font-size`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-3xs-regular-font-size`, `--bh-text-body-3xs-regular-line-height`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-letter-spacing`, `--bh-text-body-md-medium-line-height`, `--bh-text-body-xs-medium-font-weight`, `--bh-text-body-xs-regular-font-size`, `--bh-text-body-xs-regular-font-weight`, `--bh-text-body-xs-regular-letter-spacing`, `--bh-text-body-xs-regular-line-height`, `--elevation-02`, `--elevation-component-focus-ring` |
134
+ <!-- registry-contract:end -->
@@ -31,10 +31,21 @@ Use `AttributeCard` for the surface, `AttributeHeader` for optional title and de
31
31
  | `AttributeList density` | `compact`, `default`, `comfortable` |
32
32
  | `AttributeList dividers` | `boolean` |
33
33
  | `AttributeList bordered` | `boolean` |
34
- | `AttributeList labelWidth` | CSS length, default `220px` |
34
+ | `AttributeList labelWidth` | CSS length, default `220px` (`176px` at comfortable density) |
35
35
  | `AttributeItem fullSpan` | `boolean`, useful in `two-column` layouts |
36
36
  | `AttributeItem truncate` | `boolean` |
37
37
 
38
+ ## Comfortable density
39
+
40
+ `density="comfortable"` is the label/value row of a detail card inside an app shell. Rows have no block padding and sit `16px` apart, so the list follows its card's own gap. The label is body-sm (`14px / 20px`) in content/subtle, in a `176px` column (`--bh-attribute-comfortable-label-width`), and the value is body-md medium, `16px` after it. Label and value align to the top of the row.
41
+
42
+ ```tsx
43
+ <AttributeList density="comfortable">
44
+ <AttributeItem label="Requester">Mohamed Ali</AttributeItem>
45
+ <AttributeItem label="Start date">1 December 2026</AttributeItem>
46
+ </AttributeList>
47
+ ```
48
+
38
49
  ## Composition
39
50
 
40
51
  Values are open React slots. Compose existing primitives for status badges, copy buttons, file rows, progress bars, skeleton bars, links, and custom details.
@@ -97,7 +108,8 @@ This table is generated from `registry/index.json`; edit the component-owned sou
97
108
  | `supportedExports` | `components/attribute.tsx: AttributeCard, AttributeHeader, AttributeItem, AttributeLabel, AttributeList, AttributeValue, attributeCardVariants, attributeHeaderVariants, attributeItemVariants, attributeLabelVariants, attributeListVariants, attributeValueVariants` |
98
109
  | `parts` | `AttributeCard`, `AttributeHeader`, `AttributeList`, `AttributeItem`, `AttributeLabel`, `AttributeValue` |
99
110
  | `registryDependencies` | None |
111
+ | `props.AttributeList.density` | `"compact" \| "default" \| "comfortable"; default "default"; compact and default set the row padding (8px, 16px). comfortable is the app-shell detail row: no row padding, rows 16px apart, a 14/20 label in a 176px column and 16px before the value.` |
100
112
  | `densities` | `compact`, `default`, `comfortable` |
101
113
  | `layouts` | `inline`, `end`, `stacked`, `two-column` |
102
- | `tokens` | `--bh-attribute-header-icon-size`, `--bh-attribute-header-icon-size-lg`, `--bh-attribute-label-width`, `--bh-bg-default`, `--bh-bg-neutral-soft`, `--bh-border-subtle`, `--bh-content-default`, `--bh-content-subtle`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-radius-2xl-12`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-md-8`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-letter-spacing`, `--bh-text-body-md-medium-line-height`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-font-weight`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-heading-sm-medium-font-size`, `--bh-text-heading-sm-medium-font-weight`, `--bh-text-heading-sm-medium-letter-spacing`, `--bh-text-heading-sm-medium-line-height` |
114
+ | `tokens` | `--bh-attribute-comfortable-label-width`, `--bh-attribute-header-icon-size`, `--bh-attribute-header-icon-size-lg`, `--bh-attribute-label-width`, `--bh-bg-default`, `--bh-bg-neutral-soft`, `--bh-border-subtle`, `--bh-content-default`, `--bh-content-subtle`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-radius-2xl-12`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-md-8`, `--bh-space-none`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-letter-spacing`, `--bh-text-body-md-medium-line-height`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-font-weight`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-letter-spacing`, `--bh-text-body-sm-regular-line-height`, `--bh-text-heading-sm-medium-font-size`, `--bh-text-heading-sm-medium-font-weight`, `--bh-text-heading-sm-medium-letter-spacing`, `--bh-text-heading-sm-medium-line-height` |
103
115
  <!-- registry-contract:end -->
@@ -66,5 +66,5 @@ This table is generated from `registry/index.json`; edit the component-owned sou
66
66
  | `forms` | `circular`, `rounded` |
67
67
  | `sizes` | `2xs`, `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, `4xl` |
68
68
  | `statusColors` | `offline`, `busy`, `blocked`, `available`, `away` |
69
- | `tokens` | `--bh-avatar-action-icon-size`, `--bh-avatar-action-icon-size-2xl`, `--bh-avatar-action-icon-size-3xl`, `--bh-avatar-action-icon-size-4xl`, `--bh-avatar-action-icon-size-large`, `--bh-avatar-action-icon-size-medium`, `--bh-avatar-action-icon-size-small`, `--bh-avatar-action-icon-size-tiny`, `--bh-avatar-action-icon-size-xlarge`, `--bh-avatar-action-icon-size-xsmall`, `--bh-avatar-action-size`, `--bh-avatar-action-size-2xl`, `--bh-avatar-action-size-3xl`, `--bh-avatar-action-size-4xl`, `--bh-avatar-action-size-large`, `--bh-avatar-action-size-medium`, `--bh-avatar-action-size-small`, `--bh-avatar-action-size-tiny`, `--bh-avatar-action-size-xlarge`, `--bh-avatar-action-size-xsmall`, `--bh-avatar-fallback-font-size-2xl`, `--bh-avatar-fallback-font-size-2xs`, `--bh-avatar-fallback-font-size-3xl`, `--bh-avatar-fallback-font-size-4xl`, `--bh-avatar-fallback-font-size-lg`, `--bh-avatar-fallback-font-size-md`, `--bh-avatar-fallback-font-size-sm`, `--bh-avatar-fallback-font-size-xl`, `--bh-avatar-fallback-font-size-xs`, `--bh-avatar-fallback-size`, `--bh-avatar-icon-size`, `--bh-avatar-icon-size-2xl`, `--bh-avatar-icon-size-2xs`, `--bh-avatar-icon-size-3xl`, `--bh-avatar-icon-size-4xl`, `--bh-avatar-icon-size-lg`, `--bh-avatar-icon-size-md`, `--bh-avatar-icon-size-sm`, `--bh-avatar-icon-size-xl`, `--bh-avatar-icon-size-xs`, `--bh-avatar-icon-slot-size`, `--bh-avatar-radius`, `--bh-avatar-size`, `--bh-avatar-size-2xl`, `--bh-avatar-size-2xs`, `--bh-avatar-size-3xl`, `--bh-avatar-size-4xl`, `--bh-avatar-size-lg`, `--bh-avatar-size-md`, `--bh-avatar-size-sm`, `--bh-avatar-size-xl`, `--bh-avatar-size-xs`, `--bh-avatar-status-ring`, `--bh-avatar-status-ring-default`, `--bh-avatar-status-ring-thin`, `--bh-avatar-status-size`, `--bh-avatar-status-size-large`, `--bh-avatar-status-size-medium`, `--bh-avatar-status-size-small`, `--bh-avatar-status-size-tiny`, `--bh-avatar-status-size-xlarge`, `--bh-avatar-status-size-xsmall`, `--bh-bg-accent-golden-default`, `--bh-bg-accent-red-default`, `--bh-bg-accent-sky-base`, `--bh-bg-default`, `--bh-bg-neutral-default`, `--bh-bg-neutral-medium`, `--bh-bg-neutral-subtle`, `--bh-bg-success-default`, `--bh-border-width-default`, `--bh-content-default`, `--bh-content-on-brand`, `--bh-font-weight-semibold`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-210`, `--bh-icon-stroke-240`, `--bh-icon-stroke-260`, `--bh-icon-stroke-280`, `--bh-interactive-brand-default`, `--bh-interactive-danger-default`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-space-md-8`, `--bh-space-none`, `--bh-space-xl-12`, `--bh-space-xxs-2`, `--bh-text-body-sm-medium-font-size`, `--shadow-avatar-badge` |
69
+ | `tokens` | `--bh-avatar-action-icon-size`, `--bh-avatar-action-icon-size-2xl`, `--bh-avatar-action-icon-size-3xl`, `--bh-avatar-action-icon-size-4xl`, `--bh-avatar-action-icon-size-large`, `--bh-avatar-action-icon-size-medium`, `--bh-avatar-action-icon-size-small`, `--bh-avatar-action-icon-size-tiny`, `--bh-avatar-action-icon-size-xlarge`, `--bh-avatar-action-icon-size-xsmall`, `--bh-avatar-action-size`, `--bh-avatar-action-size-2xl`, `--bh-avatar-action-size-3xl`, `--bh-avatar-action-size-4xl`, `--bh-avatar-action-size-large`, `--bh-avatar-action-size-medium`, `--bh-avatar-action-size-small`, `--bh-avatar-action-size-tiny`, `--bh-avatar-action-size-xlarge`, `--bh-avatar-action-size-xsmall`, `--bh-avatar-fallback-font-size-2xl`, `--bh-avatar-fallback-font-size-2xs`, `--bh-avatar-fallback-font-size-3xl`, `--bh-avatar-fallback-font-size-4xl`, `--bh-avatar-fallback-font-size-lg`, `--bh-avatar-fallback-font-size-md`, `--bh-avatar-fallback-font-size-sm`, `--bh-avatar-fallback-font-size-xl`, `--bh-avatar-fallback-font-size-xs`, `--bh-avatar-fallback-size`, `--bh-avatar-icon-size`, `--bh-avatar-icon-size-2xl`, `--bh-avatar-icon-size-2xs`, `--bh-avatar-icon-size-3xl`, `--bh-avatar-icon-size-4xl`, `--bh-avatar-icon-size-lg`, `--bh-avatar-icon-size-md`, `--bh-avatar-icon-size-sm`, `--bh-avatar-icon-size-xl`, `--bh-avatar-icon-size-xs`, `--bh-avatar-icon-slot-size`, `--bh-avatar-radius`, `--bh-avatar-size`, `--bh-avatar-size-2xl`, `--bh-avatar-size-2xs`, `--bh-avatar-size-3xl`, `--bh-avatar-size-4xl`, `--bh-avatar-size-lg`, `--bh-avatar-size-md`, `--bh-avatar-size-sm`, `--bh-avatar-size-xl`, `--bh-avatar-size-xs`, `--bh-avatar-status-ring`, `--bh-avatar-status-ring-default`, `--bh-avatar-status-ring-thin`, `--bh-avatar-status-size`, `--bh-avatar-status-size-large`, `--bh-avatar-status-size-medium`, `--bh-avatar-status-size-small`, `--bh-avatar-status-size-tiny`, `--bh-avatar-status-size-xlarge`, `--bh-avatar-status-size-xsmall`, `--bh-bg-accent-golden-default`, `--bh-bg-accent-red-default`, `--bh-bg-accent-sky-base`, `--bh-bg-default`, `--bh-bg-neutral-default`, `--bh-bg-neutral-medium`, `--bh-bg-neutral-subtle`, `--bh-bg-success-default`, `--bh-border-width-default`, `--bh-content-default`, `--bh-content-on-brand`, `--bh-font-weight-semibold`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-210`, `--bh-icon-stroke-240`, `--bh-icon-stroke-260`, `--bh-icon-stroke-280`, `--bh-interactive-brand-default`, `--bh-interactive-danger-default`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-space-md-8`, `--bh-space-none`, `--bh-space-xl-12`, `--bh-space-xxs-2`, `--bh-text-body-sm-medium-font-size`, `--elevation-avatar-badge` |
70
70
  <!-- registry-contract:end -->
@@ -67,6 +67,6 @@ This table is generated from `registry/index.json`; edit the component-owned sou
67
67
  | `options` | `splitAction` |
68
68
  | `sizes` | `sm`, `lg` |
69
69
  | `styles` | `light`, `outline`, `solid` |
70
- | `tokens` | `--bh-badge-focus-ring-width`, `--bh-badge-outline-border-width`, `--bh-bg-accent-amber-default`, `--bh-bg-accent-amber-subtle`, `--bh-bg-accent-blue-bold`, `--bh-bg-accent-blue-subtle`, `--bh-bg-accent-fuchsia-default`, `--bh-bg-accent-fuchsia-subtle`, `--bh-bg-accent-golden-default`, `--bh-bg-accent-golden-subtle`, `--bh-bg-accent-green-default`, `--bh-bg-accent-green-subtle`, `--bh-bg-accent-purple-default`, `--bh-bg-accent-purple-subtle`, `--bh-bg-accent-red-default`, `--bh-bg-accent-red-subtle`, `--bh-bg-accent-rose-bold`, `--bh-bg-accent-rose-subtle`, `--bh-bg-accent-sky-base`, `--bh-bg-accent-sky-subtle`, `--bh-bg-neutral-bold`, `--bh-bg-neutral-subtle`, `--bh-border-accent-amber-strong`, `--bh-border-accent-amber-subtle`, `--bh-border-accent-blue-strong`, `--bh-border-accent-blue-subtle`, `--bh-border-accent-fuchsia-strong`, `--bh-border-accent-fuchsia-subtle`, `--bh-border-accent-golden-strong`, `--bh-border-accent-golden-subtle`, `--bh-border-accent-green-strong`, `--bh-border-accent-green-subtle`, `--bh-border-accent-purple-strong`, `--bh-border-accent-purple-subtle`, `--bh-border-accent-red-strong`, `--bh-border-accent-red-subtle`, `--bh-border-accent-rose-strong`, `--bh-border-accent-rose-subtle`, `--bh-border-accent-sky-strong`, `--bh-border-accent-sky-subtle`, `--bh-border-default`, `--bh-border-focus`, `--bh-border-focus-danger`, `--bh-border-strong`, `--bh-border-subtle`, `--bh-content-accent-amber-default`, `--bh-content-accent-amber-strong`, `--bh-content-accent-blue-default`, `--bh-content-accent-blue-strong`, `--bh-content-accent-fuchsia-default`, `--bh-content-accent-fuchsia-strong`, `--bh-content-accent-golden-default`, `--bh-content-accent-golden-strong`, `--bh-content-accent-green-default`, `--bh-content-accent-green-strong`, `--bh-content-accent-purple-default`, `--bh-content-accent-purple-strong`, `--bh-content-accent-red-default`, `--bh-content-accent-red-strong`, `--bh-content-accent-rose-default`, `--bh-content-accent-rose-strong`, `--bh-content-accent-sky-default`, `--bh-content-accent-sky-strong`, `--bh-content-default`, `--bh-content-link`, `--bh-content-muted`, `--bh-content-on-color`, `--bh-content-on-light`, `--bh-content-on-neutral`, `--bh-interactive-ghost-default`, `--bh-opacity-35`, `--bh-radius-full`, `--bh-radius-xs-2`, `--bh-space-2xl-14`, `--bh-space-3xl-16`, `--bh-space-5xl-24`, `--bh-space-md-8`, `--bh-space-none`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-text-body-2xs-medium-font-size`, `--bh-text-body-2xs-medium-font-weight`, `--bh-text-body-2xs-medium-letter-spacing`, `--bh-text-body-2xs-medium-line-height`, `--bh-text-body-2xs-semibold-font-weight`, `--bh-text-body-3xs-medium-font-size`, `--bh-text-body-3xs-medium-font-weight`, `--bh-text-body-3xs-medium-letter-spacing`, `--bh-text-body-3xs-medium-line-height`, `--bh-text-body-3xs-semibold-font-weight` |
70
+ | `tokens` | `--bh-badge-focus-ring-width`, `--bh-badge-outline-border-width`, `--bh-bg-accent-amber-default`, `--bh-bg-accent-amber-subtle`, `--bh-bg-accent-blue-bold`, `--bh-bg-accent-blue-subtle`, `--bh-bg-accent-fuchsia-default`, `--bh-bg-accent-fuchsia-subtle`, `--bh-bg-accent-golden-default`, `--bh-bg-accent-golden-subtle`, `--bh-bg-accent-green-default`, `--bh-bg-accent-green-subtle`, `--bh-bg-accent-purple-default`, `--bh-bg-accent-purple-subtle`, `--bh-bg-accent-red-default`, `--bh-bg-accent-red-subtle`, `--bh-bg-accent-rose-bold`, `--bh-bg-accent-rose-subtle`, `--bh-bg-accent-sky-base`, `--bh-bg-accent-sky-subtle`, `--bh-bg-neutral-bold`, `--bh-bg-neutral-subtle`, `--bh-border-accent-amber-strong`, `--bh-border-accent-amber-subtle`, `--bh-border-accent-blue-strong`, `--bh-border-accent-blue-subtle`, `--bh-border-accent-fuchsia-strong`, `--bh-border-accent-fuchsia-subtle`, `--bh-border-accent-golden-strong`, `--bh-border-accent-golden-subtle`, `--bh-border-accent-green-strong`, `--bh-border-accent-green-subtle`, `--bh-border-accent-purple-strong`, `--bh-border-accent-purple-subtle`, `--bh-border-accent-red-strong`, `--bh-border-accent-red-subtle`, `--bh-border-accent-rose-strong`, `--bh-border-accent-rose-subtle`, `--bh-border-accent-sky-strong`, `--bh-border-accent-sky-subtle`, `--bh-border-default`, `--bh-border-focus`, `--bh-border-focus-danger`, `--bh-border-strong`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-content-accent-amber-default`, `--bh-content-accent-amber-strong`, `--bh-content-accent-blue-default`, `--bh-content-accent-blue-strong`, `--bh-content-accent-fuchsia-default`, `--bh-content-accent-fuchsia-strong`, `--bh-content-accent-golden-default`, `--bh-content-accent-golden-strong`, `--bh-content-accent-green-default`, `--bh-content-accent-green-strong`, `--bh-content-accent-purple-default`, `--bh-content-accent-purple-strong`, `--bh-content-accent-red-default`, `--bh-content-accent-red-strong`, `--bh-content-accent-rose-default`, `--bh-content-accent-rose-strong`, `--bh-content-accent-sky-default`, `--bh-content-accent-sky-strong`, `--bh-content-default`, `--bh-content-link`, `--bh-content-muted`, `--bh-content-on-color`, `--bh-content-on-light`, `--bh-content-on-neutral`, `--bh-interactive-ghost-default`, `--bh-opacity-35`, `--bh-radius-full`, `--bh-radius-xs-2`, `--bh-space-2xl-14`, `--bh-space-3xl-16`, `--bh-space-5xl-24`, `--bh-space-md-8`, `--bh-space-none`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-text-body-2xs-medium-font-size`, `--bh-text-body-2xs-medium-font-weight`, `--bh-text-body-2xs-medium-letter-spacing`, `--bh-text-body-2xs-medium-line-height`, `--bh-text-body-2xs-semibold-font-weight`, `--bh-text-body-3xs-medium-font-size`, `--bh-text-body-3xs-medium-font-weight`, `--bh-text-body-3xs-medium-letter-spacing`, `--bh-text-body-3xs-medium-line-height`, `--bh-text-body-3xs-semibold-font-weight` |
71
71
  | `types` | `default`, `leading-icon`, `trailing-icon`, `dot`, `flag` |
72
72
  <!-- registry-contract:end -->
@@ -98,6 +98,6 @@ This table is generated from `registry/index.json`; edit the component-owned sou
98
98
  | `modes` | `slim`, `single-action-inline`, `single-action`, `input` |
99
99
  | `sizes` | `floating`, `full-width` |
100
100
  | `states` | `title`, `title-description` |
101
- | `tokens` | `--bh-bg-brand-bold`, `--bh-bg-brand-soft`, `--bh-bg-muted`, `--bh-border-subtle`, `--bh-content-default`, `--bh-content-link`, `--bh-content-on-brand`, `--bh-content-subtle`, `--bh-font-family`, `--bh-font-weight-regular`, `--bh-font-weight-semibold`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-210`, `--bh-icon-stroke-225`, `--bh-input-width`, `--bh-radius-xl-10`, `--bh-space-17xl-288`, `--bh-space-19xl-384`, `--bh-space-3xl-16`, `--bh-space-8xl-48`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-text-body-2xs-medium-font-size`, `--bh-text-body-2xs-medium-font-weight`, `--bh-text-body-2xs-medium-line-height`, `--bh-text-body-lg-medium-font-size`, `--bh-text-body-lg-medium-font-weight`, `--bh-text-body-lg-medium-line-height`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-letter-spacing`, `--bh-text-body-md-medium-line-height`, `--shadow-xl` |
101
+ | `tokens` | `--bh-bg-brand-bold`, `--bh-bg-brand-soft`, `--bh-bg-muted`, `--bh-border-subtle`, `--bh-content-default`, `--bh-content-link`, `--bh-content-on-brand`, `--bh-content-subtle`, `--bh-font-family`, `--bh-font-weight-regular`, `--bh-font-weight-semibold`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-210`, `--bh-icon-stroke-225`, `--bh-input-width`, `--bh-radius-xl-10`, `--bh-space-17xl-288`, `--bh-space-19xl-384`, `--bh-space-3xl-16`, `--bh-space-8xl-48`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-text-body-2xs-medium-font-size`, `--bh-text-body-2xs-medium-font-weight`, `--bh-text-body-2xs-medium-line-height`, `--bh-text-body-lg-medium-font-size`, `--bh-text-body-lg-medium-font-weight`, `--bh-text-body-lg-medium-line-height`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-letter-spacing`, `--bh-text-body-md-medium-line-height`, `--elevation-05` |
102
102
  | `variants` | `brand`, `grey`, `brand-light` |
103
103
  <!-- registry-contract:end -->
@@ -28,6 +28,7 @@ Use semantic names in code even if the Figma variant labels are title-cased.
28
28
  ```ts
29
29
  type BreadcrumbSeparator = "slash" | "chevron";
30
30
  type BreadcrumbStyle = "default" | "raised";
31
+ type BreadcrumbSize = "md" | "sm";
31
32
 
32
33
  type BreadcrumbItem = {
33
34
  label: string;
@@ -40,6 +41,7 @@ type BreadcrumbsProps = {
40
41
  items: BreadcrumbItem[];
41
42
  separator?: BreadcrumbSeparator;
42
43
  style?: BreadcrumbStyle;
44
+ size?: BreadcrumbSize;
43
45
  iconOnly?: boolean;
44
46
  dir?: "ltr" | "rtl" | "auto";
45
47
  maxItems?: number;
@@ -55,9 +57,11 @@ type BreadcrumbsProps = {
55
57
  - The default style has no container background and uses a compact inline gap.
56
58
  - The raised style adds a surface fill, a 1px border, 12px padding, and an 8px radius in the Figma source.
57
59
  - Text items use `Body/body-md (base)/medium` with a 15px size and 24px line height.
60
+ - `size="sm"` uses `Body/body-sm/medium` with a 14px size, a 20px line height, and a 12px separator, so labels sit 20px apart, as in a 14px trail written with two spaces on each side of the slash.
58
61
  - Item glyphs are 18px square inside 20px slots.
59
62
  - Slash and chevron separators are 16px square and should be decorative.
60
63
  - LTR chevrons point right; RTL chevrons point left.
64
+ - Each label resolves its own content direction, so an opposing script truncates at its actual end while the trail keeps the inherited layout direction.
61
65
  - The icon-only type keeps the same item and separator rhythm but hides visible labels.
62
66
  - Do not allow wrapping inside individual breadcrumb labels unless the component has a deliberate collapsed or overflow mode.
63
67
 
@@ -68,7 +72,7 @@ Breadcrumbs should consume existing semantic tokens:
68
72
  ```ts
69
73
  export const breadcrumbsTokens = {
70
74
  itemColor: "content.subtle",
71
- currentItemColor: "content.default",
75
+ currentItemColor: "content.default", // --bh-breadcrumbs-current-content
72
76
  iconColor: "content.subtle",
73
77
  separatorColor: "content.subtle",
74
78
  raisedBackground: "background.card",
@@ -171,7 +175,7 @@ Covered source files: `components/expanded/breadcrumbs.css`, `components/expande
171
175
 
172
176
  ## RTL Rules
173
177
 
174
- This component has no special RTL contract beyond inherited document direction and logical spacing.
178
+ The ordered trail inherits document direction and uses logical spacing. Each independently truncated label uses `dir="auto"`, preserving the identifying start of Latin content in an RTL trail (and Arabic content in an LTR trail) without changing item order, icon placement, or separator mirroring.
175
179
 
176
180
  <!-- registry-contract:start -->
177
181
  ## Registry Contract
@@ -185,7 +189,8 @@ This table is generated from `registry/index.json`; edit the component-owned sou
185
189
  | `parts` | `Breadcrumbs` |
186
190
  | `registryDependencies` | None |
187
191
  | `modes` | `text`, `icon-only`, `slash`, `chevron` |
192
+ | `sizes` | `md`, `sm` |
188
193
  | `states` | `link`, `current`, `overflow` |
189
- | `tokens` | `--bh-bg-raised`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-content-default`, `--bh-content-subtle`, `--bh-focus-ring-width`, `--bh-font-family`, `--bh-font-weight-medium`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-190`, `--bh-icon-stroke-200`, `--bh-icon-stroke-225`, `--bh-interactive-ghost-hover`, `--bh-radius-lg-8`, `--bh-radius-sm-4`, `--bh-space-3xl-16`, `--bh-space-6xl-32`, `--bh-space-md-8`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-text-base-letter-spacing`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-md-font-size`, `--bh-text-body-md-line-height` |
194
+ | `tokens` | `--bh-bg-raised`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-breadcrumbs-current-content`, `--bh-content-default`, `--bh-content-subtle`, `--bh-focus-ring-width`, `--bh-font-family`, `--bh-font-weight-medium`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-190`, `--bh-icon-stroke-200`, `--bh-icon-stroke-225`, `--bh-interactive-ghost-hover`, `--bh-radius-lg-8`, `--bh-radius-sm-4`, `--bh-space-3xl-16`, `--bh-space-6xl-32`, `--bh-space-md-8`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-text-base-letter-spacing`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-md-font-size`, `--bh-text-body-md-line-height`, `--bh-text-body-sm-medium-font-size`, `--bh-text-body-sm-medium-font-weight`, `--bh-text-body-sm-medium-letter-spacing`, `--bh-text-body-sm-medium-line-height` |
190
195
  | `variants` | `default`, `raised` |
191
196
  <!-- registry-contract:end -->
@@ -113,6 +113,6 @@ This table is generated from `registry/index.json`; edit the component-owned sou
113
113
  | `modes` | `default`, `iconOnly` |
114
114
  | `selectionTypes` | `single`, `multiple` |
115
115
  | `sizes` | `md`, `sm`, `xs` |
116
- | `tokens` | `--bh-border-width-default`, `--bh-button-group-bg`, `--bh-button-group-border`, `--bh-button-group-content`, `--bh-button-group-content-active`, `--bh-button-group-content-disabled`, `--bh-button-group-divider`, `--bh-button-group-icon-size`, `--bh-button-group-icon-slot-size`, `--bh-button-group-item-active-bg`, `--bh-button-group-item-bg`, `--bh-button-group-item-disabled-bg`, `--bh-button-group-item-hover-bg`, `--bh-button-group-item-pressed-bg`, `--bh-button-group-label-padding-x`, `--bh-button-group-md-gap`, `--bh-button-group-md-height`, `--bh-button-group-md-icon-padding-x`, `--bh-button-group-md-item-rtl-width`, `--bh-button-group-md-item-width`, `--bh-button-group-md-padding-x`, `--bh-button-group-radius`, `--bh-button-group-sm-gap`, `--bh-button-group-sm-height`, `--bh-button-group-sm-icon-padding-x`, `--bh-button-group-sm-item-rtl-width`, `--bh-button-group-sm-item-width`, `--bh-button-group-sm-padding-x`, `--bh-button-group-xs-gap`, `--bh-button-group-xs-height`, `--bh-button-group-xs-icon-padding-x`, `--bh-button-group-xs-item-rtl-width`, `--bh-button-group-xs-item-width`, `--bh-button-group-xs-padding-x`, `--bh-text-button-font-size`, `--bh-text-button-font-weight`, `--bh-text-button-letter-spacing`, `--bh-text-button-line-height`, `--bh-z-raised`, `--shadow-button-group`, `--shadow-button-group-focus`, `--shadow-component-default` |
116
+ | `tokens` | `--bh-border-width-default`, `--bh-button-group-bg`, `--bh-button-group-border`, `--bh-button-group-content`, `--bh-button-group-content-active`, `--bh-button-group-content-disabled`, `--bh-button-group-divider`, `--bh-button-group-icon-size`, `--bh-button-group-icon-slot-size`, `--bh-button-group-item-active-bg`, `--bh-button-group-item-bg`, `--bh-button-group-item-disabled-bg`, `--bh-button-group-item-hover-bg`, `--bh-button-group-item-pressed-bg`, `--bh-button-group-label-padding-x`, `--bh-button-group-md-gap`, `--bh-button-group-md-height`, `--bh-button-group-md-icon-padding-x`, `--bh-button-group-md-item-rtl-width`, `--bh-button-group-md-item-width`, `--bh-button-group-md-padding-x`, `--bh-button-group-radius`, `--bh-button-group-sm-gap`, `--bh-button-group-sm-height`, `--bh-button-group-sm-icon-padding-x`, `--bh-button-group-sm-item-rtl-width`, `--bh-button-group-sm-item-width`, `--bh-button-group-sm-padding-x`, `--bh-button-group-xs-gap`, `--bh-button-group-xs-height`, `--bh-button-group-xs-icon-padding-x`, `--bh-button-group-xs-item-rtl-width`, `--bh-button-group-xs-item-width`, `--bh-button-group-xs-padding-x`, `--bh-text-button-font-size`, `--bh-text-button-font-weight`, `--bh-text-button-letter-spacing`, `--bh-text-button-line-height`, `--bh-z-raised`, `--elevation-button-group`, `--elevation-button-group-focus`, `--elevation-component-default` |
117
117
  | `widths` | `fixed`, `content` |
118
118
  <!-- registry-contract:end -->
@@ -12,12 +12,14 @@ Button is the primary action primitive for forms, workflows, navigation actions,
12
12
 
13
13
  | Prop | Values | Default |
14
14
  | --- | --- | --- |
15
- | `variant` | `default`, `secondary`, `soft`, `outline`, `ghost`, `ghost-primary`, `danger`, `soft-danger`, `white` | `default` |
15
+ | `variant` | `default`, `secondary`, `soft`, `outline`, `ghost`, `ghost-primary`, `danger`, `soft-danger`, `white`, `success`, `warning`, `link` | `default` |
16
16
  | `size` | `xs`, `sm`, `default`, `lg`, `xl`, `icon-xs`, `icon-sm`, `icon`, `icon-lg`, `icon-xl` | `default` |
17
17
  | `asChild` | `boolean` | `false` |
18
18
  | `disabled` | `boolean` | `false` |
19
19
  | `aria-busy` | `boolean` | none |
20
20
 
21
+ `destructive` and `soft-destructive` remain accepted as deprecated compatibility aliases. Replace them with `danger` and `soft-danger`, respectively; the aliases are not separate visual treatments and will remain available for the repository's documented deprecation window.
22
+
21
23
  ## Examples
22
24
 
23
25
  ```tsx
@@ -49,13 +51,13 @@ Button is the primary action primitive for forms, workflows, navigation actions,
49
51
 
50
52
  ## Token Contract
51
53
 
52
- Button consumes semantic interaction, content, border, radius, typography, spacing, opacity, and shadow tokens. Component source should not hard-code Figma color, spacing, radius, or shadow literals.
54
+ Button consumes semantic interaction, content, border, radius, typography, spacing, opacity, and elevation tokens. Component source should not hard-code Figma color, spacing, radius, or elevation recipes.
53
55
 
54
56
  - Typography: `--bh-text-button-font-size`, `--bh-text-button-line-height`, `--bh-text-button-font-weight`, `--bh-text-button-letter-spacing`
55
57
  - Shape: `--bh-control-default`
56
58
  - Heights and padding: `--bh-button-*-height`, `--bh-button-*-padding-x`, `--bh-button-*-padding-y`
57
59
  - Icon rhythm: `--bh-button-icon-size` is an 18px glyph inside `--bh-button-icon-slot-size` at 20px; `--bh-button-icon-offset` and `--bh-button-label-padding-x` preserve optical alignment.
58
- - Effects: `--shadow-button-raised`, `--shadow-button-secondary`, `--shadow-button-soft`, `--shadow-button-focus`, `--shadow-button-danger-focus`
60
+ - Effects: `--elevation-button-raised`, `--elevation-button-secondary`, `--elevation-button-soft`, `--elevation-button-focus`, `--elevation-button-danger-focus`
59
61
 
60
62
  ## RTL Rules
61
63
 
@@ -87,9 +89,10 @@ This table is generated from `registry/index.json`; edit the component-owned sou
87
89
  | `supportedExports` | `components/button.tsx: Button, buttonVariants` |
88
90
  | `parts` | `Button` |
89
91
  | `registryDependencies` | `spinner` |
92
+ | `props.variant` | `"default" \| "secondary" \| "soft" \| "outline" \| "ghost" \| "ghost-primary" \| "danger" \| "soft-danger" \| "white" \| "success" \| "warning" \| "link" \| "destructive" \| "soft-destructive"; default "default"; Twelve canonical semantic treatments. The deprecated destructive and soft-destructive compatibility aliases remain accepted and map to danger and soft-danger, respectively.` |
90
93
  | `props.density` | `"compact" \| "default" \| "comfortable"; default "comfortable"; Preferred cross-component sizing contract. Maps to legacy xs, sm, or default sizes while preserving icon-only sizing.` |
91
94
  | `densities` | `compact`, `default`, `comfortable` |
92
95
  | `sizes` | `xs`, `sm`, `default`, `lg`, `xl`, `icon-xs`, `icon-sm`, `icon`, `icon-lg`, `icon-xl` |
93
- | `tokens` | `--bh-bg-always-white`, `--bh-border-brand-strong`, `--bh-border-danger-strong`, `--bh-border-default`, `--bh-border-disabled`, `--bh-border-focus`, `--bh-border-width-default`, `--bh-button-gap`, `--bh-button-gap-xs`, `--bh-button-icon-offset`, `--bh-button-icon-size`, `--bh-button-icon-slot-size`, `--bh-button-label-padding-x`, `--bh-button-lg-height`, `--bh-button-lg-padding-x`, `--bh-button-lg-padding-y`, `--bh-button-md-height`, `--bh-button-md-padding-x`, `--bh-button-secondary-border`, `--bh-button-secondary-focus-border`, `--bh-button-sm-height`, `--bh-button-sm-padding-x`, `--bh-button-sm-padding-y`, `--bh-button-xl-height`, `--bh-button-xl-padding-y`, `--bh-button-xs-height`, `--bh-button-xs-padding-x`, `--bh-content-brand-default`, `--bh-content-danger-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-on-brand`, `--bh-content-on-color`, `--bh-content-on-light`, `--bh-content-on-success`, `--bh-content-on-warning`, `--bh-control-default`, `--bh-interactive-brand-default`, `--bh-interactive-brand-disabled`, `--bh-interactive-brand-hover`, `--bh-interactive-brand-pressed`, `--bh-interactive-danger-default`, `--bh-interactive-danger-disabled`, `--bh-interactive-danger-hover`, `--bh-interactive-danger-pressed`, `--bh-interactive-danger-soft-default`, `--bh-interactive-danger-soft-disabled`, `--bh-interactive-danger-soft-hover`, `--bh-interactive-danger-soft-pressed`, `--bh-interactive-ghost-default`, `--bh-interactive-ghost-disabled`, `--bh-interactive-ghost-hover`, `--bh-interactive-ghost-pressed`, `--bh-interactive-outlined-default`, `--bh-interactive-outlined-disabled`, `--bh-interactive-outlined-hover`, `--bh-interactive-outlined-pressed`, `--bh-interactive-secondary-default`, `--bh-interactive-secondary-disabled`, `--bh-interactive-secondary-hover`, `--bh-interactive-secondary-pressed`, `--bh-interactive-soft-default`, `--bh-interactive-soft-disabled`, `--bh-interactive-soft-hover`, `--bh-interactive-soft-pressed`, `--bh-interactive-success-default`, `--bh-interactive-success-hover`, `--bh-interactive-success-pressed`, `--bh-interactive-warning-default`, `--bh-interactive-warning-hover`, `--bh-interactive-warning-pressed`, `--bh-opacity-30`, `--bh-space-none`, `--bh-text-button-font-size`, `--bh-text-button-font-weight`, `--bh-text-button-letter-spacing`, `--bh-text-button-line-height`, `--shadow-button-danger-focus`, `--shadow-button-focus`, `--shadow-button-raised`, `--shadow-button-secondary`, `--shadow-button-secondary-focus`, `--shadow-button-soft` |
94
- | `variants` | `default`, `secondary`, `soft`, `outline`, `ghost`, `ghost-primary`, `danger`, `soft-danger`, `white` |
96
+ | `tokens` | `--bh-bg-always-white`, `--bh-border-brand-strong`, `--bh-border-danger-strong`, `--bh-border-default`, `--bh-border-disabled`, `--bh-border-focus`, `--bh-border-width-default`, `--bh-button-gap`, `--bh-button-gap-xs`, `--bh-button-icon-offset`, `--bh-button-icon-size`, `--bh-button-icon-slot-size`, `--bh-button-label-padding-x`, `--bh-button-lg-height`, `--bh-button-lg-padding-x`, `--bh-button-lg-padding-y`, `--bh-button-md-height`, `--bh-button-md-padding-x`, `--bh-button-secondary-border`, `--bh-button-secondary-focus-border`, `--bh-button-sm-height`, `--bh-button-sm-padding-x`, `--bh-button-sm-padding-y`, `--bh-button-xl-height`, `--bh-button-xl-padding-y`, `--bh-button-xs-height`, `--bh-button-xs-padding-x`, `--bh-content-brand-default`, `--bh-content-danger-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-on-brand`, `--bh-content-on-color`, `--bh-content-on-light`, `--bh-content-on-success`, `--bh-content-on-warning`, `--bh-control-default`, `--bh-interactive-brand-default`, `--bh-interactive-brand-disabled`, `--bh-interactive-brand-hover`, `--bh-interactive-brand-pressed`, `--bh-interactive-danger-default`, `--bh-interactive-danger-disabled`, `--bh-interactive-danger-hover`, `--bh-interactive-danger-pressed`, `--bh-interactive-danger-soft-default`, `--bh-interactive-danger-soft-disabled`, `--bh-interactive-danger-soft-hover`, `--bh-interactive-danger-soft-pressed`, `--bh-interactive-ghost-default`, `--bh-interactive-ghost-disabled`, `--bh-interactive-ghost-hover`, `--bh-interactive-ghost-pressed`, `--bh-interactive-outlined-default`, `--bh-interactive-outlined-disabled`, `--bh-interactive-outlined-hover`, `--bh-interactive-outlined-pressed`, `--bh-interactive-secondary-default`, `--bh-interactive-secondary-disabled`, `--bh-interactive-secondary-hover`, `--bh-interactive-secondary-pressed`, `--bh-interactive-soft-default`, `--bh-interactive-soft-disabled`, `--bh-interactive-soft-hover`, `--bh-interactive-soft-pressed`, `--bh-interactive-success-default`, `--bh-interactive-success-hover`, `--bh-interactive-success-pressed`, `--bh-interactive-warning-default`, `--bh-interactive-warning-hover`, `--bh-interactive-warning-pressed`, `--bh-opacity-30`, `--bh-space-none`, `--bh-text-button-font-size`, `--bh-text-button-font-weight`, `--bh-text-button-letter-spacing`, `--bh-text-button-line-height`, `--elevation-button-danger-focus`, `--elevation-button-focus`, `--elevation-button-raised`, `--elevation-button-secondary`, `--elevation-button-secondary-focus`, `--elevation-button-soft` |
97
+ | `variants` | `default`, `secondary`, `soft`, `outline`, `ghost`, `ghost-primary`, `danger`, `soft-danger`, `white`, `success`, `warning`, `link` |
95
98
  <!-- registry-contract:end -->
@@ -48,8 +48,19 @@ export function Example() {
48
48
  | `orientation` | `vertical`, `horizontal` |
49
49
  | `type` | `leading-image`, `trailing-image`, `full-image` |
50
50
  | `breakpoint` | `desktop`, `mobile` |
51
+ | `size` | `default`, `comfortable` |
51
52
  | `dir` | inherited, `ltr`, `rtl` |
52
53
 
54
+ ## Comfortable size
55
+
56
+ `size="comfortable"` is the panel inside an app shell. It keeps the card surface, background and 1px inset edge, and uses a `12px` radius (`--bh-card-comfortable-radius`) where the library cards use `16px`. `Card` and `CardSurface` both take it, and both render `data-size`.
57
+
58
+ ```tsx
59
+ <CardSurface size="comfortable" className="p-[var(--bh-space-5xl-24)]">
60
+ <h2>Position details</h2>
61
+ </CardSurface>
62
+ ```
63
+
53
64
  ## RTL
54
65
 
55
66
  Card uses logical spacing, inherited direction, and `dir="auto"` copy wrappers so title, subtitle, description, footer actions, and menu affordances mirror naturally.
@@ -96,8 +107,11 @@ This table is generated from `registry/index.json`; edit the component-owned sou
96
107
  | `supportedExports` | `components/card.tsx: Card, CardActions, CardFooter, CardMedia, CardSurface, cardVariants` |
97
108
  | `parts` | `Card`, `CardSurface`, `CardMedia`, `CardFooter`, `CardActions` |
98
109
  | `registryDependencies` | `button` |
110
+ | `props.Card.size` | `"default" \| "comfortable"; default "default"; comfortable is the panel inside an app shell: the same surface and 1px inset edge with a 12px radius instead of the library card's 16px.` |
111
+ | `props.CardSurface.size` | `"default" \| "comfortable"; default "default"; The same sizes as Card, for compositions that bring their own layout.` |
99
112
  | `breakpoints` | `desktop`, `mobile` |
100
113
  | `orientations` | `vertical`, `horizontal` |
101
- | `tokens` | `--bh-bg-accent-green-subtle`, `--bh-bg-accent-sky-subtle`, `--bh-bg-neutral-subtle`, `--bh-bg-raised`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-card-actions-max-width`, `--bh-card-bg`, `--bh-card-border`, `--bh-card-content-padding`, `--bh-card-desktop-content-padding`, `--bh-card-desktop-full-height`, `--bh-card-desktop-horizontal-padding`, `--bh-card-desktop-media-height`, `--bh-card-full-height`, `--bh-card-horizontal-media-width`, `--bh-card-horizontal-min-height`, `--bh-card-horizontal-padding`, `--bh-card-horizontal-width`, `--bh-card-media-height`, `--bh-card-media-overlay`, `--bh-card-mobile-content-padding`, `--bh-card-mobile-full-height`, `--bh-card-mobile-horizontal-padding`, `--bh-card-mobile-media-height`, `--bh-card-more-icon-size`, `--bh-card-more-size`, `--bh-card-radius`, `--bh-card-vertical-width`, `--bh-content-default`, `--bh-content-on-color`, `--bh-content-subtle`, `--bh-control-default`, `--bh-interactive-ghost-hover`, `--bh-radius-3xl-16`, `--bh-space-3xl-16`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-md-8`, `--bh-space-xl-12`, `--bh-space-xxs-2`, `--bh-text-body-lg-regular-font-size`, `--bh-text-body-lg-regular-font-weight`, `--bh-text-body-lg-regular-letter-spacing`, `--bh-text-body-lg-regular-line-height`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-font-weight`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-heading-sm-semibold-font-size`, `--bh-text-heading-sm-semibold-font-weight`, `--bh-text-heading-sm-semibold-letter-spacing`, `--bh-text-heading-sm-semibold-line-height`, `--shadow-button-focus` |
114
+ | `sizes` | `default`, `comfortable` |
115
+ | `tokens` | `--bh-bg-accent-green-subtle`, `--bh-bg-accent-sky-subtle`, `--bh-bg-neutral-subtle`, `--bh-bg-raised`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-card-actions-max-width`, `--bh-card-bg`, `--bh-card-border`, `--bh-card-comfortable-radius`, `--bh-card-content-padding`, `--bh-card-desktop-content-padding`, `--bh-card-desktop-full-height`, `--bh-card-desktop-horizontal-padding`, `--bh-card-desktop-media-height`, `--bh-card-full-height`, `--bh-card-horizontal-media-width`, `--bh-card-horizontal-min-height`, `--bh-card-horizontal-padding`, `--bh-card-horizontal-width`, `--bh-card-media-height`, `--bh-card-media-overlay`, `--bh-card-mobile-content-padding`, `--bh-card-mobile-full-height`, `--bh-card-mobile-horizontal-padding`, `--bh-card-mobile-media-height`, `--bh-card-more-icon-size`, `--bh-card-more-size`, `--bh-card-radius`, `--bh-card-vertical-width`, `--bh-content-default`, `--bh-content-on-color`, `--bh-content-subtle`, `--bh-control-default`, `--bh-interactive-ghost-hover`, `--bh-radius-3xl-16`, `--bh-space-3xl-16`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-md-8`, `--bh-space-xl-12`, `--bh-space-xxs-2`, `--bh-text-body-lg-regular-font-size`, `--bh-text-body-lg-regular-font-weight`, `--bh-text-body-lg-regular-letter-spacing`, `--bh-text-body-lg-regular-line-height`, `--bh-text-body-md-regular-font-size`, `--bh-text-body-md-regular-font-weight`, `--bh-text-body-md-regular-letter-spacing`, `--bh-text-body-md-regular-line-height`, `--bh-text-heading-sm-semibold-font-size`, `--bh-text-heading-sm-semibold-font-weight`, `--bh-text-heading-sm-semibold-letter-spacing`, `--bh-text-heading-sm-semibold-line-height`, `--elevation-button-focus` |
102
116
  | `types` | `leading-image`, `trailing-image`, `full-image` |
103
117
  <!-- registry-contract:end -->
@@ -57,6 +57,6 @@ This table is generated from `registry/index.json`; edit the component-owned sou
57
57
  | `registryDependencies` | `breadcrumbs`, `button`, `file-upload`, `table`, `tag` |
58
58
  | `modes` | `showcase`, `divider`, `content-divider` |
59
59
  | `states` | `label`, `action`, `disabled-action` |
60
- | `tokens` | `--bh-bg-raised`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-catalog-file-grid-max`, `--bh-catalog-file-grid-min`, `--bh-catalog-file-preview-max`, `--bh-catalog-file-preview-min`, `--bh-content-default`, `--bh-content-muted`, `--bh-content-subtle`, `--bh-control-default`, `--bh-focus-ring-width`, `--bh-font-family`, `--bh-font-weight-medium`, `--bh-font-weight-regular`, `--bh-interactive-ghost-hover`, `--bh-interactive-soft-default`, `--bh-opacity-65`, `--bh-radius-lg-8`, `--bh-space-2xl-14`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-text-base-letter-spacing`, `--bh-text-body-md-font-size`, `--bh-text-body-md-line-height`, `--bh-text-body-sm-regular-line-height`, `--bh-text-body-xs-regular-font-size`, `--bh-text-heading-xs-semibold-font-size`, `--bh-text-heading-xs-semibold-font-weight`, `--bh-text-heading-xs-semibold-line-height`, `--shadow-component-default` |
60
+ | `tokens` | `--bh-bg-raised`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-catalog-file-grid-max`, `--bh-catalog-file-grid-min`, `--bh-catalog-file-preview-max`, `--bh-catalog-file-preview-min`, `--bh-content-default`, `--bh-content-muted`, `--bh-content-subtle`, `--bh-control-default`, `--bh-focus-ring-width`, `--bh-font-family`, `--bh-font-weight-medium`, `--bh-font-weight-regular`, `--bh-interactive-ghost-hover`, `--bh-interactive-soft-default`, `--bh-opacity-65`, `--bh-radius-lg-8`, `--bh-space-2xl-14`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-text-base-letter-spacing`, `--bh-text-body-md-font-size`, `--bh-text-body-md-line-height`, `--bh-text-body-sm-regular-line-height`, `--bh-text-body-xs-regular-font-size`, `--bh-text-heading-xs-semibold-font-size`, `--bh-text-heading-xs-semibold-font-weight`, `--bh-text-heading-xs-semibold-line-height`, `--elevation-component-default` |
61
61
  | `variants` | `solid-divider`, `dotted-divider` |
62
62
  <!-- registry-contract:end -->
@@ -1,46 +1,110 @@
1
1
  # Chart
2
2
 
3
- Chart provides Recharts wrappers with Banhaten chart tokens, responsive sizing, tooltip content, and legend content.
3
+ Eight ECharts families and 18 ready-made blocks adapted from [EvilCharts](https://evilcharts.com/docs/echarts/area-chart/blocks), using Banhaten tokens throughout. Existing Recharts wrappers remain compatible.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npx banhaten add chart
9
+ ```
10
+
11
+ This installs the source modules and declares `echarts` and `recharts`. The playground loads the chart page separately; chart instances initialize when visible.
4
12
 
5
13
  ## API
6
14
 
15
+ Use `DataChart` for application data. Every series uses a Banhaten categorical palette index; raw colors are not part of the specification.
16
+
7
17
  ```tsx
8
- <ChartContainer config={{ revenue: { color: "var(--bh-chart-1)" } }}>
9
- <LineChart data={data}>
10
- <ChartTooltip content={<ChartTooltipContent />} />
11
- <Line dataKey="revenue" stroke="var(--color-revenue)" />
12
- </LineChart>
13
- </ChartContainer>
18
+ import { DataChart } from "@/components/ui/data-chart"
19
+ import type { ChartSpec } from "@/components/ui/chart-options"
20
+
21
+ const spec: ChartSpec = {
22
+ family: "area",
23
+ variant: "gradient",
24
+ categoryKey: "month",
25
+ data: [
26
+ { month: "Jan", revenue: 120, costs: 80 },
27
+ { month: "Feb", revenue: 180, costs: 95 },
28
+ { month: "Mar", revenue: 150, costs: 90 },
29
+ ],
30
+ series: [
31
+ { key: "revenue", label: "Revenue", colorIndex: 0 },
32
+ { key: "costs", label: "Costs", colorIndex: 1 },
33
+ ],
34
+ legend: true,
35
+ brush: true,
36
+ }
37
+
38
+ <DataChart label="Revenue and costs" description="Revenue exceeds costs in each month." spec={spec} />
14
39
  ```
15
40
 
41
+ `DataChart` accepts `spec`, required `label`, optional `description`, `loading`, `loadingLabel`, `emptyLabel`, and container attributes. `ChartSpec` selects `area`, `line`, `bar`, `composed`, `radar`, `pie`, `radial`, or `sankey`. Use `stack="stacked"` or `stack="percent"`, `horizontal`, `labels`, `grid`, `max`, `nodes`, and `links` where applicable. Curves are native ECharts approximations, matching the reference implementation; named spline variants are not mathematical spline implementations.
42
+
43
+ Shared parts support seven `legendIndicator` shapes; series `dotStyle` and `activeDotStyle`; `tooltipRadius`, `tooltipSurface`, `tooltipPosition`, and `tooltipCursor`. Translucent tooltips use the Banhaten opacity token. Brush controls are enabled with `brush: true`. Forecast `buffer` tails apply to independent series; stacked series retain their complete data to preserve totals.
44
+
45
+ Use `EChart` for advanced native options. Its `option(theme)` factory receives resolved inherited Banhaten colors, font metrics, spacing, radii, opacity, direction, and reduced-motion preference. An optional `onReady(chart, theme)` callback can attach interactions and return their cleanup. Native options supplied by callers must also use these tokens.
46
+
47
+ ```tsx
48
+ import { ChartBlock } from "@/components/ui/chart-block"
49
+
50
+ <ChartBlock block="latency" />
51
+ ```
52
+
53
+ `ChartBlock` provides example compositions with illustrative datasets. Time-range controls update both chart data and summaries. The visible data table uses the same filtered rows. Use `DataChart` for your own production data or adapt the installed block source.
54
+
55
+ | Family | Block IDs |
56
+ | --- | --- |
57
+ | Area | `latency`, `portfolio`, `benchmark`, `audience` |
58
+ | Line | `payouts`, `shipments` |
59
+ | Bar | `peak`, `grid`, `monospace` |
60
+ | Pie | `market-share`, `progress-rings`, `revenue-mix`, `reliability-score` |
61
+ | Radial | `budget`, `ride`, `cache-tiers` |
62
+ | Sankey | `allocation`, `pipeline` |
63
+
64
+ Legacy `ChartContainer`, `ChartTooltip`, `ChartTooltipContent`, `ChartLegend`, and `ChartLegendContent` still compose with Recharts. Their `config` maps series names to `--color-*` CSS variables.
65
+
16
66
  ## Token Contract
17
67
 
18
- Chart adds `--bh-chart-1` through `--bh-chart-5` via the token build. `ChartContainer` maps config entries to `--color-*` variables.
68
+ - Marks use the eight `--bh-chart-categorical-*` colors. Neutral tracks, surfaces, borders, and text use existing semantic tokens.
69
+ - Fonts, weights, sizes, line heights, spacing, radii, opacity, and animation duration resolve from the chart's inherited Banhaten tokens. No duplicate theme palette is maintained.
70
+ - Theme, font, radius, and direction changes update mounted charts. Existing legend selections and brush range survive theme updates.
71
+ - Gradient fills and patterns remain available. Colored blur shadows and glow variants are omitted. Tooltips have no shadow.
72
+ - Numeric data, chart percentages, angles, and geometry ratios describe the visualization rather than design tokens.
73
+ - All source modules, including TypeScript helpers, are included in token governance checks.
19
74
 
20
- ## RTL
75
+ ### Chart corner radii
21
76
 
22
- Chart layout depends on the Recharts configuration. Surrounding labels and legends use logical layout.
77
+ Keep chart marks subtly rounded so their endpoints and stacked joins remain easy to compare. Use `--bh-radius-sm-4` for exposed bar ends and separated donut slices: 4px in the default theme, 6px in rounded mode, and 0px in sharp mode. The native `option(theme)` API exposes this as `theme.markRadius` separately from surface and tooltip radii.
23
78
 
24
- ## Accessibility
79
+ | Mark | Corner treatment |
80
+ | --- | --- |
81
+ | Bars, including composed charts | Small radius at the exposed value end; square at the baseline. |
82
+ | Stacked bars | Square between segments; Small radius only at the outer end of the stack. |
83
+ | Standard pie and donut slices | Square slice corners (0px). |
84
+ | Separated and petal donut slices | Small radius; avoid pill-shaped corners. |
25
85
 
26
- Pair charts with text summaries or tables for critical data. Tooltips are supplemental.
86
+ ## RTL
27
87
 
28
- ## Install
88
+ `DataChart` inherits direction or accepts `dir="rtl"`. Cartesian category axes and value-axis placement mirror. Block controls, summaries, and tables follow logical layout. Data order is preserved. Text uses the active Banhaten Arabic and English font tokens.
29
89
 
30
- ```bash
31
- npx banhaten add chart
32
- ```
90
+ ## Accessibility
91
+
92
+ Supply a meaningful `label` and a concise `description` of the finding. Charts are labelled figures; hover tooltips supplement that description. Blocks provide keyboard-operable time ranges and expandable data tables. For application data, provide a corresponding table or text summary when precise values matter. Do not rely on color alone: use labels, patterns, dashed strokes, or point markers. Reduced motion disables animations and moving dashes.
33
93
 
34
94
  ## Examples
35
95
 
36
- Primary registry example: `examples/chart-demo.tsx`.
96
+ Primary registry example: `examples/chart-demo.tsx`. Ready-made block example: `examples/chart-block-demo.tsx`.
37
97
 
38
- Open the live component page or run `npx banhaten docs chart` to inspect source files, install command, and examples.
98
+ The live Chart page contains all eight families, all 18 blocks, and an Arabic RTL example. Examples include gradients, texture fills, grouped/stacked/percentage series, dots, brush controls, circular and polygon radar grids, pie labels, radial progress, and Sankey links.
39
99
 
40
100
  ## RTL Rules
41
101
 
42
102
  - `inheritsDirection`: true
43
- - `layout`: Chart labels and legends should use logical layout; chart geometry is controlled by Recharts.
103
+ - `layout`: Cartesian axes mirror; block layouts use logical properties. Legacy Recharts geometry remains caller-controlled.
104
+
105
+ ## Source attribution
106
+
107
+ Chart designs and illustrative block data are adapted from the MIT-licensed [EvilCharts repository](https://github.com/legions-developer/evilcharts). Its notice is included in the installed chart files. The shared renderer uses Apache ECharts under Apache-2.0. Banhaten's token layer, family builders, and lifecycle replace duplicated source chart implementations.
44
108
 
45
109
  <!-- registry-contract:start -->
46
110
  ## Registry Contract
@@ -50,9 +114,16 @@ This table is generated from `registry/index.json`; edit the component-owned sou
50
114
 
51
115
  | Field | Values |
52
116
  | --- | --- |
53
- | `supportedExports` | `components/chart.tsx: ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent` |
54
- | `parts` | `ChartContainer`, `ChartTooltip`, `ChartTooltipContent`, `ChartLegend`, `ChartLegendContent` |
55
- | `registryDependencies` | None |
56
- | `features` | `responsive container`, `tooltip content`, `legend content`, `config colors` |
57
- | `tokens` | `--bh-bg-neutral-subtle`, `--bh-bg-raised`, `--bh-border-subtle`, `--bh-chart-categorical-aqua`, `--bh-chart-categorical-brand`, `--bh-chart-categorical-danger`, `--bh-chart-categorical-fuchsia`, `--bh-chart-categorical-information`, `--bh-chart-categorical-purple`, `--bh-chart-categorical-success`, `--bh-chart-categorical-warning`, `--bh-content-default`, `--bh-content-muted`, `--bh-content-subtle`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-space-17xl-288`, `--bh-space-19xl-384`, `--bh-space-3xl-16`, `--bh-space-md-8`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-text-body-xs-medium-font-size`, `--bh-text-body-xs-medium-font-weight`, `--bh-text-body-xs-regular-font-size`, `--bh-text-body-xs-regular-font-weight`, `--bh-text-body-xs-regular-letter-spacing`, `--bh-text-body-xs-regular-line-height`, `--shadow-popover` |
117
+ | `supportedExports` | `components/chart.tsx: ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent`, `components/echart.tsx: EChart`, `components/chart-options.ts: createChartOption`, `components/data-chart.tsx: DataChart`, `components/chart-block.tsx: ChartBlock, chartBlocks` |
118
+ | `parts` | `DataChart`, `ChartBlock`, `EChart`, `ChartContainer`, `ChartTooltip`, `ChartTooltipContent`, `ChartLegend`, `ChartLegendContent` |
119
+ | `registryDependencies` | `button`, `progress` |
120
+ | `props.spec` | `ChartSpec; default null; DataChart: family, data, series, variant, stacking, labels, brush, and interaction options. Series colors are Banhaten palette indices.` |
121
+ | `props.label` | `string; default null; DataChart / EChart: required accessible chart name.` |
122
+ | `props.description` | `string; default null; DataChart / EChart: optional accessible text summary.` |
123
+ | `props.block` | `ChartBlockId; default null; ChartBlock: one of 18 documented blocks, with illustrative data and period controls where applicable.` |
124
+ | `props.option` | `(theme: EChartTheme) => EChartsOption; default null; EChart: advanced option factory receives resolved inherited Banhaten tokens.` |
125
+ | `props.onReady` | `(chart, theme) => void \| (() => void); default null; EChart: attach custom interactions and return their cleanup.` |
126
+ | `props.config` | `ChartConfig; default null; Legacy ChartContainer: maps series names to CSS color variables.` |
127
+ | `features` | `eight chart families`, `18 ready-made blocks`, `live theme and font tokens`, `responsive SVG`, `tooltips and legends`, `range brush`, `pattern fills`, `reduced motion`, `RTL`, `Recharts compatibility` |
128
+ | `tokens` | `--bh-bg-neutral-subtle`, `--bh-bg-raised`, `--bh-border-default`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-chart-categorical-aqua`, `--bh-chart-categorical-brand`, `--bh-chart-categorical-danger`, `--bh-chart-categorical-fuchsia`, `--bh-chart-categorical-information`, `--bh-chart-categorical-purple`, `--bh-chart-categorical-success`, `--bh-chart-categorical-warning`, `--bh-chart-neutral-default`, `--bh-content-brand-default`, `--bh-content-default`, `--bh-content-muted`, `--bh-content-subtle`, `--bh-duration-default`, `--bh-focus-ring-width`, `--bh-font-family`, `--bh-font-family-mono`, `--bh-icon-stroke-200`, `--bh-interactive-ghost-hover`, `--bh-opacity-15`, `--bh-opacity-30`, `--bh-opacity-90`, `--bh-opacity-muted`, `--bh-radius-3xl-16`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-radius-md-6`, `--bh-radius-sm-4`, `--bh-radius-xl-10`, `--bh-radius-xs-2`, `--bh-space-11xl-96`, `--bh-space-12xl-128`, `--bh-space-15xl-224`, `--bh-space-17xl-288`, `--bh-space-19xl-384`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-7xl-40`, `--bh-space-9xl-64`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-space-xxs-2`, `--bh-space-xxxs-1`, `--bh-text-body-2xs-regular-font-size`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-3xs-regular-font-size`, `--bh-text-body-3xs-regular-line-height`, `--bh-text-body-sm-medium-font-size`, `--bh-text-body-sm-medium-line-height`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-line-height`, `--bh-text-body-xs-medium-font-size`, `--bh-text-body-xs-medium-font-weight`, `--bh-text-body-xs-regular-font-size`, `--bh-text-body-xs-regular-font-weight`, `--bh-text-body-xs-regular-letter-spacing`, `--bh-text-body-xs-regular-line-height`, `--bh-text-heading-lg-semibold-font-size`, `--bh-text-heading-lg-semibold-font-weight`, `--bh-text-heading-lg-semibold-letter-spacing`, `--bh-text-heading-lg-semibold-line-height`, `--bh-text-heading-md-semibold-font-size`, `--bh-text-heading-md-semibold-font-weight`, `--bh-text-heading-md-semibold-letter-spacing`, `--bh-text-heading-md-semibold-line-height`, `--bh-text-heading-xs-semibold-font-size`, `--bh-text-heading-xs-semibold-font-weight`, `--bh-text-heading-xs-semibold-letter-spacing`, `--bh-text-heading-xs-semibold-line-height`, `--elevation-popover` |
58
129
  <!-- registry-contract:end -->
@@ -64,5 +64,5 @@ This table is generated from `registry/index.json`; edit the component-owned sou
64
64
  | `registryDependencies` | None |
65
65
  | `cardPositions` | `start`, `end` |
66
66
  | `states` | `unchecked`, `checked`, `indeterminate`, `selected`, `disabled` |
67
- | `tokens` | `--bh-bg-default`, `--bh-bg-disabled`, `--bh-bg-raised`, `--bh-bg-raised-hover`, `--bh-border-brand-strong`, `--bh-border-disabled`, `--bh-border-input`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-checkbox-card-border`, `--bh-checkbox-card-border-width`, `--bh-checkbox-card-border-width-active`, `--bh-checkbox-card-border-width-current`, `--bh-checkbox-card-content-gap`, `--bh-checkbox-card-gap`, `--bh-checkbox-card-label-gap`, `--bh-checkbox-card-media-icon-size`, `--bh-checkbox-card-padding`, `--bh-checkbox-card-radius`, `--bh-checkbox-card-text-gap`, `--bh-checkbox-card-width`, `--bh-checkbox-control-border`, `--bh-checkbox-control-radius`, `--bh-checkbox-control-size`, `--bh-checkbox-icon-size`, `--bh-checkbox-root-size`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-on-color`, `--bh-content-subtle`, `--bh-icon-stroke-300`, `--bh-interactive-checkbox-default`, `--bh-interactive-checkbox-hover`, `--bh-opacity-60`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-letter-spacing`, `--bh-text-body-md-medium-line-height`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-letter-spacing`, `--bh-text-body-sm-regular-line-height`, `--shadow-checkbox-card`, `--shadow-checkbox-card-focus`, `--shadow-checkbox-control`, `--shadow-checkbox-focus`, `--shadow-component-default` |
67
+ | `tokens` | `--bh-bg-default`, `--bh-bg-disabled`, `--bh-bg-raised`, `--bh-bg-raised-hover`, `--bh-border-brand-strong`, `--bh-border-disabled`, `--bh-border-input`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-checkbox-card-border`, `--bh-checkbox-card-border-width`, `--bh-checkbox-card-border-width-active`, `--bh-checkbox-card-border-width-current`, `--bh-checkbox-card-content-gap`, `--bh-checkbox-card-gap`, `--bh-checkbox-card-label-gap`, `--bh-checkbox-card-media-icon-size`, `--bh-checkbox-card-padding`, `--bh-checkbox-card-radius`, `--bh-checkbox-card-text-gap`, `--bh-checkbox-card-width`, `--bh-checkbox-control-border`, `--bh-checkbox-control-radius`, `--bh-checkbox-control-size`, `--bh-checkbox-icon-size`, `--bh-checkbox-root-size`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-subtle`, `--bh-icon-stroke-300`, `--bh-interactive-checkbox-content`, `--bh-interactive-checkbox-default`, `--bh-interactive-checkbox-hover`, `--bh-opacity-60`, `--bh-text-body-md-medium-font-size`, `--bh-text-body-md-medium-font-weight`, `--bh-text-body-md-medium-letter-spacing`, `--bh-text-body-md-medium-line-height`, `--bh-text-body-sm-regular-font-size`, `--bh-text-body-sm-regular-font-weight`, `--bh-text-body-sm-regular-letter-spacing`, `--bh-text-body-sm-regular-line-height`, `--elevation-checkbox-card`, `--elevation-checkbox-card-focus`, `--elevation-checkbox-control`, `--elevation-checkbox-focus`, `--elevation-component-default` |
68
68
  <!-- registry-contract:end -->