banhaten 0.3.0 → 0.4.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 (65) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +4 -4
  3. package/docs/design-system/component-implementation-ownership-audit.md +2 -1
  4. package/docs/design-system/component-quality-audit-2026-08-20.md +4 -2
  5. package/docs/design-system/components/alert-dialog.md +1 -1
  6. package/docs/design-system/components/alert.md +2 -2
  7. package/docs/design-system/components/attribute.md +1 -1
  8. package/docs/design-system/components/avatar.md +2 -2
  9. package/docs/design-system/components/banner.md +2 -2
  10. package/docs/design-system/components/breadcrumbs.md +2 -2
  11. package/docs/design-system/components/button-group.md +2 -2
  12. package/docs/design-system/components/button.md +2 -2
  13. package/docs/design-system/components/date-picker.md +3 -1
  14. package/docs/design-system/components/file-upload.md +1 -1
  15. package/docs/design-system/components/input.md +8 -6
  16. package/docs/design-system/components/item.md +3 -1
  17. package/docs/design-system/components/kbd.md +11 -8
  18. package/docs/design-system/components/menu.md +2 -2
  19. package/docs/design-system/components/modal.md +1 -1
  20. package/docs/design-system/components/onboarding-step-list-item.md +2 -2
  21. package/docs/design-system/components/pagination.md +2 -2
  22. package/docs/design-system/components/radio-card.md +1 -1
  23. package/docs/design-system/components/select.md +1 -1
  24. package/docs/design-system/components/sidebar.md +59 -19
  25. package/docs/design-system/components/table.md +1 -1
  26. package/docs/design-system/components/tag.md +3 -3
  27. package/docs/design-system/components/toolbar.md +1 -1
  28. package/docs/design-system/density-and-elevation.md +16 -12
  29. package/docs/design-system/icons.md +6 -1
  30. package/docs/design-system/installation.md +1 -1
  31. package/docs/design-system/token-governance.md +7 -8
  32. package/package.json +1 -1
  33. package/registry/components/alert-dialog.tsx +1 -1
  34. package/registry/components/alert.tsx +3 -3
  35. package/registry/components/attribute.tsx +8 -2
  36. package/registry/components/avatar.tsx +21 -17
  37. package/registry/components/button-group.tsx +1 -1
  38. package/registry/components/button.tsx +1 -1
  39. package/registry/components/date-picker.tsx +16 -4
  40. package/registry/components/expanded/OnboardingStepListItem.tsx +6 -5
  41. package/registry/components/expanded/banner.css +10 -5
  42. package/registry/components/expanded/breadcrumbs.css +4 -4
  43. package/registry/components/expanded/fileUpload.css +3 -3
  44. package/registry/components/expanded/onboardingStepListItem.css +14 -5
  45. package/registry/components/expanded/table.css +5 -2
  46. package/registry/components/input.tsx +12 -5
  47. package/registry/components/item.tsx +1 -1
  48. package/registry/components/kbd.tsx +109 -15
  49. package/registry/components/menu.tsx +1 -1
  50. package/registry/components/modal.tsx +1 -1
  51. package/registry/components/pagination.tsx +12 -7
  52. package/registry/components/radio-card.tsx +1 -2
  53. package/registry/components/select.tsx +1 -1
  54. package/registry/components/sidebar.tsx +929 -57
  55. package/registry/components/tag.tsx +1 -1
  56. package/registry/components/toolbar.tsx +1 -1
  57. package/registry/examples/attribute-demo.tsx +1 -1
  58. package/registry/examples/command-palette-demo.tsx +0 -1
  59. package/registry/examples/kbd-demo.tsx +2 -1
  60. package/registry/examples/sidebar-demo.tsx +60 -16
  61. package/registry/guidance/USAGE.md +1 -1
  62. package/registry/index.json +233 -31
  63. package/registry/styles/globals.css +152 -118
  64. package/registry.json +4 -3
  65. package/tokens/banhaten.tokens.json +169 -49
@@ -1,8 +1,8 @@
1
1
  # Sidebar
2
2
 
3
- Sidebar is a composable app navigation surface with collapse state, grouped menu items, logical start/end placement, and generated `--bh-sidebar-*` tokens.
3
+ Sidebar is the complete Figma-aligned application navigation system. It provides a 280px expanded shell and 48px icon rail, brand and workspace headers, grouped and nested menus, tree navigation, badges and actions, user footers, tooltips, controlled state, and logical RTL placement.
4
4
 
5
- `Sidebar` can also render as a standalone static navigation surface. Use `SidebarProvider` whenever collapse controls or provider-aware menu parts are present.
5
+ All sidebar parts render inside `SidebarProvider`. The provider owns controlled or uncontrolled collapse state and supplies the tooltip context used by icon-only menu rows.
6
6
 
7
7
  ## API
8
8
 
@@ -10,19 +10,33 @@ Sidebar is a composable app navigation surface with collapse state, grouped menu
10
10
  <SidebarProvider>
11
11
  <Sidebar>
12
12
  <SidebarHeader>
13
- Workspace
14
- <SidebarToggle />
13
+ <SidebarHeaderWorkspaceSwitch label="Acme Inc" />
15
14
  </SidebarHeader>
16
15
  <SidebarBody>
17
16
  <SidebarGroup>
18
17
  <SidebarGroupLabel>Main</SidebarGroupLabel>
19
18
  <SidebarMenu>
20
19
  <SidebarMenuItem>
21
- <SidebarMenuButton active>Overview</SidebarMenuButton>
20
+ <SidebarMenuButton active icon={<LayoutGridIcon />}>
21
+ Overview
22
+ </SidebarMenuButton>
23
+ </SidebarMenuItem>
24
+ <SidebarMenuItem>
25
+ <SidebarMenuButton icon={<CreditCardIcon />}>
26
+ Transactions
27
+ </SidebarMenuButton>
28
+ <SidebarMenuSub>
29
+ <SidebarMenuSubButton active showLine>
30
+ Payments
31
+ </SidebarMenuSubButton>
32
+ </SidebarMenuSub>
22
33
  </SidebarMenuItem>
23
34
  </SidebarMenu>
24
35
  </SidebarGroup>
25
36
  </SidebarBody>
37
+ <SidebarFooter>
38
+ <SidebarFooterUser name="Olivia Rhye" supportText="olivia@acme.com" />
39
+ </SidebarFooter>
26
40
  </Sidebar>
27
41
  </SidebarProvider>
