@devalok/shilp-sutra 0.37.0 → 0.38.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 (183) hide show
  1. package/MIGRATION.md +67 -0
  2. package/dist/_chunks/tiptap.js +977 -968
  3. package/dist/_chunks/tiptap.js.map +1 -1
  4. package/dist/composed/index.d.ts +0 -2
  5. package/dist/composed/index.d.ts.map +1 -1
  6. package/dist/composed/index.js +5 -6
  7. package/dist/composed/inline-edit.d.ts.map +1 -1
  8. package/dist/composed/inline-edit.js +42 -40
  9. package/dist/composed/inline-edit.js.map +1 -1
  10. package/dist/hooks/index.d.ts +2 -2
  11. package/dist/hooks/index.d.ts.map +1 -1
  12. package/dist/hooks/index.js +0 -1
  13. package/dist/test-utils/conformance.d.ts +37 -0
  14. package/dist/test-utils/conformance.d.ts.map +1 -0
  15. package/dist/ui/alert.d.ts +1 -2
  16. package/dist/ui/alert.d.ts.map +1 -1
  17. package/dist/ui/alert.js +1 -27
  18. package/dist/ui/alert.js.map +1 -1
  19. package/dist/ui/banner.d.ts +3 -5
  20. package/dist/ui/banner.d.ts.map +1 -1
  21. package/dist/ui/banner.js +13 -13
  22. package/dist/ui/banner.js.map +1 -1
  23. package/dist/ui/index.d.ts +1 -1
  24. package/dist/ui/index.d.ts.map +1 -1
  25. package/dist/ui/index.js +2 -2
  26. package/dist/ui/index.js.map +1 -1
  27. package/dist/ui/input.d.ts +1 -9
  28. package/dist/ui/input.d.ts.map +1 -1
  29. package/dist/ui/input.js +26 -27
  30. package/dist/ui/input.js.map +1 -1
  31. package/dist/ui/segmented-control.d.ts +1 -1
  32. package/dist/ui/segmented-control.d.ts.map +1 -1
  33. package/dist/ui/segmented-control.js +2 -4
  34. package/dist/ui/segmented-control.js.map +1 -1
  35. package/docs/components/_header.md +1 -1
  36. package/docs/components/composed/activity-feed.md +6 -0
  37. package/docs/components/composed/avatar-group.md +8 -0
  38. package/docs/components/composed/bulk-action-bar.md +6 -0
  39. package/docs/components/composed/command-palette.md +7 -0
  40. package/docs/components/composed/confirm-dialog.md +6 -0
  41. package/docs/components/composed/content-card.md +6 -0
  42. package/docs/components/composed/date-picker.md +10 -0
  43. package/docs/components/composed/deadline-indicator.md +7 -0
  44. package/docs/components/composed/emoji-picker.md +8 -0
  45. package/docs/components/composed/empty-state.md +8 -0
  46. package/docs/components/composed/error-boundary.md +7 -0
  47. package/docs/components/composed/file-preview.md +8 -0
  48. package/docs/components/composed/filter-bar.md +8 -0
  49. package/docs/components/composed/form-section.md +6 -0
  50. package/docs/components/composed/global-loading.md +6 -0
  51. package/docs/components/composed/inline-edit.md +8 -0
  52. package/docs/components/composed/loading-skeleton.md +7 -0
  53. package/docs/components/composed/markdown-viewer.md +9 -0
  54. package/docs/components/composed/master-detail.md +7 -0
  55. package/docs/components/composed/member-picker.md +7 -0
  56. package/docs/components/composed/multi-select-popover.md +11 -0
  57. package/docs/components/composed/page-header.md +7 -0
  58. package/docs/components/composed/page-skeletons.md +10 -0
  59. package/docs/components/composed/priority-indicator.md +7 -0
  60. package/docs/components/composed/rich-chat-input.md +12 -0
  61. package/docs/components/composed/rich-text-editor.md +9 -0
  62. package/docs/components/composed/schedule-view.md +8 -0
  63. package/docs/components/composed/simple-tooltip.md +7 -0
  64. package/docs/components/composed/status-badge.md +10 -0
  65. package/docs/components/shell/app-command-palette.md +8 -0
  66. package/docs/components/shell/bottom-navbar.md +12 -0
  67. package/docs/components/shell/command-registry.md +7 -0
  68. package/docs/components/shell/link-context.md +15 -0
  69. package/docs/components/shell/notification-center.md +10 -0
  70. package/docs/components/shell/notification-preferences.md +7 -0
  71. package/docs/components/shell/sidebar.md +11 -0
  72. package/docs/components/shell/top-bar.md +11 -0
  73. package/docs/components/ui/accordion.md +10 -0
  74. package/docs/components/ui/alert-dialog.md +10 -1
  75. package/docs/components/ui/alert.md +13 -2
  76. package/docs/components/ui/aspect-ratio.md +7 -0
  77. package/docs/components/ui/autocomplete.md +7 -0
  78. package/docs/components/ui/avatar.md +10 -0
  79. package/docs/components/ui/badge-group.md +7 -0
  80. package/docs/components/ui/badge-indicator.md +7 -0
  81. package/docs/components/ui/badge.md +12 -0
  82. package/docs/components/ui/banner.md +10 -1
  83. package/docs/components/ui/breadcrumb.md +8 -0
  84. package/docs/components/ui/button-group.md +24 -4
  85. package/docs/components/ui/button-processing.md +5 -0
  86. package/docs/components/ui/button.md +9 -0
  87. package/docs/components/ui/card.md +16 -4
  88. package/docs/components/ui/charts.md +9 -0
  89. package/docs/components/ui/chat.md +9 -0
  90. package/docs/components/ui/checkbox.md +15 -1
  91. package/docs/components/ui/chip.md +9 -0
  92. package/docs/components/ui/code.md +7 -0
  93. package/docs/components/ui/collapsible.md +9 -0
  94. package/docs/components/ui/color-input.md +8 -0
  95. package/docs/components/ui/color-swatch.md +7 -0
  96. package/docs/components/ui/combobox.md +16 -3
  97. package/docs/components/ui/container.md +11 -0
  98. package/docs/components/ui/context-menu.md +8 -0
  99. package/docs/components/ui/data-table-body.md +4 -0
  100. package/docs/components/ui/data-table-bulk-actions.md +4 -0
  101. package/docs/components/ui/data-table-card.md +4 -0
  102. package/docs/components/ui/data-table-context.md +4 -0
  103. package/docs/components/ui/data-table-header.md +4 -0
  104. package/docs/components/ui/data-table-pagination.md +4 -0
  105. package/docs/components/ui/data-table-toolbar.md +7 -0
  106. package/docs/components/ui/data-table.md +25 -0
  107. package/docs/components/ui/devalok-grain.md +8 -0
  108. package/docs/components/ui/dialog.md +10 -0
  109. package/docs/components/ui/dropdown-menu.md +10 -0
  110. package/docs/components/ui/file-upload.md +8 -0
  111. package/docs/components/ui/form.md +21 -0
  112. package/docs/components/ui/hover-card.md +9 -0
  113. package/docs/components/ui/icon-button.md +7 -0
  114. package/docs/components/ui/icon-context.md +7 -0
  115. package/docs/components/ui/icon-group.md +9 -0
  116. package/docs/components/ui/icon.md +7 -0
  117. package/docs/components/ui/input-otp.md +29 -7
  118. package/docs/components/ui/input.md +11 -2
  119. package/docs/components/ui/label.md +7 -1
  120. package/docs/components/ui/link.md +7 -0
  121. package/docs/components/ui/menubar.md +9 -0
  122. package/docs/components/ui/navigation-menu.md +9 -0
  123. package/docs/components/ui/number-input.md +10 -0
  124. package/docs/components/ui/pagination.md +10 -0
  125. package/docs/components/ui/popover.md +9 -0
  126. package/docs/components/ui/progress-ring.md +7 -0
  127. package/docs/components/ui/progress.md +7 -0
  128. package/docs/components/ui/radio.md +27 -2
  129. package/docs/components/ui/search-input.md +8 -0
  130. package/docs/components/ui/segmented-control.md +21 -6
  131. package/docs/components/ui/select.md +14 -3
  132. package/docs/components/ui/separator.md +7 -0
  133. package/docs/components/ui/sheet.md +10 -0
  134. package/docs/components/ui/sidebar.md +30 -0
  135. package/docs/components/ui/skeleton.md +7 -0
  136. package/docs/components/ui/slider.md +15 -1
  137. package/docs/components/ui/spinner.md +7 -0
  138. package/docs/components/ui/split-button.md +11 -0
  139. package/docs/components/ui/stack.md +8 -0
  140. package/docs/components/ui/stat-card.md +9 -0
  141. package/docs/components/ui/status-dot.md +9 -0
  142. package/docs/components/ui/stepper.md +8 -0
  143. package/docs/components/ui/switch.md +8 -0
  144. package/docs/components/ui/table.md +8 -0
  145. package/docs/components/ui/tabs.md +10 -2
  146. package/docs/components/ui/text.md +9 -4
  147. package/docs/components/ui/textarea.md +7 -1
  148. package/docs/components/ui/toast.md +9 -0
  149. package/docs/components/ui/toaster.md +8 -0
  150. package/docs/components/ui/toggle-group.md +13 -4
  151. package/docs/components/ui/toggle.md +10 -2
  152. package/docs/components/ui/tooltip.md +11 -1
  153. package/docs/components/ui/tree-view.md +12 -0
  154. package/docs/components/ui/visually-hidden.md +7 -0
  155. package/docs/recipes/customize-brand.md +216 -0
  156. package/docs/recipes/index.md +51 -0
  157. package/docs/recipes/install-astro.md +178 -0
  158. package/docs/recipes/install-next-app-router.md +230 -0
  159. package/docs/recipes/install-next-pages.md +123 -0
  160. package/docs/recipes/install-remix.md +171 -0
  161. package/docs/recipes/install-tanstack-start.md +143 -0
  162. package/docs/recipes/install-vite.md +170 -0
  163. package/docs/recipes/server-components.md +209 -0
  164. package/docs/recipes/troubleshoot.md +217 -0
  165. package/llms-full.txt +1133 -88
  166. package/llms.txt +33 -16
  167. package/package.json +45 -35
  168. package/dist/composed/responsive-overlay.d.ts +0 -23
  169. package/dist/composed/responsive-overlay.d.ts.map +0 -1
  170. package/dist/composed/responsive-overlay.js +0 -40
  171. package/dist/composed/responsive-overlay.js.map +0 -1
  172. package/dist/hooks/use-toast.d.ts +0 -17
  173. package/dist/hooks/use-toast.d.ts.map +0 -1
  174. package/dist/hooks/use-toast.js +0 -3
  175. package/dist/tailwind/index.cjs +0 -41
  176. package/dist/tailwind/index.d.ts +0 -2
  177. package/dist/tailwind/index.d.ts.map +0 -1
  178. package/dist/tailwind/index.js +0 -2
  179. package/dist/tailwind/preset.d.ts +0 -25
  180. package/dist/tailwind/preset.d.ts.map +0 -1
  181. package/dist/tailwind/preset.js +0 -17
  182. package/dist/tailwind/preset.js.map +0 -1
  183. package/docs/components/composed/responsive-overlay.md +0 -34