28
42
  ```
@@ -30,26 +44,37 @@ Sidebar is a composable app navigation surface with collapse state, grouped menu
30
44
  | Part | Purpose |
31
45
  | --- | --- |
32
46
  | `SidebarProvider` | controlled or uncontrolled collapsed state |
33
- | `Sidebar` | navigation surface |
47
+ | `Sidebar` | 280px navigation surface and 48px collapsed rail |
34
48
  | `SidebarHeader`, `SidebarBody`, `SidebarFooter` | layout regions |
35
- | `SidebarGroup`, `SidebarGroupLabel` | grouped navigation |
36
- | `SidebarMenu`, `SidebarMenuItem`, `SidebarMenuButton` | menu structure |
49
+ | `SidebarHeaderBrand`, `SidebarHeaderWorkspaceSwitch` | brand and workspace header compositions |
50
+ | `SidebarHeaderAction` | compact search or custom header action |
51
+ | `SidebarFooterUser` | supporting-text or bordered user switcher |
52
+ | `SidebarGroup`, `SidebarGroupLabel`, `SidebarGroupAction` | grouped navigation, disclosure, and actions |
53
+ | `SidebarMenu`, `SidebarMenuItem`, `SidebarMenuButton` | default and avatar menu structure with optional slots |
54
+ | `SidebarMenuBadge` | neutral count badge for a menu row |
55
+ | `SidebarMenuSub`, `SidebarMenuSubButton` | plain or line-guided Level 2 navigation |
56
+ | `SidebarTreeGroup`, `SidebarMenuTreeItem` | four-level file or project tree navigation |
37
57
  | `SidebarToggle`, `SidebarRail` | collapse controls |
38
58
  | `SidebarTrigger` | shadcn-compatible alias for `SidebarToggle` |
39
59
  | `SidebarInset` | main content wrapper for app-shell layouts |
40
60
 
61
+ `SidebarMenuButton` accepts `variant="default" | "avatar" | "level2" | "level2-line"`. Its optional `icon`, `supportText`, `badge`, and `trailingIcon` props keep every row aligned to the Figma slots. Prefer `SidebarMenuSubButton` for nested rows because its `showLine` prop selects the nested treatment directly.
62
+
63
+ Use `SidebarHeaderBrand` for product navigation. Use `SidebarHeaderWorkspaceSwitch` with `variant="actions"` for the compact search/collapse header or `variant="card"` for the bordered workspace card.
64
+
41
65
  ## Token Contract
42
66
 
43
- Sidebar uses generated `--bh-sidebar-width`, `--bh-sidebar-width-collapsed`, `--bh-sidebar-item-height`, `--bh-sidebar-padding`, `--bh-sidebar-bg`, `--bh-sidebar-border`, and `--shadow-sidebar` tokens.
67
+ Sidebar uses generated `--bh-sidebar-*` tokens for expanded and collapsed widths, row heights, header actions, compact controls, workspace cards, tree indentation, padding, backgrounds, borders, and menu states. Expanded and collapsed navigation share the same 18px glyph in a centered 20px slot. Do not replace these dimensions with local literals in product compositions.
44
68
 
45
69
  ## RTL
46
70
 
47
- Use `dir="rtl"` on `SidebarProvider` and `side="start"` or `side="end"` for logical placement.
71
+ Use `dir="rtl"` on `SidebarProvider` and `side="start"` or `side="end"` for logical placement. Inline borders, padding, submenu guides, tree indentation, collapse icons, and disclosure chevrons mirror automatically.
48
72
 
49
73
  ## Accessibility
50
74
 
51
- Use clear labels for collapsed icon-only menu buttons. `SidebarToggle` announces the pressed collapse state.
52
- Sidebar composition parts must be rendered inside `SidebarProvider`.
75
+ Provide a text child or explicit `aria-label` for every menu button. Collapsed rows reuse that label in a tooltip. Active menu and tree rows expose `aria-current="page"`; expandable tree rows expose `aria-expanded`; `SidebarToggle` announces both its action and pressed collapse state.
76
+
77
+ Level 2 rows, group labels, and tree groups unmount while collapsed so keyboard users do not encounter invisible controls. Use native links through `asChild` when a row navigates, and keep button rendering for in-place actions.
53
78
 
54
79
  ## Install
55
80
 
@@ -66,8 +91,9 @@ Open the live component page or run `npx banhaten docs sidebar` to inspect sourc
66
91
  ## RTL Rules
67
92
 
68
93
  - `inheritsDirection`: true
69
- - `placement`: Use side="start" or side="end" with dir on SidebarProvider for logical placement.
70
- - `spacing`: Menu item icon and label spacing uses flex gaps and logical text alignment.
94
+ - `placement`: Use side="start" or side="end" with dir on SidebarProvider for logical placement and border selection.
95
+ - `spacing`: Headers, menu rows, nested guides, tree indentation, group actions, and footer content use logical inline spacing.
96
+ - `icons`: Directional collapse, disclosure, and tree chevrons mirror in RTL; semantic icons retain their orientation.
71
97
 
72
98
  <!-- registry-contract:start -->
73
99
  ## Registry Contract
@@ -77,9 +103,23 @@ This table is generated from `registry/index.json`; edit the component-owned sou
77
103
 
78
104
  | Field | Values |
79
105
  | --- | --- |
80
- | `supportedExports` | `components/sidebar.tsx: Sidebar, SidebarBody, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarRail, SidebarToggle, SidebarTrigger` |
81
- | `parts` | `SidebarProvider`, `Sidebar`, `SidebarHeader`, `SidebarBody`, `SidebarFooter`, `SidebarGroup`, `SidebarGroupLabel`, `SidebarMenu`, `SidebarMenuItem`, `SidebarMenuButton`, `SidebarToggle`, `SidebarTrigger`, `SidebarInset`, `SidebarRail` |
82
- | `registryDependencies` | None |
83
- | `features` | `standalone static surface`, `controlled collapse`, `uncontrolled collapse`, `group labels`, `icon-only collapsed state`, `logical side placement` |
84
- | `tokens` | `--bh-content-default`, `--bh-content-muted`, `--bh-content-subtle`, `--bh-icon-stroke-200`, `--bh-interactive-ghost-hover`, `--bh-radius-lg-8`, `--bh-sidebar-bg`, `--bh-sidebar-bg-collapsed`, `--bh-sidebar-border`, `--bh-sidebar-icon-size`, `--bh-sidebar-item-bg-active`, `--bh-sidebar-item-bg-hover`, `--bh-sidebar-item-content`, `--bh-sidebar-item-content-active`, `--bh-sidebar-item-gap`, `--bh-sidebar-item-height`, `--bh-sidebar-item-padding-x`, `--bh-sidebar-padding`, `--bh-sidebar-rail-width`, `--bh-sidebar-width`, `--bh-sidebar-width-collapsed`, `--bh-space-19xl-384`, `--bh-space-5xl-24`, `--bh-space-md-8`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--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`, `--bh-text-body-xs-medium-font-size`, `--bh-text-body-xs-medium-font-weight`, `--bh-text-body-xs-medium-letter-spacing`, `--bh-text-body-xs-medium-line-height`, `--shadow-button-focus`, `--shadow-sidebar` |
106
+ | `supportedExports` | `components/sidebar.tsx: Sidebar, SidebarBody, SidebarFooter, SidebarFooterUser, SidebarGroup, SidebarGroupAction, SidebarGroupLabel, SidebarHeader, SidebarHeaderAction, SidebarHeaderBrand, SidebarHeaderWorkspaceSwitch, SidebarInset, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuTreeItem, SidebarProvider, SidebarRail, SidebarToggle, SidebarTreeGroup, SidebarTrigger` |
107
+ | `parts` | `SidebarProvider`, `Sidebar`, `SidebarHeader`, `SidebarHeaderAction`, `SidebarHeaderBrand`, `SidebarHeaderWorkspaceSwitch`, `SidebarBody`, `SidebarFooter`, `SidebarFooterUser`, `SidebarGroup`, `SidebarGroupAction`, `SidebarGroupLabel`, `SidebarMenu`, `SidebarMenuItem`, `SidebarMenuBadge`, `SidebarMenuButton`, `SidebarMenuSub`, `SidebarMenuSubButton`, `SidebarMenuTreeItem`, `SidebarTreeGroup`, `SidebarToggle`, `SidebarTrigger`, `SidebarInset`, `SidebarRail` |
108
+ | `registryDependencies` | `tooltip` |
109
+ | `props.collapsed` | `boolean; default false; Controls the provider-owned 280px expanded or 48px rail state. Pair with onCollapsedChange for controlled usage.` |
110
+ | `props.defaultCollapsed` | `boolean; default false; Sets the initial rail state when SidebarProvider owns collapse state internally.` |
111
+ | `props.side` | `"start" \| "end"; default "start"; Places Sidebar on the logical start or end edge and selects the matching logical border.` |
112
+ | `props.variant` | `SidebarMenuButtonVariant \| SidebarWorkspaceVariant \| "default" \| "supporting-text"; default "part-specific"; Selects default/avatar/nested menu rows, action/card workspace headers, or default/supporting-text footers on the part that owns it.` |
113
+ | `props.active` | `boolean; default false; Applies the selected menu or tree treatment and exposes aria-current="page".` |
114
+ | `props.state` | `"default" \| "hover" \| "active" \| "focus"; default "default"; Deterministic interaction state for documentation matrices; product hover and focus remain native CSS interactions.` |
115
+ | `props.showLine` | `boolean; default false; Selects the neutral/brand line-guided Level 2 treatment on SidebarMenuSubButton.` |
116
+ | `props.level` | `1 \| 2 \| 3 \| 4; default 1; Controls tokenized logical indentation for SidebarMenuTreeItem.` |
117
+ | `props.expandable` | `boolean; default false; Adds a disclosure affordance to group labels or tree-group headings.` |
118
+ | `props.asChild` | `boolean; default false; Renders menu, tree, or inset content through Radix Slot while preserving sidebar layout and semantics.` |
119
+ | `directions` | `ltr`, `rtl` |
120
+ | `features` | `280px expanded and 48px collapsed shells`, `controlled collapse`, `uncontrolled collapse`, `brand header with search and collapse actions`, `workspace action and card headers`, `default, avatar, Level 2, and Level 2 line menu rows`, `supporting text, count badge, and trailing action slots`, `group labels with disclosure and icon actions`, `four-level tree navigation`, `supporting-text and bordered user footers`, `icon-only rail with tooltips`, `logical side placement and RTL mirroring` |
121
+ | `sides` | `start`, `end` |
122
+ | `states` | `default`, `hover`, `active`, `focus`, `expanded`, `collapsed` |
123
+ | `tokens` | `--bh-bg-brand-default`, `--bh-bg-default`, `--bh-bg-default-hover`, `--bh-bg-neutral-subtle`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-border-width-default`, `--bh-border-width-strong`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-muted`, `--bh-content-on-brand`, `--bh-content-subtle`, `--bh-control-sm`, `--bh-icon-stroke-200`, `--bh-interactive-ghost-active`, `--bh-interactive-ghost-default`, `--bh-interactive-ghost-hover`, `--bh-opacity-0`, `--bh-opacity-100`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-radius-md-6`, `--bh-sidebar-active-guide-height`, `--bh-sidebar-bg`, `--bh-sidebar-bg-collapsed`, `--bh-sidebar-border`, `--bh-sidebar-brand-height`, `--bh-sidebar-compact-control-size`, `--bh-sidebar-header-action-size`, `--bh-sidebar-icon-size`, `--bh-sidebar-icon-size-collapsed`, `--bh-sidebar-icon-slot-size`, `--bh-sidebar-item-bg-active`, `--bh-sidebar-item-bg-hover`, `--bh-sidebar-item-content`, `--bh-sidebar-item-content-active`, `--bh-sidebar-item-height`, `--bh-sidebar-item-height-collapsed`, `--bh-sidebar-item-padding-x`, `--bh-sidebar-level-2-indent-width`, `--bh-sidebar-padding`, `--bh-sidebar-rail-width`, `--bh-sidebar-tree-indent-step`, `--bh-sidebar-tree-item-height`, `--bh-sidebar-width`, `--bh-sidebar-width-collapsed`, `--bh-sidebar-workspace-card-height`, `--bh-sidebar-workspace-trigger-max-width`, `--bh-sidebar-workspace-trigger-width`, `--bh-space-19xl-384`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-8xl-48`, `--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-font-weight`, `--bh-text-body-2xs-regular-letter-spacing`, `--bh-text-body-2xs-regular-line-height`, `--bh-text-body-3xs-semibold-font-size`, `--bh-text-body-3xs-semibold-font-weight`, `--bh-text-body-3xs-semibold-letter-spacing`, `--bh-text-body-3xs-semibold-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`, `--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-body-xl-semibold-font-size`, `--bh-text-body-xl-semibold-font-weight`, `--bh-text-body-xl-semibold-letter-spacing`, `--bh-text-body-xl-semibold-line-height`, `--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-body-xs-semibold-font-weight`, `--shadow-button-focus` |
124
+ | `variants` | `default menu`, `avatar menu`, `Level 2 menu`, `Level 2 line menu`, `workspace actions`, `workspace card`, `default footer`, `supporting-text footer` |
85
125
  <!-- registry-contract:end -->