@@ -7,7 +7,6 @@
7
7
  ## Props
8
8
  color: "info" | "success" | "warning" | "error" | "neutral"
9
9
  actions: ReactNode (optional action slot for one or more buttons; wraps on narrow viewports)
10
- action: ReactNode (DEPRECATED — use `actions` instead)
11
10
  onDismiss: () => void (optional, shows X button)
12
11
  children: ReactNode (message text)
13
12
 
@@ -21,12 +20,22 @@
21
20
  </Banner>
22
21
  ```
23
22
 
23
+ ## Composability
24
+ - **Banner vs Alert vs Toast:** Banner is full-width, top-of-page / top-of-region announcements (maintenance, quota, trial-expiring). Alert is inline in-flow. Toast is transient floating notifications. Pick by where the message lives in the visual hierarchy.
25
+ - **actions slot:** Pass one or more `<Button>` elements — the container flex-wraps on narrow viewports so actions drop below the message rather than squeezing horizontally.
26
+ - **Role=alert** announces assertively. Same constraint as Alert — don't stack multiple Banners.
27
+ - **Dismissal timing:** `onDismiss` fires AFTER the exit animation completes, not on click. Use it to remove the banner from state only after it's visually gone.
28
+ - Works inside Shell layouts (TopBar / Sidebar / main content) — usually sits above main content, below the TopBar.
29
+
24
30
  ## Gotchas
25
31
  - Banner is full-width (spans container). Alert is inline.
26
32
  - Renders role="alert" automatically
27
33
  - `onDismiss` fires after the exit animation completes, not immediately on dismiss button click
28
34
 
29
35
  ## Changes
36
+ ### v0.38.0
37
+ - **Removed** (BREAKING) deprecated singular `action` prop. Use `actions={[...]}` (plural).
38
+
30
39
  ### v0.20.0
31
40
  - **Added** `actions` prop (plural) for multiple action buttons with mobile-friendly flex-wrap
32
41
  - **Deprecated** `action` prop — use `actions` instead (both still work; `actions` takes priority)
@@ -26,8 +26,16 @@
26
26
  </Breadcrumb>
27
27
  ```