@@ -425,5 +425,5 @@ This table is generated from `registry/index.json`; edit the component-owned sou
425
425
  | `modes` | `table`, `data-table` |
426
426
  | `sizes` | `sm`, `lg` |
427
427
  | `states` | `default`, `selected`, `disabled`, `loading`, `empty`, `error` |
428
- | `tokens` | `--bh-avatar-fallback-font-size-md`, `--bh-avatar-fallback-font-size-xs`, `--bh-avatar-size-md`, `--bh-avatar-size-xs`, `--bh-bg-accent-amber-subtle`, `--bh-bg-accent-fuchsia-subtle`, `--bh-bg-accent-golden-default`, `--bh-bg-brand-default`, `--bh-bg-brand-soft`, `--bh-bg-danger-subtle`, `--bh-bg-default`, `--bh-bg-muted`, `--bh-bg-neutral-medium`, `--bh-bg-neutral-soft`, `--bh-bg-neutral-subtle`, `--bh-bg-raised`, `--bh-bg-success-default`, `--bh-bg-success-subtle`, `--bh-bg-warning-default`, `--bh-bg-warning-subtle`, `--bh-border-brand-strong`, `--bh-border-default`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-content-accent-amber-strong`, `--bh-content-accent-fuchsia-strong`, `--bh-content-brand-default`, `--bh-content-danger-strong`, `--bh-content-default`, `--bh-content-muted`, `--bh-content-on-brand`, `--bh-content-subtle`, `--bh-content-success-default`, `--bh-content-warning-strong`, `--bh-control-default`, `--bh-duration-fast`, `--bh-easing-standard`, `--bh-focus-ring-width`, `--bh-font-family`, `--bh-font-weight-semibold`, `--bh-icon-stroke-190`, `--bh-icon-stroke-225`, `--bh-input-width`, `--bh-interactive-ghost-hover`, `--bh-opacity-0`, `--bh-opacity-100`, `--bh-opacity-muted`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-radius-md-6`, `--bh-radius-sm-4`, `--bh-space-12xl-128`, `--bh-space-16xl-256`, `--bh-space-19xl-384`, `--bh-space-2xl-14`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-7xl-40`, `--bh-space-8xl-48`, `--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-table-min-width`, `--bh-table-selection-column-min-width`, `--bh-text-base-letter-spacing`, `--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-lg-semibold-font-size`, `--bh-text-body-lg-semibold-font-weight`, `--bh-text-body-lg-semibold-line-height`, `--bh-text-body-md-semibold-font-size`, `--bh-text-body-md-semibold-font-weight`, `--bh-text-body-md-semibold-line-height`, `--bh-text-body-sm-medium-font-weight`, `--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-regular-font-size`, `--shadow-component-focus-ring` |
428
+ | `tokens` | `--bh-avatar-fallback-font-size-md`, `--bh-avatar-fallback-font-size-xs`, `--bh-avatar-size-md`, `--bh-avatar-size-xs`, `--bh-bg-accent-amber-subtle`, `--bh-bg-accent-fuchsia-subtle`, `--bh-bg-accent-golden-default`, `--bh-bg-brand-default`, `--bh-bg-brand-soft`, `--bh-bg-danger-subtle`, `--bh-bg-default`, `--bh-bg-muted`, `--bh-bg-neutral-medium`, `--bh-bg-neutral-soft`, `--bh-bg-neutral-subtle`, `--bh-bg-raised`, `--bh-bg-success-default`, `--bh-bg-success-subtle`, `--bh-bg-warning-default`, `--bh-bg-warning-subtle`, `--bh-border-brand-strong`, `--bh-border-default`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-content-accent-amber-strong`, `--bh-content-accent-fuchsia-strong`, `--bh-content-brand-default`, `--bh-content-danger-strong`, `--bh-content-default`, `--bh-content-muted`, `--bh-content-on-brand`, `--bh-content-subtle`, `--bh-content-success-default`, `--bh-content-warning-strong`, `--bh-control-default`, `--bh-duration-fast`, `--bh-easing-standard`, `--bh-focus-ring-width`, `--bh-font-family`, `--bh-font-weight-semibold`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-190`, `--bh-icon-stroke-225`, `--bh-input-width`, `--bh-interactive-ghost-hover`, `--bh-opacity-0`, `--bh-opacity-100`, `--bh-opacity-muted`, `--bh-radius-full`, `--bh-radius-lg-8`, `--bh-radius-md-6`, `--bh-radius-sm-4`, `--bh-space-12xl-128`, `--bh-space-16xl-256`, `--bh-space-19xl-384`, `--bh-space-2xl-14`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-6xl-32`, `--bh-space-7xl-40`, `--bh-space-8xl-48`, `--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-table-min-width`, `--bh-table-selection-column-min-width`, `--bh-text-base-letter-spacing`, `--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-lg-semibold-font-size`, `--bh-text-body-lg-semibold-font-weight`, `--bh-text-body-lg-semibold-line-height`, `--bh-text-body-md-semibold-font-size`, `--bh-text-body-md-semibold-font-weight`, `--bh-text-body-md-semibold-line-height`, `--bh-text-body-sm-medium-font-weight`, `--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-regular-font-size`, `--shadow-component-focus-ring` |
429
429
  <!-- registry-contract:end -->
@@ -47,9 +47,9 @@ The component consumes semantic design-system tokens only:
47
47
  - `md` tags are `28px` tall and use body-sm medium text.
48
48
  - `lg` tags are `36px` tall and use body-md medium text.
49
49
  - Simple tags use centered text and no leading slot.
50
- - Dot, flag, avatar, and icon tags use a `20px` leading slot.
50
+ - Dot, flag, avatar, and icon tags use a `20px` leading slot; the default icon glyph is `18px`.
51
51
  - Close buttons are `16px` with a `12px` icon.
52
- - Focused tags use a brand border plus an outside focus ring at reduced opacity.
52
+ - Focused tags use a brand border plus a solid outside focus ring.
53
53
  - Active tags use selected/brand treatment and a stronger brand border.
54
54
  - Disabled tags use disabled background, border, and content tokens.
55
55
  - RTL uses logical inline padding and inherited direction so leading media, label, and close button mirror naturally.
@@ -90,6 +90,6 @@ This table is generated from `registry/index.json`; edit the component-owned sou
90
90
  | `registryDependencies` | None |
91
91
  | `sizes` | `xs`, `md`, `lg` |
92
92
  | `states` | `default`, `active`, `disabled` |
93
- | `tokens` | `--bh-bg-accent-green-default`, `--bh-bg-always-dark`, `--bh-bg-always-white`, `--bh-bg-brand-subtle`, `--bh-bg-danger-default`, `--bh-bg-neutral-bold`, `--bh-bg-neutral-subtle`, `--bh-border-brand-strong`, `--bh-border-default`, `--bh-border-disabled`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-content-brand-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-muted`, `--bh-content-on-neutral`, `--bh-control-default`, `--bh-control-md`, `--bh-icon-stroke-200`, `--bh-icon-stroke-225`, `--bh-interactive-ghost-hover`, `--bh-interactive-secondary-default`, `--bh-interactive-secondary-disabled`, `--bh-interactive-secondary-hover`, `--bh-opacity-30`, `--bh-radius-full`, `--bh-radius-xs-2`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-none`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-tag-active-border-width`, `--bh-tag-focus-ring-inset`, `--bh-tag-focus-ring-width`, `--bh-text-body-3xs-semibold-font-size`, `--bh-text-body-3xs-semibold-font-weight`, `--bh-text-body-3xs-semibold-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-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`, `--bh-text-body-xs-medium-font-size`, `--bh-text-body-xs-medium-font-weight`, `--bh-text-body-xs-medium-letter-spacing`, `--bh-text-body-xs-medium-line-height`, `--bh-z-raised`, `--shadow-button-focus` |
93
+ | `tokens` | `--bh-bg-accent-green-default`, `--bh-bg-always-dark`, `--bh-bg-always-white`, `--bh-bg-brand-subtle`, `--bh-bg-danger-default`, `--bh-bg-neutral-bold`, `--bh-bg-neutral-subtle`, `--bh-border-brand-strong`, `--bh-border-default`, `--bh-border-disabled`, `--bh-border-focus`, `--bh-border-subtle`, `--bh-content-brand-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-muted`, `--bh-content-on-neutral`, `--bh-control-default`, `--bh-control-md`, `--bh-icon-size-default`, `--bh-icon-slot-size-default`, `--bh-icon-stroke-200`, `--bh-icon-stroke-225`, `--bh-interactive-ghost-hover`, `--bh-interactive-secondary-default`, `--bh-interactive-secondary-disabled`, `--bh-interactive-secondary-hover`, `--bh-opacity-30`, `--bh-radius-full`, `--bh-radius-xs-2`, `--bh-space-3xl-16`, `--bh-space-4xl-20`, `--bh-space-5xl-24`, `--bh-space-lg-10`, `--bh-space-md-8`, `--bh-space-none`, `--bh-space-sm-6`, `--bh-space-xl-12`, `--bh-space-xs-4`, `--bh-tag-active-border-width`, `--bh-tag-focus-ring-inset`, `--bh-tag-focus-ring-width`, `--bh-text-body-3xs-semibold-font-size`, `--bh-text-body-3xs-semibold-font-weight`, `--bh-text-body-3xs-semibold-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-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`, `--bh-text-body-xs-medium-font-size`, `--bh-text-body-xs-medium-font-weight`, `--bh-text-body-xs-medium-letter-spacing`, `--bh-text-body-xs-medium-line-height`, `--bh-z-raised`, `--shadow-button-focus` |
94
94
  | `types` | `simple`, `dot`, `flag`, `avatar`, `icon` |
95
95
  <!-- registry-contract:end -->
@@ -101,5 +101,5 @@ This table is generated from `registry/index.json`; edit the component-owned sou
101
101
  | `densities` | `compact`, `default`, `comfortable` |
102
102
  | `layouts` | `inline`, `split`, `stack`, `wrap` |
103
103
  | `styles` | `filter-fields`, `filter-actions`, `selection-actions`, `grouped-fields`, `field-grid`, `inline-search`, `filter-chips`, `saved-segment` |
104
- | `tokens` | `--bh-bg-brand-subtle`, `--bh-bg-default-hover`, `--bh-bg-neutral-subtle`, `--bh-border-brand-strong`, `--bh-border-danger-strong`, `--bh-border-default`, `--bh-border-disabled`, `--bh-border-width-default`, `--bh-button-gap`, `--bh-button-gap-xs`, `--bh-button-icon-offset`, `--bh-button-icon-size`, `--bh-button-label-padding-x`, `--bh-button-md-height`, `--bh-button-md-padding-x`, `--bh-button-sm-height`, `--bh-button-sm-padding-x`, `--bh-button-sm-padding-y`, `--bh-button-xs-height`, `--bh-button-xs-padding-x`, `--bh-content-brand-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-muted`, `--bh-content-on-brand`, `--bh-content-subtle`, `--bh-control-default`, `--bh-input-comfortable-height`, `--bh-input-comfortable-padding-x`, `--bh-input-content-gap`, `--bh-input-lg-height`, `--bh-input-lg-padding-x`, `--bh-input-md-height`, `--bh-input-md-padding-x`, `--bh-input-radius`, `--bh-input-width`, `--bh-interactive-brand-default`, `--bh-interactive-brand-disabled`, `--bh-interactive-brand-hover`, `--bh-interactive-brand-pressed`, `--bh-interactive-ghost-default`, `--bh-interactive-ghost-disabled`, `--bh-interactive-ghost-hover`, `--bh-interactive-ghost-pressed`, `--bh-interactive-input-default`, `--bh-interactive-input-disabled`, `--bh-interactive-outlined-default`, `--bh-interactive-outlined-disabled`, `--bh-interactive-outlined-hover`, `--bh-interactive-outlined-pressed`, `--bh-interactive-soft-default`, `--bh-interactive-soft-disabled`, `--bh-interactive-soft-hover`, `--bh-interactive-soft-pressed`, `--bh-radius-full`, `--bh-select-icon-size`, `--bh-select-icon-slot-size`, `--bh-select-trigger-padding-x`, `--bh-select-width`, `--bh-space-19xl-384`, `--bh-space-3xl-16`, `--bh-space-5xl-24`, `--bh-space-md-8`, `--bh-space-none`, `--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-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-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-soft`, `--shadow-input`, `--shadow-input-focus-ring`, `--shadow-select-trigger` |
104
+ | `tokens` | `--bh-bg-brand-subtle`, `--bh-bg-default-hover`, `--bh-bg-neutral-subtle`, `--bh-border-brand-strong`, `--bh-border-danger-strong`, `--bh-border-default`, `--bh-border-disabled`, `--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-md-height`, `--bh-button-md-padding-x`, `--bh-button-sm-height`, `--bh-button-sm-padding-x`, `--bh-button-sm-padding-y`, `--bh-button-xs-height`, `--bh-button-xs-padding-x`, `--bh-content-brand-default`, `--bh-content-default`, `--bh-content-disabled`, `--bh-content-muted`, `--bh-content-on-brand`, `--bh-content-subtle`, `--bh-control-default`, `--bh-input-comfortable-height`, `--bh-input-comfortable-padding-x`, `--bh-input-content-gap`, `--bh-input-lg-height`, `--bh-input-lg-padding-x`, `--bh-input-md-height`, `--bh-input-md-padding-x`, `--bh-input-radius`, `--bh-input-width`, `--bh-interactive-brand-default`, `--bh-interactive-brand-disabled`, `--bh-interactive-brand-hover`, `--bh-interactive-brand-pressed`, `--bh-interactive-ghost-default`, `--bh-interactive-ghost-disabled`, `--bh-interactive-ghost-hover`, `--bh-interactive-ghost-pressed`, `--bh-interactive-input-default`, `--bh-interactive-input-disabled`, `--bh-interactive-outlined-default`, `--bh-interactive-outlined-disabled`, `--bh-interactive-outlined-hover`, `--bh-interactive-outlined-pressed`, `--bh-interactive-soft-default`, `--bh-interactive-soft-disabled`, `--bh-interactive-soft-hover`, `--bh-interactive-soft-pressed`, `--bh-radius-full`, `--bh-select-icon-size`, `--bh-select-icon-slot-size`, `--bh-select-trigger-padding-x`, `--bh-select-width`, `--bh-space-19xl-384`, `--bh-space-3xl-16`, `--bh-space-5xl-24`, `--bh-space-md-8`, `--bh-space-none`, `--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-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-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-soft`, `--shadow-input`, `--shadow-input-focus-ring`, `--shadow-select-trigger` |
105
105
  <!-- registry-contract:end -->
@@ -38,20 +38,24 @@ not silently resize navigation, cards, typography, or unrelated layout spacing.
38
38
 
39
39
  Elevation uses two layers:
40
40
 
41
- - General steps: `--shadow-xs` through `--shadow-xl`.
41
+ - General steps: `--shadow-xs` through `--shadow-3xl`.
42
42
  - Semantic component aliases: `--shadow-component-*`, focus/danger rings, menus, controls, and component-specific aliases.
43
43
 
44
- The five general steps mirror the Figma `Shadow/xs`, `Shadow/sm`, `Shadow/md`,
45
- `Shadow/lg`, and `Shadow/xl` effect styles. They have no mode-specific bindings
46
- in Figma, so the same recipes are used in light and dark modes.
47
-
48
- | Step | Current component roles |
49
- | --- | --- |
50
- | `xs` | Compact raised details and file-upload error surfaces |
51
- | `sm` | Quiet cards, panels, and staged content surfaces |
52
- | `md` | Slider and toggle handles and floating banners |
53
- | `lg` | Menus, select menus, popovers, tooltips, date surfaces, and dialogs |
54
- | `xl` | Reserved for surfaces that need more separation than current dialogs |
44
+ The seven general steps mirror the current Figma `elevation/01` through
45
+ `elevation/07` effect styles in [Shadow tests → With Variables](https://www.figma.com/design/LVAWtr5A5oEcAvsBmk6vdt/Banhaten-Design-system-3.0?node-id=6436-13190).
46
+ The geometry is shared across modes. Figma's bound shadow color variables make
47
+ the dark-mode drops, rings, and highlights stronger, so Banhaten publishes a
48
+ separate exact recipe for each mode.
49
+
50
+ | CSS step | Figma style | Current component roles |
51
+ | --- | --- | --- |
52
+ | `xs` | `elevation/01` | Flat surfaces with an ambient ring only |
53
+ | `sm` | `elevation/02` | Subtle lift for inner cards, inset panels, and compact raised details |
54
+ | `md` | `elevation/03` | Cards, surfaces, slider thumbs, and toggle handles |
55
+ | `lg` | `elevation/04` | Menus, select menus, popovers, tooltips, and date surfaces |
56
+ | `xl` | `elevation/05` | Sticky and floating surfaces, including floating banners |
57
+ | `2xl` | `elevation/06` | Modals and dialogs |
58
+ | `3xl` | `elevation/07` | Full-overlay surfaces that require maximum depth |
55
59
 
56
60
  Components consume the semantic alias that describes purpose rather than
57
61
  selecting a visual step directly. Focus rings, inset borders, and the Figma
@@ -40,13 +40,18 @@ Keep `Simple Icons` as a separate brand-logo source. It should not be the primar
40
40
 
41
41
  ## Decision Criteria
42
42
 
43
- - Button icon clarity at 16px and 20px.
43
+ - Default UI glyph clarity at 18px, with the glyph centered in a 20px layout slot.
44
+ - Small-control icon clarity at 16px without promoting those intentionally compact glyphs to the default size.
44
45
  - Menu, dropdown, and table icon legibility in dense rows.
45
46
  - Visual match with `default`, `rounded`, and `sharp` appearances.
46
47
  - Variant usefulness: filled states, two-tone states, sharp geometry, or rounded geometry.
47
48
  - Coverage for navigation, data, files, users, settings, alerts, billing, integrations, and brand logos.
48
49
  - Licensing and attribution obligations. Keep source license notices in the project when redistributing SVG/source files.
49
50
 
51
+ ## Default Geometry Contract
52
+
53
+ Default UI icons use `--bh-icon-size-default` (`18px`) inside `--bh-icon-slot-size-default` (`20px`). The resulting `--bh-icon-slot-inset-default` is `1px` on each side. Components must preserve that slot so switching from the former 20px glyph does not change alignment, gaps, or control dimensions. Intentional 12px, 16px, 24px, brand-logo, avatar, spinner, and control-indicator sizes remain component-specific.
54
+
50
55
  ## Sources
51
56
 
52
57
  - Google Material Icons repository: https://github.com/google/material-design-icons
@@ -53,7 +53,7 @@ Without Vite, the CLI installs Tailwind and writes the configured global stylesh
53
53
  The generated stylesheet imports Inter weights 400–700 and IBM Plex Sans Arabic weights 400–700. Set document language and direction at the application boundary:
54
54
 
55
55
  ```tsx