28
28
 
29
+ ## Composability
30
+ - **Compound structure only** — no context cascade, each part is standalone. The semantic shape (`<nav>` → `<ol>` → `<li>`) is what matters for a11y, not any internal state.
31
+ - **BreadcrumbLink vs BreadcrumbPage:** Use BreadcrumbLink for everything except the current page; BreadcrumbPage for the current page (not clickable, renders without href, announced as "current page" via aria-current).
32
+ - **Router integration:** Use `<BreadcrumbLink asChild><NextLink href="/x">...</NextLink></BreadcrumbLink>` (Radix Slot pattern).
33
+ - **Truncation:** For long paths, use BreadcrumbEllipsis between the first and last items instead of showing all. Pair with a DropdownMenu (open the ellipsis to show hidden intermediate items) if you need to expose them.
34
+ - **PageHeader (composed) auto-builds Breadcrumb** from its `breadcrumbs` prop — use that higher-level component for page-top breadcrumbs and save this component for inline or custom positioning.
35
+
29
36
  ## Gotchas
30
37
  - Use BreadcrumbPage for the current (non-clickable) page, BreadcrumbLink for navigable items
38
+ - Don't wrap BreadcrumbPage in a link — it's the current location by definition
31
39
 
32
40
  ## Changes
33
41
  ### v0.18.0
@@ -5,24 +5,44 @@
5
5
  - Category: ui
6
6
 
7
7
  ## Props
8
- variant: ButtonProps['variant'] (propagated to children)
8
+ variant: ButtonProps['variant'] (propagated to children via context)
9
9
  color: ButtonProps['color'] (propagated to children)
10
10
  size: ButtonProps['size'] (propagated to children)
11
- orientation: "horizontal" | "vertical" (default: "horizontal")
11
+ weight: ButtonProps['weight'] (propagated to children)
12
+ shape: ButtonProps['shape'] (propagated to children)
13
+ disabled: boolean (propagates to all children)
14
+ orientation: "horizontal" | "vertical"
15
+ attached: boolean (true = buttons visually merge with shared borders; false = spaced apart via gap)
16
+ fullWidth: boolean (group stretches to parent width; children stretch equally)
12
17
 
13
18
  ## Defaults
14
- orientation="horizontal"
19
+ orientation="horizontal", attached=true
15
20
 
16
21
  ## Example
17
22
  ```jsx
18
23
  <ButtonGroup variant="outline" size="sm">
19
24
  <Button>Bold</Button>
20
25
  <Button>Italic</Button>
26
+ <Button>Underline</Button>
27
+ </ButtonGroup>
28
+
29
+ {/* Spaced, not attached */}
30
+ <ButtonGroup attached={false} variant="soft">
31
+ <Button>Save</Button>
32
+ <Button>Cancel</Button>
21
33
  </ButtonGroup>
22
34
  ```
23
35
 
36
+ ## Composability
37
+ - Every Button child reads variant/color/size/weight/shape/disabled from ButtonGroup context. Explicit props on individual children override context.
38
+ - **Position-aware border radius:** Child Buttons read their position in the group (first / middle / last) and apply appropriate corner radii inline. Works for both horizontal and vertical orientations.
39
+ - **Focus z-index isolation:** The focused button rises above its siblings so the focus ring isn't clipped by adjacent borders.
40
+ - **Tonal dividers:** For solid/soft/ghost variants without visible borders, ButtonGroup injects subtle divider elements between children.
41
+ - **Works with SplitButton:** `<SplitButton>` inside a `<ButtonGroup>` inherits the same context and position rules.
42
+
24
43
  ## Gotchas
25
- - Children can override variant/size individually
44
+ - Children can override variant/size individually — context is a default, not a lock
45
+ - `attached={false}` disables the position-aware border radius (children use their default corners)
26
46
 
27
47
  ## Changes
28
48
  ### v0.33.0
@@ -10,6 +10,11 @@ Internal component that renders the processing animation overlay for Button. Con
10
10
 
11
11
  See `Button` docs for the public API.
12
12
 
13
+ ## Composability
14
+ - **Internal only — do NOT import or use directly.** The Button component owns the lifecycle of this overlay.
15
+ - Drives Button's `processing` prop rendering. If you're building a custom button and want similar marching-ants feedback, copy the SVG/animation pattern — don't reach into this component.
16
+ - Listed here for reference so consumers grep'ing for "processing" find the behavior origin.
17
+
13
18
  ## Changes
14
19
  ### v0.29.0
15
20
  - **Added** Initial release — marching ants processing overlay (SVG dashed rect with animated stroke-dashoffset). Speed tiers: ambient (3s), working (2s), urgent (1s). Color maps to step-11 tokens for visibility on all variants.
@@ -34,6 +34,15 @@
34
34
  <Button variant="soft" color="warning" size="compact-sm" shape="pill">Overdue</Button>
35
35
  ```
36
36
 
37
+ ## Composability
38
+ - **ButtonGroup context consumption:** When nested inside `<ButtonGroup>`, Button auto-inherits variant/color/size/weight/shape/disabled. Explicit props on the individual Button override. The context also drives position-aware border-radius (first/middle/last within an attached group).
39
+ - **IconProvider cascade:** Icons in `startIcon`/`endIcon` auto-size via IconProvider per the button size (xs→sm, sm→sm, md→md, lg→md, icon-xs→xs, icon-lg→lg). Don't pass explicit `size` to `<Icon>` inside Button.
40
+ - **asChild for router links:** `<Button asChild><Link href="/foo">...</Link></Button>` transfers Button's styling to the Link while preserving navigation semantics. Required for Next.js `<Link>` / react-router `<Link>`.
41
+ - **onClickAsync state machine:** Overrides onClick. Auto-cycles `idle → loading (aria-busy, spinner) → success (checkmark) → idle` on resolve; `loading → error (X mark) → idle` on reject. Duration controlled by `asyncFeedbackDuration` (1500ms default). Auto-activates `processing='working'` during loading — marching-ants border keeps users visually aware.
42
+ - **Processing vs loading:** `loading` is a short async state (shows spinner, blocks clicks). `processing` is a longer-running state (marching ants border, may or may not block clicks based on `processingDisabled`). Use onClickAsync for simple request cases; use processing explicitly for long-running background operations.
43
+ - **DevalokGrain children:** Grain elements are auto-extracted and rendered as direct button children for absolute positioning — lets you layer grain texture on solid-variant buttons without breaking layout.
44
+ - **Prefer `variant="soft"` over `variant="outline"` for secondary actions** (see Gotchas for details). This is a design-system-wide convention.
45
+
37
46
  ## Gotchas
38
47
  - **Prefer `variant="soft"` over `variant="outline"` for secondary actions.** Soft (tinted step-3 bg, step-11 text) is the Devalok-recommended default — it feels warmer and brand-consistent. Use `outline` only when soft's tint would disappear (on colored/surface-raised bg), in toolbar/icon-dense contexts, or when you need outline's stronger hierarchy next to a primary action.
39
48
  - DO NOT use variant="destructive" — use variant="solid" color="error"
@@ -6,18 +6,22 @@
6
6
 
7
7
  ## Props
8
8
  variant: "default" | "elevated" | "outline" | "flat"
9
+ color: "default" | "accent" | "error" | "success" | "warning" | "info" | "neutral" (border accent color)
10
+ size: "sm" | "md" | "lg" (padding — propagated to CardHeader/CardContent/CardFooter via context)
9
11
  interactive: boolean (enables hover shadow lift + pointer cursor)
12
+ accent: "left" | "top" | "right" | "bottom" (render a colored accent bar on the specified edge)
13
+ accentColor: "default" | "accent" | "error" | "success" | "warning" | "info" (color of the accent bar; default maps to accent-9)
10
14
 
11
15
  ## Compound Components
12
16
  Card (root)
13
- CardHeader
17
+ CardHeader ← inherits size from Card context
14
18
  CardTitle
15
19
  CardDescription
16
- CardContent
17
- CardFooter
20
+ CardContent ← inherits size from Card context
21
+ CardFooter ← inherits size from Card context
18
22
 
19
23
  ## Defaults
20
- variant="default"
24
+ variant="default", color="default", size="md"
21
25
 
22
26
  ## Example
23
27
  ```jsx