56
- <html lang="ar" dir="rtl" data-theme="blue" data-radius="default">
56
+ <html lang="ar" dir="rtl" data-theme="gray" data-radius="default">
57
57
  ```
58
58
 
59
59
  Wrap server-rendered component trees in `DirectionProvider` when request data already determines the direction; this keeps the first server and client snapshots identical. It is also the boundary for nested surfaces that differ from the document. Without it, components hydrate from a stable LTR snapshot and then synchronize from the document through one shared observer.
@@ -29,14 +29,13 @@ that authored CSS source.
29
29
 
30
30
  The generator emits the complete default-theme graph once, then emits only declarations whose values differ for each alternate theme. Dark mode and alternate themes inherit unchanged scoped aliases through the cascade. This keeps the live graph auditable without duplicating identical declarations.
31
31
 
32
- The default appearance deliberately composes two concerns: `blue` owns brand
33
- roles and `gray` owns the neutral ramp. As a result, default product surfaces
34
- are achromatic, while links, focus states, and primary actions remain blue.
35
- `cssContract.defaultTheme` and `cssContract.defaultNeutralTheme` record that
36
- composition. `npm run tokens:build` synchronizes it into the authored default
37
- theme and semantic color-mode values before compiling the public artifacts.
38
- Alternate brand themes keep their reviewed neutral ramps unless their source is
39
- explicitly changed.
32
+ The default appearance uses `gray` for both brand roles and the neutral ramp.
33
+ As a result, product surfaces, links, focus states, and primary actions are
34
+ monochrome by default. `cssContract.defaultTheme` and
35
+ `cssContract.defaultNeutralTheme` record that composition. `npm run
36
+ tokens:build` synchronizes it into the authored default theme and semantic
37
+ color-mode values before compiling the public artifacts. Alternate brand themes
38
+ keep their reviewed neutral ramps unless their source is explicitly changed.
40
39
 
41
40
  Color values are design-owned. The generated Color Contrast page reports failing combinations and recommendations, but automation must not silently change source colors. A color change requires an explicit design review and regeneration of the contrast baseline.
42
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "banhaten",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Banhaten design system: an Arabic and English-first React design system with shadcn-style component installation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@ const alertDialogContentVariants = cva(
16
16
  "fixed left-1/2 top-1/2 z-[var(--bh-z-overlay)] flex max-h-[calc(100vh-(var(--bh-space-8xl-48)*2))] w-[var(--bh-alert-dialog-width)]",
17
17
  "max-w-[calc(100vw-(var(--bh-space-4xl-20)*2))] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden",
18
18
  "rounded-[var(--bh-radius-2xl-12)] bg-[var(--bh-bg-raised)] text-[var(--bh-content-default)] shadow-[var(--shadow-modal)] outline-none",
19
- "[--bh-alert-dialog-border:var(--bh-border-subtle)] [--shadow-modal:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-alert-dialog-border),var(--shadow-lg)]",
19
+ "[--bh-alert-dialog-border:var(--bh-border-subtle)] [--shadow-modal:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-alert-dialog-border),var(--shadow-2xl)]",
20
20
  ],
21
21
  {
22
22
  variants: {
@@ -159,7 +159,7 @@ const Alert = React.forwardRef<HTMLElement, AlertProps>(function Alert(
159
159
  >
160
160
  <span
161
161
  data-slot="alert-icon"
162
- className="inline-flex size-[var(--bh-alert-icon-size)] shrink-0 items-center justify-center text-[var(--alert-icon)]"
162
+ className="inline-flex size-[var(--bh-alert-icon-slot-size)] shrink-0 items-center justify-center text-[var(--alert-icon)]"
163
163
  >
164
164
  <AlertStatusIcon status={resolvedStatus} />
165
165
  </span>
@@ -216,7 +216,7 @@ const Alert = React.forwardRef<HTMLElement, AlertProps>(function Alert(
216
216
  <Button
217
217
  data-slot="alert-close"
218
218
  aria-label={resolvedCloseLabel}
219
- className="size-[var(--bh-alert-icon-size)] shrink-0 rounded-[var(--bh-radius-full)] p-0 text-[var(--bh-content-subtle)]"
219
+ className="size-[var(--bh-alert-icon-slot-size)] shrink-0 rounded-[var(--bh-radius-full)] p-0 text-[var(--bh-content-subtle)]"
220
220
  onClick={onClose}
221
221
  size="icon-xs"
222
222
  type="button"
@@ -277,7 +277,7 @@ function AlertStatusIcon({ status }: { status: AlertStatus }) {
277
277
 
278
278
  function CloseIcon() {
279
279
  return (
280
- <XIcon aria-hidden="true" className="size-[var(--bh-alert-icon-size)]" strokeWidth="var(--bh-icon-stroke-225)" />
280
+ <XIcon aria-hidden="true" className="size-[var(--bh-space-3xl-16)]" strokeWidth="var(--bh-icon-stroke-225)" />
281
281
  )
282
282
  }
283
283
 
@@ -208,9 +208,15 @@ const AttributeHeader = React.forwardRef<HTMLElement, AttributeHeaderProps>(
208
208
  {icon ? (
209
209
  <span
210
210
  data-slot="attribute-header-icon"
211
- className="grid size-[var(--bh-attribute-header-icon-size)] place-items-center rounded-[var(--bh-radius-full)] bg-[var(--bh-bg-neutral-soft)] text-[var(--bh-content-subtle)] sm:size-[var(--bh-attribute-header-icon-size-lg)] [&_svg]:size-[var(--bh-space-4xl-20)] sm:[&_svg]:size-[var(--bh-space-5xl-24)]"
211
+ className="grid size-[var(--bh-attribute-header-icon-size)] place-items-center rounded-[var(--bh-radius-full)] bg-[var(--bh-bg-neutral-soft)] text-[var(--bh-content-subtle)] sm:size-[var(--bh-attribute-header-icon-size-lg)]"
212
212
  >
213
- {icon}
213
+ <span
214
+ aria-hidden="true"
215
+ data-slot="attribute-header-icon-slot"
216
+ className="grid size-[var(--bh-icon-slot-size-default)] place-items-center [&_svg]:size-[var(--bh-icon-size-default)] sm:size-[var(--bh-space-5xl-24)] sm:[&_svg]:size-[var(--bh-space-5xl-24)]"
217
+ >
218
+ {icon}
219
+ </span>
214
220
  </span>
215
221
  ) : null}
216
222
  <span
@@ -16,20 +16,20 @@ import { cn } from "@/lib/utils"
16
16
 
17
17
  const avatarSizeVariants = {
18
18
  "2xs":
19
- "[--bh-avatar-size:var(--bh-avatar-size-2xs)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-2xs)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-2xs)]",
20
- xs: "[--bh-avatar-size:var(--bh-avatar-size-xs)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-xs)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-xs)]",
21
- sm: "[--bh-avatar-size:var(--bh-avatar-size-sm)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-sm)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-sm)]",
22
- md: "[--bh-avatar-size:var(--bh-avatar-size-md)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-md)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-md)]",
23
- lg: "[--bh-avatar-size:var(--bh-avatar-size-lg)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-lg)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-lg)]",
19
+ "[--bh-avatar-size:var(--bh-avatar-size-2xs)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-2xs)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-2xs)] [--bh-avatar-icon-slot-size:var(--bh-avatar-icon-size)]",
20
+ xs: "[--bh-avatar-size:var(--bh-avatar-size-xs)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-xs)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-xs)] [--bh-avatar-icon-slot-size:var(--bh-avatar-icon-size)]",
21
+ sm: "[--bh-avatar-size:var(--bh-avatar-size-sm)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-sm)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-sm)] [--bh-avatar-icon-slot-size:var(--bh-avatar-icon-size)]",
22
+ md: "[--bh-avatar-size:var(--bh-avatar-size-md)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-md)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-md)] [--bh-avatar-icon-slot-size:var(--bh-icon-slot-size-default)]",
23
+ lg: "[--bh-avatar-size:var(--bh-avatar-size-lg)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-lg)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-lg)] [--bh-avatar-icon-slot-size:var(--bh-icon-slot-size-default)]",
24
24
  default:
25
- "[--bh-avatar-size:var(--bh-avatar-size-xl)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-xl)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-xl)]",
26
- xl: "[--bh-avatar-size:var(--bh-avatar-size-xl)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-xl)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-xl)]",
25
+ "[--bh-avatar-size:var(--bh-avatar-size-xl)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-xl)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-xl)] [--bh-avatar-icon-slot-size:var(--bh-avatar-icon-size)]",
26
+ xl: "[--bh-avatar-size:var(--bh-avatar-size-xl)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-xl)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-xl)] [--bh-avatar-icon-slot-size:var(--bh-avatar-icon-size)]",
27
27
  "2xl":
28
- "[--bh-avatar-size:var(--bh-avatar-size-2xl)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-2xl)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-2xl)]",
28
+ "[--bh-avatar-size:var(--bh-avatar-size-2xl)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-2xl)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-2xl)] [--bh-avatar-icon-slot-size:var(--bh-avatar-icon-size)]",
29
29
  "3xl":
30
- "[--bh-avatar-size:var(--bh-avatar-size-3xl)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-3xl)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-3xl)]",
30
+ "[--bh-avatar-size:var(--bh-avatar-size-3xl)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-3xl)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-3xl)] [--bh-avatar-icon-slot-size:var(--bh-avatar-icon-size)]",
31
31
  "4xl":
32
- "[--bh-avatar-size:var(--bh-avatar-size-4xl)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-4xl)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-4xl)]",
32
+ "[--bh-avatar-size:var(--bh-avatar-size-4xl)] [--bh-avatar-fallback-size:var(--bh-avatar-fallback-font-size-4xl)] [--bh-avatar-icon-size:var(--bh-avatar-icon-size-4xl)] [--bh-avatar-icon-slot-size:var(--bh-avatar-icon-size)]",
33
33
  } as const
34
34
 
35
35
  const avatarVariants = cva(
@@ -212,13 +212,17 @@ function AvatarIcon({
212
212
  className={cn(avatarIconVariants({ size, className }))}
213
213
  {...props}
214
214
  >
215
- {children ?? (
216
- <UserIcon
217
- aria-hidden="true"
218
- className="size-[var(--bh-avatar-icon-size)]"
219
- strokeWidth="var(--bh-icon-stroke-210)"
220
- />
221
- )}
215
+ <span
216
+ data-slot="avatar-icon-slot"
217
+ className="flex size-[var(--bh-avatar-icon-slot-size,var(--bh-avatar-icon-size))] shrink-0 items-center justify-center [&_svg]:size-[var(--bh-avatar-icon-size)]"
218
+ >
219
+ {children ?? (
220
+ <UserIcon
221
+ aria-hidden="true"
222
+ strokeWidth="var(--bh-icon-stroke-210)"
223
+ />
224
+ )}
225
+ </span>
222
226
  </span>
223
227
  )
224
228
  }
@@ -48,7 +48,7 @@ const buttonGroupVariants = cva(
48
48
  )
49
49
 
50
50
  const buttonGroupItemVariants = cva(
51
- "relative inline-flex shrink-0 items-center justify-center whitespace-nowrap bg-[var(--bh-button-group-item-bg)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] text-[var(--bh-button-group-content)] outline-none transition-[background-color,box-shadow,color] before:absolute before:inset-y-0 before:start-0 before:w-[var(--bh-border-width-default)] before:bg-[var(--bh-button-group-divider)] before:content-[''] first:before:hidden hover:bg-[var(--bh-button-group-item-hover-bg)] active:bg-[var(--bh-button-group-item-pressed-bg)] focus-visible:z-[var(--bh-z-raised)] focus-visible:shadow-[var(--shadow-button-group-focus)] aria-[pressed=true]:bg-[var(--bh-button-group-item-active-bg)] aria-[pressed=true]:text-[var(--bh-button-group-content-active)] data-[active=true]:bg-[var(--bh-button-group-item-active-bg)] data-[active=true]:text-[var(--bh-button-group-content-active)] disabled:pointer-events-none disabled:bg-[var(--bh-button-group-item-disabled-bg)] disabled:text-[var(--bh-button-group-content-disabled)] [&_svg]:pointer-events-none [&_svg]:size-[var(--bh-button-group-icon-size)] [&_svg]:shrink-0 rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
51
+ "relative inline-flex shrink-0 items-center justify-center whitespace-nowrap bg-[var(--bh-button-group-item-bg)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] text-[var(--bh-button-group-content)] outline-none transition-[background-color,box-shadow,color] before:absolute before:inset-y-0 before:start-0 before:w-[var(--bh-border-width-default)] before:bg-[var(--bh-button-group-divider)] before:content-[''] first:before:hidden hover:bg-[var(--bh-button-group-item-hover-bg)] active:bg-[var(--bh-button-group-item-pressed-bg)] focus-visible:z-[var(--bh-z-raised)] focus-visible:shadow-[var(--shadow-button-group-focus)] aria-[pressed=true]:bg-[var(--bh-button-group-item-active-bg)] aria-[pressed=true]:text-[var(--bh-button-group-content-active)] data-[active=true]:bg-[var(--bh-button-group-item-active-bg)] data-[active=true]:text-[var(--bh-button-group-content-active)] disabled:pointer-events-none disabled:bg-[var(--bh-button-group-item-disabled-bg)] disabled:text-[var(--bh-button-group-content-disabled)] [&_svg]:pointer-events-none [&_svg]:size-[var(--bh-button-group-icon-size)] [&_svg]:m-[calc((var(--bh-button-group-icon-slot-size)-var(--bh-button-group-icon-size))/2)] [&_svg]:shrink-0 rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
52
52
  {
53
53
  variants: {
54
54
  size: {
@@ -7,7 +7,7 @@ import { cn } from "@/lib/utils"
7
7
  type ControlDensity = "compact" | "default" | "comfortable"
8
8
 
9
9
  const buttonVariants = cva(
10
- "group/button inline-flex min-w-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-control-default)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:shadow-[var(--shadow-button-focus)] disabled:pointer-events-none disabled:shadow-none aria-invalid:border-[length:var(--bh-border-width-default)] aria-invalid:border-[var(--bh-border-danger-strong)] aria-invalid:shadow-[var(--shadow-button-danger-focus)] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[var(--bh-button-icon-size)] [&_svg[data-icon='inline-start']]:ms-[var(--bh-button-icon-offset)] [&_svg[data-icon='inline-end']]:me-[var(--bh-button-icon-offset)] rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
10
+ "group/button inline-flex min-w-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-control-default)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:shadow-[var(--shadow-button-focus)] disabled:pointer-events-none disabled:shadow-none aria-invalid:border-[length:var(--bh-border-width-default)] aria-invalid:border-[var(--bh-border-danger-strong)] aria-invalid:shadow-[var(--shadow-button-danger-focus)] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[var(--bh-button-icon-size)] [&_svg:not([class*='size-'])]:m-[calc((var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] [&_svg[data-icon='inline-start']]:ms-[calc(var(--bh-button-icon-offset)+(var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] [&_svg[data-icon='inline-end']]:me-[calc(var(--bh-button-icon-offset)+(var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
11
11
  {
12
12
  variants: {
13
13
  variant: {
@@ -224,7 +224,7 @@ const monthButton = cva(
224
224
  "flex size-[var(--bh-date-nav-button-size)] shrink-0 items-center justify-center rounded-[var(--bh-control-default)]",
225
225
  "bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-default)] outline-none transition-[background-color,box-shadow,color]",
226
226
  "hover:bg-[var(--bh-interactive-soft-hover)] focus-visible:shadow-[var(--shadow-button-focus)] disabled:opacity-[var(--bh-opacity-0)]",
227
- "[&_svg]:size-[var(--bh-date-nav-icon-size)]",
227
+ "[&_[data-slot=date-picker-nav-icon]>svg]:size-[var(--bh-date-nav-icon-size)]",
228
228
  ]
229
229
  )
230
230
 
@@ -764,7 +764,7 @@ function DatePickerTrigger({
764
764
  <span
765
765
  aria-hidden="true"
766
766
  data-slot="date-picker-trigger-icon"
767
- className="flex size-[var(--bh-date-trigger-icon-size)] shrink-0 items-center justify-center text-[var(--bh-content-muted)] group-disabled/date-trigger:text-[var(--bh-content-disabled)] [&_svg:not([class*='size-'])]:size-[var(--bh-date-trigger-icon-size)]"
767
+ className="flex size-[var(--bh-date-trigger-icon-slot-size)] shrink-0 items-center justify-center text-[var(--bh-content-muted)] group-disabled/date-trigger:text-[var(--bh-content-disabled)] [&_svg:not([class*='size-'])]:size-[var(--bh-date-trigger-icon-size)]"
768
768
  >
769
769
  {icon ?? <CalendarDaysIcon strokeWidth="var(--bh-icon-stroke-180)" />}
770
770
  </span>
@@ -873,7 +873,13 @@ function CalendarMonth({
873
873
  className={monthButton()}
874
874
  onClick={() => onDisplayMonthChange(addMonths(displayMonth, -1))}
875
875
  >
876
- <ChevronLeftIcon aria-hidden="true" data-rtl-flip="true" />
876
+ <span
877
+ aria-hidden="true"
878
+ data-slot="date-picker-nav-icon"
879
+ className="inline-flex size-[var(--bh-date-nav-icon-slot-size)] items-center justify-center"
880
+ >
881
+ <ChevronLeftIcon data-rtl-flip="true" />
882
+ </span>
877
883
  </button>
878
884
 
879
885
  {hasMonthToggle ? (
@@ -912,7 +918,13 @@ function CalendarMonth({
912
918
  className={monthButton()}
913
919
  onClick={() => onDisplayMonthChange(addMonths(displayMonth, 1))}
914
920
  >
915
- <ChevronRightIcon aria-hidden="true" data-rtl-flip="true" />
921
+ <span
922
+ aria-hidden="true"
923
+ data-slot="date-picker-nav-icon"
924
+ className="inline-flex size-[var(--bh-date-nav-icon-slot-size)] items-center justify-center"
925
+ >
926
+ <ChevronRightIcon data-rtl-flip="true" />
927
+ </span>
916
928
  </button>
917
929
  </div>
918
930
 
@@ -50,10 +50,11 @@ export function OnboardingStepListItem({
50
50
 
51
51
  function CheckCircleIcon() {
52
52
  return (
53
- <CircleCheckIcon
54
- aria-hidden="true"
55
- className="ds-onboarding-step__check"
56
- strokeWidth="var(--bh-icon-stroke-225)"
57
- />
53
+ <span className="ds-onboarding-step__check-slot" aria-hidden="true">
54
+ <CircleCheckIcon
55
+ className="ds-onboarding-step__check"
56
+ strokeWidth="var(--bh-icon-stroke-225)"
57
+ />
58
+ </span>
58
59
  );
59
60
  }
@@ -17,7 +17,7 @@
17
17
  --banner-height-default: calc(var(--bh-space-8xl-48) + var(--bh-space-xl-12));
18
18
  --banner-height-title: calc(var(--bh-space-8xl-48) + var(--bh-space-xs-4));
19
19
  --banner-input-width: var(--bh-input-width);
20
- --banner-shadow-floating: var(--shadow-md);
20
+ --banner-shadow-floating: var(--shadow-xl);
21
21
 
22
22
  box-sizing: border-box;
23
23
  position: relative;
@@ -148,10 +148,15 @@
148
148
  color: var(--banner-icon-fg);
149
149
  }
150
150
 
151
- .ds-banner__icon-wrap,
152
- .ds-banner__icon {
153
- width: var(--bh-space-4xl-20);
154
- height: var(--bh-space-4xl-20);
151
+ .ds-banner__icon-wrap {
152
+ width: var(--bh-icon-slot-size-default);
153
+ height: var(--bh-icon-slot-size-default);
154
+ }
155
+
156
+ .ds-banner__icon,
157
+ .ds-banner__icon-wrap > svg {
158
+ width: var(--bh-icon-size-default);
159
+ height: var(--bh-icon-size-default);
155
160
  }
156
161
 
157
162
  .ds-banner__icon,
@@ -73,8 +73,8 @@
73
73
  }
74
74
 
75
75
  .ds-breadcrumbs__icon {
76
- width: var(--bh-space-4xl-20);
77
- height: var(--bh-space-4xl-20);
76
+ width: var(--bh-icon-slot-size-default);
77
+ height: var(--bh-icon-slot-size-default);
78
78
  flex: 0 0 auto;
79
79
  display: inline-grid;
80
80
  place-items: center;
@@ -86,8 +86,8 @@
86
86
  }
87
87
 
88
88
  .ds-breadcrumbs__icon svg {
89
- width: var(--bh-space-4xl-20);
90
- height: var(--bh-space-4xl-20);
89
+ width: var(--bh-icon-size-default);
90
+ height: var(--bh-icon-size-default);
91
91
  display: block;
92
92
  }
93
93
 
@@ -195,7 +195,7 @@
195
195
  --ds-file-upload-row-border: var(--bh-border-danger-strong);
196
196
  --ds-file-upload-row-shadow:
197
197
  inset 0px 0px 0px var(--bh-border-width-default) var(--ds-file-upload-row-border),
198
- var(--shadow-xs);
198
+ var(--shadow-sm);
199
199
  }
200
200
 
201
201
  .ds-file-upload-row__content {
@@ -442,8 +442,8 @@
442
442
  }
443
443
 
444
444
  .ds-file-upload-row__action svg {
445
- width: var(--bh-space-4xl-20);
446
- height: var(--bh-space-4xl-20);
445
+ width: var(--bh-icon-size-default);
446
+ height: var(--bh-icon-size-default);
447
447
  }
448
448
 
449
449
  .ds-file-upload:dir(rtl) {