@@ -30,8 +34,16 @@
30
34
  </Card>
31
35
  ```
32
36
 
37
+ ## Composability
38
+ - **Size cascades through context** — Card's `size` prop sets padding on CardHeader, CardContent, and CardFooter via `CardSizeContext`. Don't set padding classes on sub-components directly; override via `className` if needed.
39
+ - **Not a compound state machine** — Card, CardHeader, CardTitle, etc. are purely structural. No open/close state.
40
+ - **Accent bar is independent:** The `accent` / `accentColor` props render a decorative colored edge bar (absolutely positioned, `aria-hidden`). Works alongside `color` (which tints the border) — the two can stack for layered emphasis.
41
+ - **Interactive cards:** Set `interactive={true}` + `onClick` for clickable cards (entire surface becomes the button). Add `aria-label` on the Card root when there's no visible heading. For complex multi-action cards, prefer standard Card with explicit buttons inside.
42
+ - **ContentCard (composed)** is a higher-level wrapper with built-in header/footer slots and title/actions — use it for list-row-style cards; use Card directly for custom layouts.
43
+
33
44
  ## Gotchas
34
45
  - Use `interactive` prop for clickable cards — adds hover lift and pointer cursor
46
+ - Don't override CardHeader/CardContent/CardFooter padding via className if you want the size cascade to work — set size on Card instead
35
47
 
36
48
  ## Changes
37
49
  ### v0.31.0
@@ -28,9 +28,18 @@ import { BarChart } from '@devalok/shilp-sutra/ui/charts'
28
28
  <BarChart data={salesData} />
29
29
  ```
30
30
 
31
+ ## Composability
32
+ - **ChartContainer wraps all chart primitives** — provides responsive sizing (fills parent width, configurable aspect ratio) and a consistent background/padding. Always render charts inside a ChartContainer unless you're building a custom container.
33
+ - **Legend is a sibling, not a child** — render `<Legend items={...} />` next to your chart (above/below), not nested. Use `onHover` / `onClick` handlers in Legend + chart to sync hover highlights.
34
+ - **Data shape is chart-specific** — BarChart wants `[{ label, value }]`, LineChart wants `[{ x, y, series }]`, etc. Each chart's props doc spells out the shape; don't assume cross-chart compatibility.
35
+ - **Sparkline is the only chart safe to use inline** — it's a minimal 1-line SVG, no axes, no legend. Use it inside StatCard, tables, tight cells. For anything larger, reach for the full chart types.
36
+ - **Entrance animations** — all charts use Framer Motion spring-based entry. Animations respect `prefers-reduced-motion`.
37
+ - **Accessibility:** Charts render role="img" with aria-label from the chart title. For dense data, provide a hidden accessible table fallback manually (WCAG requirement for complex charts).
38
+
31
39
  ## Gotchas
32
40
  - Barrel-isolated since v0.5.0 — must use `@devalok/shilp-sutra/ui/charts`, NOT the `ui` barrel
33
41
  - Requires D3 as an optional peer dependency
42
+ - Chart components do NOT auto-generate accessible data tables — add one manually for WCAG compliance on complex charts
34
43
 
35
44
  ## Changes
36
45
  ### v0.18.0
@@ -199,6 +199,15 @@ Animated bouncing dots with a text description of who is typing.
199
199
 
200
200
  ---
201
201
 
202
+ ## Composability
203
+ - **Chat is a kit of 7 primitives, not a monolithic ChatWindow.** Compose MessageList (scroll container) + Message/SystemMessage (row variants) + DateSeparator/UnreadSeparator (visual dividers) + MessageInput (composer) + TypingIndicator (presence).
204
+ - **Message is a compound component** — `Message.Avatar`, `Message.Content`, `Message.Author`, `Message.Body`, `Message.Reactions`, `Message.Actions`, `Message.Action`. Arrange them to match your design; most layouts only need Avatar + Content (with Author + Body inside).
205
+ - **Message.Actions reveal on hover** via `group-hover/message` — ONLY works when Actions is nested inside a Message root. If you want a custom action bar, do it differently.
206
+ - **MessageList ARIA:** `role="log" + aria-live="polite"` — screen readers announce new messages automatically. Don't manually add aria-live to children.
207
+ - **grouped mode:** For consecutive messages from the same user, set `grouped={true}` to hide the avatar and author (typical chat UI pattern). Logic for "same user as previous" is consumer-side.
208
+ - **MessageInput is specifically for chat** — auto-resizing textarea (up to 160px), Enter-to-send, Shift+Enter-for-newline, streaming support. For richer composition (formatting, attachments), use RichChatInput from composed (built on top of this + TipTap).
209
+ - **TypingIndicator** accepts multiple users — handles pluralization of the "is/are typing" label automatically.
210
+
202
211
  ## Gotchas
203
212
  - MessageList uses `role="log"` with `aria-live="polite"` — screen readers announce new messages
204
213
  - Message entrance animations use Framer Motion springs — AnimatePresence wraps children in MessageList
@@ -7,20 +7,34 @@
7
7
  ## Props
8
8
  checked: boolean | "indeterminate"
9
9
  onCheckedChange: (checked: boolean | "indeterminate") => void
10
+ size: "sm" | "md" | "lg"
10
11
  error: boolean (shows red border)
11
12
  indeterminate: boolean (overrides checked, shows dash icon)
12
13
  disabled: boolean
13
14
 
14
15
  ## Defaults
15
- none
16
+ size="md"
16
17
 
17
18
  ## Example
18
19
  ```jsx
19
20
  <Checkbox checked={agreed} onCheckedChange={(v) => setAgreed(v === true)} />
21
+
22
+ {/* With label */}
23
+ <label htmlFor="terms" className="flex items-center gap-ds-02">
24
+ <Checkbox id="terms" checked={agreed} onCheckedChange={(v) => setAgreed(v === true)} />
25
+ I agree to the terms
26
+ </label>
20
27
  ```
21
28
 
29
+ ## Composability
30
+ - **FormField integration:** Unlike Input/Textarea, Checkbox does NOT auto-consume FormField state — the `error` prop must be set explicitly. (Form-library convention differs for checkboxes since they often don't share the same visual grouping.)
31
+ - **Labels:** Checkbox doesn't carry its own label — pair it with `<Label htmlFor="x" />` + `<Checkbox id="x" />`, or wrap both in a `<label>` element. Screen readers rely on that association.
32
+ - **Controlled vs uncontrolled:** Pass `checked` + `onCheckedChange` for controlled, `defaultChecked` for uncontrolled. Don't mix.
33
+
22
34
  ## Gotchas
23
35
  - indeterminate overrides checked visually
36
+ - Checkbox does NOT auto-consume FormField error state — pass `error` explicitly if using inside a FormField
37
+ - WCAG 2.5.8 minimum target size met at md (24px); sm (20px) may fail if not paired with enough label hit area
24
38
 
25
39
  ## Changes
26
40
  ### v0.22.0
@@ -26,11 +26,20 @@
26
26
  ## Additional Exports
27
27
  ChipGroup — re-exported AnimatePresence from framer-motion; wrap a list of Chips for coordinated exit animations
28
28
 
29
+ ## Composability
30
+ - **DEPRECATED — use Badge instead.** Chip v0.29.0+ superset capability merged into Badge. Chip remains in the library for backward compatibility; new code should use:
31
+ - `<Badge>...</Badge>` for static tags (replaces `<Chip label="..." />`)
32
+ - `<Badge onClick={...} selected={isSelected}>...</Badge>` for interactive filter chips
33
+ - `<Badge onDismiss={...}>...</Badge>` for dismissible chips
34
+ - **Why deprecated:** Chip used `label` prop (not children) and had a separate CVA; Badge now supports everything Chip did via children + onClick/onDismiss/selected, with more colors and variants.
35
+ - **Migration:** `<Chip label="React" color="info" />` → `<Badge variant="subtle" color="info">React</Badge>`. ChipGroup → BadgeGroup or just AnimatePresence wrapper.
36
+
29
37
  ## Gotchas
30
38
  - MUST use label prop — children are NOT rendered
31
39
  - `<Chip>text</Chip>` is WRONG — use `<Chip label="text" />`
32
40
  - Wrap dynamic chip lists in `<ChipGroup>` for exit animations
33
41
  - `color="primary"` will be renamed to `color="brand"` in v1.0 — use `color="primary"` for now
42
+ - **Deprecated — migrate to Badge for all new code**
34
43
 
35
44
  ## Changes
36
45
  ### v0.19.1
@@ -17,8 +17,15 @@
17
17
  <Code variant="block">{`const x = 1;\nconsole.log(x);`}</Code>
18
18
  ```
19
19
 
20
+ ## Composability
21
+ - **Server-safe.** No context, no cascade.
22
+ - Inline variant composes inside Text, paragraphs, list items, table cells — wherever body text goes.
23
+ - Block variant is a `<pre><code>` pair with overflow-x scroll; pair with a dark background Card or MarkdownViewer for long snippets.
24
+ - Not a syntax-highlighted code viewer — for that, use RichTextEditor's code block or a dedicated library. Code here is typographic emphasis, not lexer-aware rendering.
25
+
20
26
  ## Gotchas
21
27
  - "block" renders as `<pre><code>`, "inline" renders as `<code>`
28
+ - Block variant doesn't syntax-highlight — it's a styled pre tag, nothing more
22
29
 
23
30
  ## Changes
24
31
  ### v0.1.1
@@ -25,8 +25,17 @@
25
25
  </Collapsible>
26
26
  ```
27
27
 
28
+ ## Composability
29
+ - Radix Collapsible primitive — smaller than Accordion (no grouping/value matching, just open/close).
30
+ - **When to use:** A single show/hide section where the state is local (e.g. "Advanced options" inside a form, expandable row detail, show-more). For multi-section with mutual exclusion, use Accordion.
31
+ - **Inline rendering:** CollapsibleContent is NOT portalled — it expands inline and pushes sibling content down. Respects `overflow` rules of the parent.
32
+ - **No built-in chevron** — unlike AccordionTrigger, CollapsibleTrigger is bare. Add your own icon + rotate via `data-state` attribute Radix sets (`[data-state=open]:rotate-180`).
33
+ - **Animation:** Height transition via `animate-collapsible-down/up` keyframes + Radix `--radix-collapsible-content-height` CSS custom property.
34
+
28
35
  ## Gotchas
29
36
  - Standard Radix Collapsible API
37
+ - No auto-chevron — add one manually if you want the affordance
38
+ - Not portalled — clipped by parent `overflow: hidden`
30
39
 
31
40
  ## Changes
32
41
  ### v0.13.0
@@ -61,6 +61,14 @@
61
61
  <ColorInput value={color} onChange={setColor} showPicker={false} />
62
62
  ```
63
63
 
64
+ ## Composability
65
+ - **Built on Popover internally** — trigger opens a portal-rendered picker panel. z-popover (1400) stacking.
66
+ - **variant="default" vs "inline":** Default variant is an input-style trigger with gradient swatch + hex label (fits in forms). Inline variant IS the selected color — the entire trigger takes the color as its background with contrast-aware text. Use inline for color-tags in chat, lists, or tight toolbar UIs.
67
+ - **Controlled or uncontrolled:** Works both ways — pass `value` + `onChange` for controlled, or omit and let internal state track.
68
+ - **Presets are compositional:** Pass `{ hex, label }[]` for named brand colors (accessible, keyboard-navigable). Pass `false` to hide the preset strip entirely (picker-only mode).
69
+ - **react-colorful** is bundled (2.8KB gzipped) — zero additional setup. Picker itself is pointer-based; keyboard users edit via the HEX/RGB/HSL format inputs below.
70
+ - **FormField:** Not auto-consumed (no `state` prop). Wrap in FormField for label + helper text; style error visuals via className.
71
+
64
72
  ## Gotchas
65
73
  - Value must be a 6-character hex string (e.g. "#d33163")
66
74
  - Presets accept both `string[]` (backward-compatible) and `{ hex, label }[]` (recommended for accessibility)
@@ -20,6 +20,13 @@
20
20
  <ColorSwatch color="oklch(0.7 0.15 200)" shape="rounded" />
21
21
  ```
22
22
 
23
+ ## Composability
24
+ - **Server-safe, decorative-only primitive.** Accepts any valid CSS color string — hex, rgb, oklch, hsl, named. Pure presentation, no context.
25
+ - **Usage patterns:** Inline color marker next to a label, preview dot in lists, color-indicator in a category chip, legend swatch for charts.
26
+ - **`ring={true}`** adds a subtle outline — essential for white/very-light colors that would otherwise disappear on surface-raised backgrounds.
27
+ - **Interactive color picking:** Use ColorInput (which opens a full picker). ColorSwatch is display-only.
28
+ - No IconProvider cascade, no FormField consumption — composes freely with anything.
29
+
23
30
  ## Gotchas
24
31
  - Color is applied via inline `backgroundColor` style, not a token class — accepts any runtime CSS color string
25
32
  - Renders `role="presentation"` — purely decorative, not interactive
@@ -6,6 +6,7 @@
6
6
 
7
7
  ## Props
8
8
  options: ComboboxOption[] (REQUIRED) — { value: string, label: string, description?: string, icon?: ReactNode, disabled?: boolean }
9
+ size: "xs" | "sm" | "md" | "lg" (trigger height)
9
10
  DISCRIMINATED UNION — type depends on `multiple` flag:
10
11
  Single (default): multiple?: false, value?: string, onValueChange: (value: string) => void
11
12
  Multiple: multiple: true, value?: string[], onValueChange: (value: string[]) => void
@@ -13,13 +14,14 @@
13
14
  searchPlaceholder: string (default: "Search...")
14
15
  emptyMessage: string (default: "No results found")
15
16
  disabled: boolean
16
- triggerClassName: string
17
+ className: string (wrapper div — the positioning container, NOT the trigger)
18
+ triggerClassName: string (the actual Popover trigger button)
17
19
  accessibleLabel: string (custom aria-label for trigger, falls back to placeholder)
18
20
  maxVisible: number (default: 6, max dropdown items before scroll)
19
21
  renderOption: (option, selected) => ReactNode
20
22
 
21
23
  ## Defaults
22
- placeholder="Select...", searchPlaceholder="Search...", emptyMessage="No results found", maxVisible=6
24
+ size="md", placeholder="Select...", searchPlaceholder="Search...", emptyMessage="No results found", maxVisible=6
23
25
 
24
26
  ## Example
25
27
  ```jsx
@@ -32,9 +34,20 @@
32
34
  />
33
35
  ```
34
36
 
37
+ ## Composability
38
+ - **Combobox vs Autocomplete vs Select:** Combobox = typeahead search + forced selection (the user picks from filtered options). Autocomplete = typeahead + free text allowed. Select = no typeahead, click-to-open with fixed options. Pick by user behavior, not visual style.
39
+ - **Single vs multi mode** is a discriminated union — `multiple: true` changes the shape of value (`string[]`) and onValueChange. TypeScript enforces the pairing.
40
+ - **Multi-select pills:** Capped at 2 visible + "+N more" overflow regardless of `maxVisible`. Clicking the +N opens a popover list of all selected items (handled by the component).
41
+ - **className vs triggerClassName:** className lands on the wrapper (positioning); triggerClassName lands on the trigger button (styling the control). Know which you need.
42
+ - **Portal + z-popover (1400):** content stacks above Dialog/Sheet; works inside scrolling containers without clipping.
43
+ - **renderOption:** For complex option rendering (avatar + label + description), pass `renderOption: (option, selected) => <YourCustom />`. The selected state is a boolean flag.
44
+ - **FormField:** Does NOT auto-consume FormField state. Wrap in FormField for label + helper text, but style error manually.
45
+
35
46
  ## Gotchas
36
47
  - Enforces selection from list (unlike Autocomplete which allows free text)
37
- - In multi mode, selected items appear as pills with "+N more" overflow
48
+ - In multi mode, selected items appear as pills with "+N more" overflow (capped at 2 visible pills regardless of `maxVisible`)
49
+ - `className` vs `triggerClassName`: `className` lands on the wrapper div (useful for width/positioning); `triggerClassName` lands on the actual Popover trigger button (useful for styling the control itself)
50
+ - Dropdown content is portalled to document.body — parent styles like `overflow: hidden` don't clip it, and container-scoped test queries won't find it
38
51
 
39
52
  ## Changes
40
53
  ### v0.18.0
@@ -18,8 +18,19 @@
18
18
  </Container>
19
19
  ```
20
20
 
21
+ ## Composability
22
+ - **Server-safe layout primitive.** Safe in RSC trees — no hooks, no context.
23
+ - **maxWidth choices:**
24
+ - `"default"` (standard page container — matches the design system's layout grid)
25
+ - `"body"` (narrower reading width — use for article/blog content)
26
+ - `"full"` (no max — edge-to-edge, useful for full-bleed marketing sections)
27
+ - **Polymorphic via `as`:** Change the rendered element for semantics (`as="main"` for page content, `as="section"` for major subdivisions, `as="article"` for standalone content).
28
+ - **mx-auto centering is automatic** — Container handles horizontal centering; it does NOT add vertical spacing. Pair with `py-*` utility classes on the Container itself or inside it.
29
+ - Nothing cascades — nesting Containers is fine but rarely useful (max-width constraints compound).
30
+
21
31
  ## Gotchas
22
32
  - Server-safe component — can be imported directly in Next.js Server Components
33
+ - Container does NOT add vertical padding — add it explicitly via className if needed
23
34
 
24
35
  ## Changes
25
36
  ### v0.1.0
@@ -29,8 +29,16 @@
29
29
  </ContextMenu>
30
30
  ```
31
31
 
32
+ ## Composability
33
+ - Radix ContextMenu — same item-variant surface as DropdownMenu (checkbox, radio group, sub-menus, label, separator) with the same keyboard model. Swap `Dropdown` → `Context` in imports and everything behaves the same.
34
+ - **Trigger is not a button** — ContextMenuTrigger is a wrapper element (defaults to `<div>`) that listens for `contextmenu` events on itself and children. Use `asChild` to pass styling to your own element.
35
+ - **Touch support:** Radix maps long-press to right-click. On mobile, hold-to-open just works without extra code.
36
+ - **No visible trigger affordance** — unlike DropdownMenu/Popover, the user has to *know* the element is right-clickable. Pair with a visible hint (keyboard shortcut label, menu icon elsewhere) for discoverability.
37
+ - **Portal + z-index:** z-popover (1400).
38
+
32
39
  ## Gotchas
33
40
  - Triggered by right-click (or long-press on touch devices)
41
+ - ContextMenuTrigger doesn't auto-indicate it's interactive — add visual affordance elsewhere on the page
34
42
 
35
43
  ## Changes
36
44
  ### v0.18.0
@@ -7,6 +7,10 @@
7
7
  This component is used internally by `<DataTable>` and should not be imported directly.
8
8
  See [DataTable](./data-table.md) for the public API.
9
9
 
10
+ ## Composability
11
+ - **Internal only.** Renders the tbody + rows for DataTable. Consumes DataTableContext for row data, selection state, expandable state, loading skeletons.
12
+ - Customization routes through DataTable props: `renderExpanded`, `onRowClick`, `getRowId`, `loading`, `emptyState`.
13
+
10
14
  ## Changes
11
15
 
12
16
  ### v0.32.0
@@ -7,6 +7,10 @@
7
7
  This component is used internally by `<DataTable>` and should not be imported directly.
8
8
  See [DataTable](./data-table.md) for the public API.
9
9
 
10
+ ## Composability
11
+ - **Internal only.** Renders the floating bulk-action bar that appears when rows are selected. Reads selection state + bulkActions config from DataTableContext.
12
+ - Customization goes through DataTable's `bulkActions` prop — `{ label, onClick, color?, disabled? }[]`.
13
+
10
14
  ## Changes
11
15
 
12
16
  ### v0.32.0
@@ -7,6 +7,10 @@
7
7
  This component is used internally by `<DataTable>` and should not be imported directly.
8
8
  See [DataTable](./data-table.md) for the public API.
9
9
 
10
+ ## Composability
11
+ - **Internal only.** Renders the card wrapper around DataTable (border, shadow, rounded corners matching the Card primitive's surface treatment).
12
+ - DataTable uses this automatically — no consumer-facing prop toggles it.
13
+
10
14
  ## Changes
11
15
 
12
16
  ### v0.32.0
@@ -7,6 +7,10 @@
7
7
  This component is used internally by `<DataTable>` and should not be imported directly.
8
8
  See [DataTable](./data-table.md) for the public API.
9
9
 
10
+ ## Composability
11
+ - **Internal React context** — binds DataTable's sub-components (header, body, pagination, toolbar, bulk-actions) to shared state: TanStack table instance, density, selection, loading.
12
+ - Consumer code never reads this context — all interaction goes through DataTable's props.
13
+
10
14
  ## Changes
11
15
 
12
16
  ### v0.32.0
@@ -7,6 +7,10 @@
7
7
  This component is used internally by `<DataTable>` and should not be imported directly.
8
8
  See [DataTable](./data-table.md) for the public API.
9
9
 
10
+ ## Composability
11
+ - **Internal only.** Renders the thead + column headers for DataTable. Reads sort state + column definitions from DataTableContext.
12
+ - Customization goes through DataTable's `columns` prop (column definitions are TanStack ColumnDef<TData> — header, sortable, filter, etc. all declared there).
13
+
10
14
  ## Changes
11
15
 
12
16
  ### v0.32.0
@@ -7,6 +7,10 @@
7
7
  This component is used internally by `<DataTable>` and should not be imported directly.
8
8
  See [DataTable](./data-table.md) for the public API.
9
9
 
10
+ ## Composability
11
+ - **Internal only.** Renders the pagination footer for DataTable. Reads pagination state from DataTableContext.
12
+ - Customization goes through DataTable's `pagination` prop (server-side: pass `{ page, pageSize, total, onPageChange }`) or `pageSize` + `paginated` (client-side).
13
+
10
14
  ## Changes
11
15
 
12
16
  ### v0.32.0
@@ -31,10 +31,17 @@ import { DataTableToolbar } from '@devalok/shilp-sutra/ui/data-table-toolbar'
31
31
  />
32
32
  ```
33
33
 
34
+ ## Composability
35
+ - **Companion to DataTable** — typically enabled via DataTable's `toolbar={true}` prop (which auto-renders this internally). Use DataTableToolbar directly only if you're building a custom DataTable integration that needs the toolbar positioned/styled differently.
36
+ - **Reads table state via the `table` instance** (TanStack react-table) — you must pass it. For the auto-rendered version inside DataTable, this wiring is automatic.
37
+ - **Feature toggles:** `globalFilter` (search across all columns), density switcher (compact/standard/comfortable row heights), CSV export. Turn each on/off independently.
38
+ - **Density changes are runtime** — the switcher writes to the same `defaultDensity` that DataTable's prop seeds. State lives in DataTable context.
39
+
34
40
  ## Gotchas
35
41
  - Barrel-isolated since v0.5.0 — must use `@devalok/shilp-sutra/ui/data-table-toolbar`, NOT the `ui` barrel
36
42
  - Companion to DataTable — provides column visibility, density toggle, and CSV export controls
37
43
  - Requires @tanstack/react-table as a peer dependency
44
+ - Prefer DataTable's `toolbar={true}` prop over rendering this directly
38
45
 
39
46
  ## Changes
40
47
  ### v0.5.0
@@ -55,6 +55,30 @@ import { DataTable } from '@devalok/shilp-sutra/ui/data-table'
55
55
  />
56
56
  ```
57
57
 
58
+ ## Composability
59
+ **Server vs client mode is prop-driven, not explicit.**
60
+ - Pass `onSort` → server-side sort (manual, rows stay in data order — you're responsible for re-fetching).
61
+ - Pass `pagination` object → server-side pagination (manual, pass total count).
62
+ - Omit both → client-side sort/pagination via TanStack react-table.
63
+ - Mix-and-match: `onSort` + no pagination = server sort + client pagination.
64
+
65
+ **Companion components:**
66
+ - `DataTableToolbar` — enabled via `toolbar={true}`. Provides column visibility, density switcher, CSV export. Reads table state via `DataTableContext` (internal). Rendered ABOVE the table automatically.
67
+ - `BulkActionBar` (floating) — appears when rows are selected AND `bulkActions` array is non-empty. Synced with `selectedIds`; shows count + action buttons.
68
+ - `EmptyState` from `@devalok/shilp-sutra/composed` — pass to `emptyState` prop. Takes precedence over `noResultsText` string.
69
+
70
+ **Controlled selection:**
71
+ - Pass `selectedIds` (Set<string>) + `onSelectionChange` for controlled row selection.
72
+ - Provide `getRowId: (row) => row.id` so selection survives data refetches (otherwise TanStack uses array index, which breaks on sort/filter).
73
+ - `selectableFilter: (row) => boolean` disables selection on specific rows (e.g. archived items).
74
+
75
+ **Row click model:**
76
+ - `onRowClick` fires on row-level click BUT excludes clicks on checkboxes, buttons, links, and inputs automatically. No manual `stopPropagation` needed for standard interactive elements.
77
+
78
+ **Virtualization:** `virtualRows={true}` enables row virtualization via `@tanstack/react-virtual`. Turn it on for 1000+ row datasets; the scroll container must have a bounded height.
79
+
80
+ **Density integration:** `defaultDensity="compact"` is the Karm-style dense mode (h-9 rows). DataTableToolbar's density switcher updates this at runtime; the prop sets the initial state only.
81
+
58
82
  ## Gotchas
59
83
  - Barrel-isolated since v0.5.0 — must use `@devalok/shilp-sutra/ui/data-table`, NOT the `ui` barrel
60
84
  - Requires @tanstack/react-table and @tanstack/react-virtual as peer dependencies
@@ -63,6 +87,7 @@ import { DataTable } from '@devalok/shilp-sutra/ui/data-table'
63
87
  - selectedIds syncs via useEffect — provide getRowId for custom row IDs
64
88
  - onRowClick does NOT fire when clicking checkboxes, buttons, links, or inputs
65
89
  - Use defaultDensity="compact" for Karm-style h-9 rows
90
+ - `virtualRows={true}` requires a bounded scroll container — unbounded height silently disables virtualization
66
91
 
67
92
  ## Changes
68
93
  ### v0.29.0
@@ -41,6 +41,14 @@
41
41
  </div>
42
42
  ```
43
43
 
44
+ ## Composability
45
+ - **Brand texture overlay** — drops into any parent with `relative overflow-hidden isolate`. Auto-inherits the parent's border radius.
46
+ - **Button auto-extracts Grain children** — if you nest `<DevalokGrain>` inside a `<Button>`, Button auto-separates it from the label slot and positions it correctly. No extra wrapping needed.
47
+ - **Card, custom hero sections, landing-page tiles** — wrap in a positioned container (or add the `relative overflow-hidden isolate` classes to Card explicitly) and drop Grain in as a sibling to your content.
48
+ - **z-layer contract:** Grain renders at `z-[1]`. Content on top needs `z-[2]+`. The component doesn't boost child z-index — that's consumer responsibility.
49
+ - **`hoverIntensify` depends on parent** — the parent must have the Tailwind `group` class so `group-hover:` selectors apply. Forgetting this silently disables the effect.
50
+ - **Tint composes with surface:** Pass an OKLCH color (or CSS variable reference like `var(--color-accent-9)`) to `tint` for a directional gradient on top of the noise. Without tint, just noise.
51
+
44
52
  ## Gotchas
45
53
  - Parent element MUST have `relative overflow-hidden isolate` for the grain to render correctly
46
54
  - The grain layers are absolute-positioned at `z-[1]` — content that should appear above must use `z-[2]` or higher
@@ -35,8 +35,18 @@
35
35
  </Dialog>
36
36
  ```
37
37
 
38
+ ## Composability
39
+ - Built on Radix Dialog primitives — every standard Radix prop passes through (`open`, `onOpenChange`, `defaultOpen`, `modal`).
40
+ - **Trigger composition:** `<DialogTrigger asChild>` renders the wrapped element as the trigger — typical pattern is `<DialogTrigger asChild><Button>...</Button></DialogTrigger>`. Without `asChild`, a default button is injected.
41
+ - **Portal rendering:** DialogContent portals to `document.body`. CSS containers (`overflow: hidden`, `transform`, stacking contexts) on ancestors of the trigger don't clip it. Container-scoped test queries miss portalled content — use `screen` or the portal root.
42
+ - **Focus management:** Radix traps focus inside Content while open and restores it to the trigger on close. First focusable element inside receives focus automatically.
43
+ - **Imperative close** from deep children: use `<DialogClose asChild>` around your own button, or `useDialogContext` — no prop drilling.
44
+ - **z-index:** DialogContent uses `z-dialog` (configured in Tailwind theme). Nested overlays (Popover inside Dialog) stack on top because Popover uses `z-popover` (1400) higher than `z-dialog`.
45
+
38
46
  ## Gotchas
39
47
  - DialogTitle is required for accessibility — screen readers announce it when the dialog opens
48
+ - If your layout suppresses the title visually, use `<VisuallyHidden>` around DialogTitle — don't omit it
49
+ - Focus returns to the trigger on close — if the trigger is conditionally unmounted, focus lands on body; handle manually if that matters
40
50
 
41
51
  ## Changes
42
52
  ### v0.19.1
@@ -31,8 +31,18 @@
31
31
  </DropdownMenu>
32
32
  ```
33
33
 
34
+ ## Composability
35
+ - Radix DropdownMenu underneath — same prop surface (`open`, `onOpenChange`, `defaultOpen`, `modal`).
36
+ - **Item variants** stack cleanly: `DropdownMenuCheckboxItem` for multi-select toggles, `DropdownMenuRadioGroup` + `DropdownMenuRadioItem` for single-select, `DropdownMenuSub` + `DropdownMenuSubTrigger/SubContent` for nested submenus. Each has its own keyboard model pre-wired.
37
+ - **Keyboard:** Arrow keys navigate items, Enter/Space activates, Esc closes, typeahead jumps to first letter. All handled by Radix — don't re-implement.
38
+ - **Trigger:** `<DropdownMenuTrigger asChild>` around any button. IconButton is the common pairing.
39
+ - **Shortcut hints:** `<DropdownMenuShortcut>` inside an item renders a right-aligned `⌘K`-style kbd. Visual only — does NOT bind the shortcut globally.
40
+ - **Closing from a handler:** Item onSelect auto-closes the menu by default. Pass `event.preventDefault()` inside the handler to keep it open (e.g. for checkbox items that shouldn't close on toggle).
41
+
34
42
  ## Gotchas
35
43
  - Use `asChild` on DropdownMenuTrigger to render your own button element
44
+ - DropdownMenuShortcut is decorative — bind keyboard shortcuts separately (e.g. with `useHotkeys`)
45
+ - Sub-menus need BOTH DropdownMenuSubTrigger (visible item) and DropdownMenuSubContent (the submenu panel) — missing either silently breaks the hover-open behavior
36
46
 
37
47
  ## Changes
38
48
  ### v0.22.0