@sableui/react 0.1.1 → 0.3.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 (137) hide show
  1. package/AGENTS.md +3 -1
  2. package/CHANGELOG.md +120 -0
  3. package/CONTRIBUTING.md +8 -0
  4. package/README.md +1 -0
  5. package/bin/sable-lint.mjs +0 -0
  6. package/bin/sable.mjs +68 -26
  7. package/dist/{_contract-CYScOdf5.d.ts → _contract-o3tUc8Ig.d.ts} +1 -1
  8. package/dist/blocks/index.d.ts +396 -0
  9. package/dist/blocks/index.js +1997 -0
  10. package/dist/chunk-6LCIBMMG.js +4908 -0
  11. package/dist/{chunk-J23ELQQZ.js → chunk-CKNM7KSB.js} +1664 -4270
  12. package/dist/chunk-K6LII3OE.js +5093 -0
  13. package/dist/{chunk-RWP4XBSK.js → chunk-KFZ43RKU.js} +1 -1
  14. package/dist/{chunk-PAOZBZME.js → chunk-NGBDP3BM.js} +1 -1
  15. package/dist/{chunk-5Z5KLOAU.js → chunk-TH563WLW.js} +14 -1
  16. package/dist/{chunk-JI7WJP2L.js → chunk-X276MF2W.js} +5 -0
  17. package/dist/foundations/index.js +1 -1
  18. package/dist/icon/index.js +2 -2
  19. package/dist/icon-sets/reicon.js +1 -1
  20. package/dist/index.d.ts +624 -133
  21. package/dist/index.js +767 -4964
  22. package/dist/ios/index.d.ts +14 -2
  23. package/dist/ios/index.js +10 -9
  24. package/dist/ios/preview.d.ts +11 -1
  25. package/dist/ios/preview.js +14 -9
  26. package/dist/ios/styles.css +26 -6
  27. package/dist/{meta-6dUzgC6M.d.ts → meta-CcE_SeFu.d.ts} +37 -1
  28. package/dist/patterns/index.d.ts +1 -1
  29. package/dist/patterns/index.js +1684 -471
  30. package/dist/presets/index.d.ts +1 -1
  31. package/dist/presets/index.js +1 -1
  32. package/dist/styles.css +238 -40
  33. package/dist/{theme-provider-BWcizhAt.d.ts → theme-provider-BE-KqUG-.d.ts} +1 -1
  34. package/dist/themes/atelier/atelier.css +3 -0
  35. package/dist/themes/atelier/index.d.ts +1 -1
  36. package/dist/themes/base/base.css +34 -4
  37. package/dist/themes/base/index.d.ts +1 -1
  38. package/dist/themes/ember/ember.css +3 -0
  39. package/dist/themes/ember/index.d.ts +1 -1
  40. package/dist/themes/index.d.ts +2 -2
  41. package/dist/themes/index.js +2 -2
  42. package/dist/themes/meridian/index.d.ts +1 -1
  43. package/dist/themes/meridian/meridian.css +10 -5
  44. package/dist/themes/poppy/index.d.ts +1 -1
  45. package/dist/themes/poppy/poppy.css +16 -0
  46. package/dist/themes/prism/index.d.ts +1 -1
  47. package/dist/themes/prism/prism.css +4 -1
  48. package/dist/themes/verdant/index.d.ts +1 -1
  49. package/dist/themes/verdant/verdant.css +7 -2
  50. package/llms/blocks/api-keys-block.md +79 -0
  51. package/llms/blocks/chat-thread-block.md +51 -0
  52. package/llms/blocks/command-palette-block.md +48 -0
  53. package/llms/blocks/cta-band-block.md +62 -0
  54. package/llms/blocks/danger-zone-block.md +69 -0
  55. package/llms/blocks/device-comparison-block.md +91 -0
  56. package/llms/blocks/event-card-block.md +84 -0
  57. package/llms/blocks/faq-block.md +72 -0
  58. package/llms/blocks/feature-grid-block.md +75 -0
  59. package/llms/blocks/feedback-form-block.md +66 -0
  60. package/llms/blocks/goal-progress-block.md +54 -0
  61. package/llms/blocks/hero-block.md +65 -0
  62. package/llms/blocks/invite-team-block.md +61 -0
  63. package/llms/blocks/invoice-history-block.md +84 -0
  64. package/llms/blocks/kpi-sparkline-block.md +57 -0
  65. package/llms/blocks/media-player-block.md +100 -0
  66. package/llms/blocks/newsletter-block.md +64 -0
  67. package/llms/blocks/not-found-block.md +56 -0
  68. package/llms/blocks/notification-list-block.md +63 -0
  69. package/llms/blocks/notification-settings-block.md +54 -0
  70. package/llms/blocks/order-tracking-block.md +62 -0
  71. package/llms/blocks/pricing-card-block.md +82 -0
  72. package/llms/blocks/pricing-table-block.md +129 -0
  73. package/llms/blocks/product-card-block.md +69 -0
  74. package/llms/blocks/profile-card-block.md +103 -0
  75. package/llms/blocks/profile-form-block.md +114 -0
  76. package/llms/blocks/revenue-breakdown-block.md +70 -0
  77. package/llms/blocks/sign-in-block.md +64 -0
  78. package/llms/blocks/sign-up-block.md +78 -0
  79. package/llms/blocks/site-footer-block.md +96 -0
  80. package/llms/blocks/stat-row-block.md +59 -0
  81. package/llms/blocks/steps-block.md +73 -0
  82. package/llms/blocks/task-list-block.md +81 -0
  83. package/llms/blocks/team-members-block.md +91 -0
  84. package/llms/blocks/testimonial-block.md +78 -0
  85. package/llms/blocks/traffic-sources-block.md +63 -0
  86. package/llms/blocks/traffic-trend-block.md +95 -0
  87. package/llms/blocks/upload-list-block.md +66 -0
  88. package/llms/blocks/verify-code-block.md +59 -0
  89. package/llms/blocks/wallet-block.md +84 -0
  90. package/llms/components/area-chart.md +112 -0
  91. package/llms/components/bar-chart.md +131 -0
  92. package/llms/components/bubble.md +5 -5
  93. package/llms/components/button.md +1 -1
  94. package/llms/components/card.md +2 -2
  95. package/llms/components/checkbox-card.md +1 -1
  96. package/llms/components/context-card.md +2 -2
  97. package/llms/components/context-menu.md +1 -1
  98. package/llms/components/date-tile.md +79 -0
  99. package/llms/components/dialog.md +1 -1
  100. package/llms/components/empty-state.md +4 -4
  101. package/llms/components/entity.md +5 -3
  102. package/llms/components/fieldset.md +1 -1
  103. package/llms/components/kbd.md +18 -6
  104. package/llms/components/line-chart.md +101 -0
  105. package/llms/components/menu.md +1 -1
  106. package/llms/components/{dots-menu.md → overflow-menu.md} +9 -9
  107. package/llms/components/pie-chart.md +103 -0
  108. package/llms/components/pill.md +1 -1
  109. package/llms/components/radar-chart.md +106 -0
  110. package/llms/components/radial-chart.md +102 -0
  111. package/llms/components/segmented-bar.md +102 -0
  112. package/llms/components/settings-menu.md +2 -2
  113. package/llms/components/side-nav.md +1 -1
  114. package/llms/components/skeleton.md +1 -1
  115. package/llms/components/sparkline.md +80 -0
  116. package/llms/components/table.md +1 -1
  117. package/llms/components/theme-selector.md +1 -1
  118. package/llms/foundations/shape.md +1 -1
  119. package/llms/patterns/auth-page.md +132 -42
  120. package/llms/patterns/brand-landing-page.md +271 -175
  121. package/llms/patterns/chat-panel.md +120 -39
  122. package/llms/patterns/dashboard-page.md +276 -65
  123. package/llms/patterns/feed-page.md +245 -64
  124. package/llms/patterns/landing-page.md +473 -75
  125. package/llms/patterns/list-detail-page.md +377 -68
  126. package/llms/patterns/settings-page.md +268 -67
  127. package/llms/reference/token-intent.md +4 -2
  128. package/llms/reference/utilities.md +20 -5
  129. package/llms/tasks/building-an-ios-prototype.md +4 -1
  130. package/llms/tasks/choosing-a-component.md +2 -2
  131. package/llms-full.txt +6428 -892
  132. package/llms.txt +67 -16
  133. package/package.json +5 -1
  134. package/registry.json +3217 -552
  135. package/targets/ios.json +88 -18
  136. package/llms/components/description.md +0 -96
  137. package/dist/{chunk-L6Y46C62.js → chunk-MXJX3NZI.js} +1 -1
@@ -0,0 +1,84 @@
1
+ # WalletBlock
2
+
3
+ A balance card: the balance in the metric mono tier, Send and Receive as an equal pair, and recent activity as Entity rows with signed amounts.
4
+
5
+ **Intent:** Reach for this for a wallet, account balance or credits summary with its latest movements. For a full transaction history with filters, use a DataTable.
6
+
7
+ **Block:** Commerce · card · designed for 18rem–20rem wide (fills its container up to the max)
8
+
9
+ ## Notes
10
+
11
+ - The balance is CardTitle at `text-mono-xl` — tabular figures, never a heading tier — under its label as CardDescription.
12
+ - Send is primary and Receive secondary, side by side at equal width: two directions of one action, with sending the riskier and more frequent.
13
+ - Incoming amounts are `text-success`; outgoing stay muted rather than red, because a withdrawal is not an error.
14
+
15
+ ## Accessibility
16
+
17
+ - Each activity row's leading swatch is aria-hidden; the title and detail carry the meaning.
18
+ - Amounts keep their sign in text (+0.80 / -1.50), so the direction does not depend on colour.
19
+
20
+ ## Source
21
+
22
+ ```tsx
23
+ import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, cn, Entity, Icon } from "@sableui/react"
24
+
25
+ const balance = "14.62 SOL"
26
+
27
+ const activity = [
28
+ { title: "Sold “Dusk, no. 4”", detail: "Edition · 2h ago", amount: "+0.80", tone: "edition" },
29
+ { title: "Royalty", detail: "Collectible · Yesterday", amount: "+0.12", tone: "collectible" },
30
+ { title: "Withdrawal", detail: "To Phantom · Mon", amount: "-1.50", tone: "standard" },
31
+ ]
32
+
33
+ export interface WalletBlockProps {
34
+ className?: string
35
+ /** false drops the card's surface and border — for a Sheet or muted section that already frames it. */
36
+ framed?: boolean
37
+ onSend?: () => void
38
+ onReceive?: () => void
39
+ }
40
+
41
+ export function WalletBlock({ className, framed = true, onSend, onReceive }: WalletBlockProps) {
42
+ return (
43
+ <Card variant={framed ? "default" : "flat"} className={cn("w-full max-w-xs", className)}>
44
+ <CardHeader>
45
+ <CardDescription>Balance</CardDescription>
46
+ <CardTitle className="text-mono-xl">{balance}</CardTitle>
47
+ </CardHeader>
48
+ <CardContent className="flex flex-col gap-3">
49
+ <div className="grid grid-cols-2 gap-2">
50
+ <Button iconStart={<Icon name="arrow-up" />} onClick={onSend}>
51
+ Send
52
+ </Button>
53
+ <Button variant="secondary" iconStart={<Icon name="arrow-down" />} onClick={onReceive}>
54
+ Receive
55
+ </Button>
56
+ </div>
57
+ <div className="flex flex-col">
58
+ {activity.map((item) => (
59
+ <Entity
60
+ key={item.title}
61
+ className="px-0"
62
+ leading={
63
+ <span
64
+ aria-hidden
65
+ className="block size-8 rounded-full"
66
+ // design-lint-disable-next-line inline-color -- relative colour derived from the theme's own --tone token, so it follows every look
67
+ style={{ background: `oklch(from var(--tone-${item.tone}) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h)` }}
68
+ />
69
+ }
70
+ title={item.title}
71
+ subtitle={item.detail}
72
+ action={
73
+ <span className={cn("text-mono-md", item.amount.startsWith("+") ? "text-success" : "text-muted-foreground")}>
74
+ {item.amount}
75
+ </span>
76
+ }
77
+ />
78
+ ))}
79
+ </div>
80
+ </CardContent>
81
+ </Card>
82
+ )
83
+ }
84
+ ```
@@ -0,0 +1,112 @@
1
+ # AreaChart
2
+
3
+ **Status:** beta
4
+
5
+ A trend over a sequence, filled to the baseline: overlapping translucent series, stacked, or stacked to 100%, with smooth, linear or step curves and optional gradient fills. The line draws on and the fill rises as it scrolls into view.
6
+
7
+ **Keywords:** chart, area, trend, time series, stacked, graph, analytics, dashboard, visitors, revenue
8
+
9
+ ## Usage
10
+
11
+ Use AreaChart for volume over time — visitors, revenue, usage — where the filled shape helps the eye read the size of the total. Stack the series to show a total and its parts; `stacked="expand"` shows each category as 100%. For comparing how measures move relative to each other, LineChart is cleaner; for amounts per category, BarChart.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Keep to one to three series; past that the tints muddy — stack them or switch to LineChart.
16
+ - Do: Set a height on className (h-72, aspect-video h-auto); the chart fills the box it is given.
17
+ - Don't: Overlap more than two unstacked series — the translucent tints blend into a colour no series has.
18
+ - Don't: Use it for data-heavy views — streaming or real-time data, thousands of points, zoom and brushing, trading charts. Those belong on a charting library (Recharts, visx, ECharts); Sable's charts are for dashboards, landing pages and decks.
19
+
20
+ ## Props
21
+
22
+ | Name | Type | Default | Description |
23
+ | --- | --- | --- | --- |
24
+ | data | Record<string, unknown>[] | — | The rows, in order along the category axis. |
25
+ | index | string | — | The datum field naming each row's category (a month, a day, a channel). |
26
+ | series | { key: string; label: string; tone?: ChartTone; color?: string; icon?: string }[] | — | The measures to plot: the datum field, its name in the legend/tooltip/table, and its colour (a tone by role, or any CSS colour). Omitted, series take the chart palette in order: --chart-1 … --chart-8, validated for colour-blind separation in light and dark; a theme may override the set. |
27
+ | label | string | — | What the chart shows. Its accessible name and the caption of the data table assistive tech reads. |
28
+ | className | string | "h-60" | Sizes the chart (plot + legend). Set a height: `h-72`, `aspect-video h-auto`. |
29
+ | valueFormatter | (value: number) => string | — | Format values everywhere (axis, labels, tooltip, table) — units like "$12,480". Default: compact on the axis, full elsewhere. |
30
+ | indexFormatter | (value: string) => string | — | Shorten category labels on the axis ("January" → "Jan"); the tooltip keeps the full value. |
31
+ | locale | string | runtime locale | BCP 47 locale for default number and date formatting. |
32
+ | tooltip | boolean \| { indicator?: "dot" \| "line" \| "dashed" \| "none"; hideLabel?: boolean; labelFormatter?: (label, datum) => ReactNode; showTotal?: boolean } | true | Show a tooltip on hover and keyboard focus, or configure it: the row marker, hiding the heading, rewriting it, adding a Total row. |
33
+ | legend | boolean | true when there is more than one series | Show the legend under the chart. |
34
+ | grid | boolean | true (false for horizontal bars) | Hairline gridlines along the value axis. |
35
+ | xAxis | boolean | true for columns; false for horizontal bars | The horizontal axis. |
36
+ | yAxis | boolean | false for columns; true for horizontal bars | The vertical axis. |
37
+ | animate | boolean | true | Play the arrival once the chart scrolls into view. Reduced motion shows the finished chart. |
38
+ | activeIndex | number \| null | — | The highlighted row (controlled); `null` for none. |
39
+ | defaultActiveIndex | number \| null | — | The row highlighted at first — a tooltip already open before anyone hovers. |
40
+ | onActiveIndexChange | (index: number \| null) => void | — | Called as the pointer or keyboard moves between rows. |
41
+ | curve | "smooth" \| "linear" \| "step" | "smooth" | How the line runs between points. `smooth` is monotone: it never overshoots a value the data does not contain. |
42
+ | stacked | boolean \| "expand" | false | Stack the series, or `"expand"` to show each category as 100%. |
43
+ | gradient | boolean | false | Fill each area with a fade from its colour to transparent instead of a flat tint. |
44
+
45
+ ## Tokens used
46
+
47
+ `tone-edition`, `tone-collectible`, `tone-standard`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
48
+
49
+ ## Motion
50
+
51
+ - **arrival** · Area + line · _custom (outside the recipe layer)_ — Once on screen, each line draws on (stroke-dashoffset) and its fill rises from the baseline, series staggered — the trend is read as it is drawn. On the motion tokens; reduced motion shows the finished chart.
52
+ - **response** · Cursor + tooltip · _no motion_ — Hover and focus move the cursor and tooltip instantly — a lagging cursor reads as a slow chart.
53
+ - **transition** · Area + line · _custom (outside the recipe layer)_ — Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place.
54
+ - **activity** · AreaChart · _no motion_ — A chart has no indeterminate state; show a Skeleton while the data loads.
55
+
56
+ ## Accessibility
57
+
58
+ - The drawing is aria-hidden; the same numbers are in a visually hidden data table (captioned by `label`) that the chart is aria-describedby, so assistive tech reads values, not shapes.
59
+ - With a tooltip, the chart is one tab stop (role=group, aria-roledescription="chart"): arrow keys, Home and End step through the data, Escape clears, and each step is announced in a polite live region.
60
+ - Colour is never the only key: series are named in the legend, tooltip and table.
61
+
62
+ ## Related
63
+
64
+ - Components: LineChart, BarChart, Sparkline, Card
65
+
66
+ ## Examples
67
+
68
+ ### Two series with a legend
69
+
70
+ ```tsx
71
+ import { AreaChart } from "@sableui/react"
72
+
73
+ const visitors = [
74
+ { month: "January", desktop: 186, mobile: 80 },
75
+ { month: "February", desktop: 305, mobile: 200 },
76
+ { month: "March", desktop: 237, mobile: 120 },
77
+ { month: "April", desktop: 73, mobile: 190 },
78
+ { month: "May", desktop: 209, mobile: 130 },
79
+ { month: "June", desktop: 214, mobile: 140 },
80
+ ]
81
+
82
+ <AreaChart
83
+ label="Visitors, January to June"
84
+ data={visitors}
85
+ index="month"
86
+ series={[
87
+ { key: "desktop", label: "Desktop" },
88
+ { key: "mobile", label: "Mobile" },
89
+ ]}
90
+ indexFormatter={(m) => m.slice(0, 3)}
91
+ gradient
92
+ />
93
+ ```
94
+
95
+ ### Stacked to 100%
96
+
97
+ ```tsx
98
+ import { AreaChart } from "@sableui/react"
99
+
100
+ <AreaChart
101
+ label="Share of visitors by device"
102
+ data={visitors}
103
+ index="month"
104
+ series={[
105
+ { key: "desktop", label: "Desktop" },
106
+ { key: "mobile", label: "Mobile" },
107
+ ]}
108
+ stacked="expand"
109
+ yAxis
110
+ tooltip={{ indicator: "line", showTotal: true }}
111
+ />
112
+ ```
@@ -0,0 +1,131 @@
1
+ # BarChart
2
+
3
+ **Status:** beta
4
+
5
+ Bars comparing amounts across categories: grouped, stacked or stacked to 100%, vertical columns or horizontal bars, with value labels, per-category colours and a separate tone for negative values. Bars rise from the baseline in sequence as the chart scrolls into view.
6
+
7
+ **Keywords:** chart, bar, column, histogram, compare, stacked, grouped, horizontal, analytics, dashboard
8
+
9
+ ## Usage
10
+
11
+ Use BarChart to compare amounts across categories — the default chart for "how much of each". Go horizontal (`orientation="horizontal"`) when category names are long or there are many of them; group series side by side to compare them, stack them to show a total. For parts of one whole, a stacked bar or PieChart; for a trend, AreaChart or LineChart.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Sort categories by value unless they have a natural order (months, steps).
16
+ - Do: Use negativeTone for diverging data (profit and loss), so a loss reads apart from a gain.
17
+ - Don't: Group more than three series side by side — the bars get too thin to compare; stack them or split the chart.
18
+ - Don't: Use it for data-heavy views — streaming or real-time data, thousands of points, zoom and brushing, trading charts. Those belong on a charting library (Recharts, visx, ECharts); Sable's charts are for dashboards, landing pages and decks.
19
+
20
+ ## Props
21
+
22
+ | Name | Type | Default | Description |
23
+ | --- | --- | --- | --- |
24
+ | data | Record<string, unknown>[] | — | The rows, in order along the category axis. |
25
+ | index | string | — | The datum field naming each row's category (a month, a day, a channel). |
26
+ | series | { key: string; label: string; tone?: ChartTone; color?: string; icon?: string }[] | — | The measures to plot: the datum field, its name in the legend/tooltip/table, and its colour (a tone by role, or any CSS colour). Omitted, series take the chart palette in order: --chart-1 … --chart-8, validated for colour-blind separation in light and dark; a theme may override the set. |
27
+ | label | string | — | What the chart shows. Its accessible name and the caption of the data table assistive tech reads. |
28
+ | className | string | "h-60" | Sizes the chart (plot + legend). Set a height: `h-72`, `aspect-video h-auto`. |
29
+ | valueFormatter | (value: number) => string | — | Format values everywhere (axis, labels, tooltip, table) — units like "$12,480". Default: compact on the axis, full elsewhere. |
30
+ | indexFormatter | (value: string) => string | — | Shorten category labels on the axis ("January" → "Jan"); the tooltip keeps the full value. |
31
+ | locale | string | runtime locale | BCP 47 locale for default number and date formatting. |
32
+ | tooltip | boolean \| { indicator?: "dot" \| "line" \| "dashed" \| "none"; hideLabel?: boolean; labelFormatter?: (label, datum) => ReactNode; showTotal?: boolean } | true | Show a tooltip on hover and keyboard focus, or configure it: the row marker, hiding the heading, rewriting it, adding a Total row. |
33
+ | legend | boolean | true when there is more than one series | Show the legend under the chart. |
34
+ | grid | boolean | true (false for horizontal bars) | Hairline gridlines along the value axis. |
35
+ | xAxis | boolean | true for columns; false for horizontal bars | The horizontal axis. |
36
+ | yAxis | boolean | false for columns; true for horizontal bars | The vertical axis. |
37
+ | animate | boolean | true | Play the arrival once the chart scrolls into view. Reduced motion shows the finished chart. |
38
+ | activeIndex | number \| null | — | The highlighted row (controlled); `null` for none. |
39
+ | defaultActiveIndex | number \| null | — | The row highlighted at first — a tooltip already open before anyone hovers. |
40
+ | onActiveIndexChange | (index: number \| null) => void | — | Called as the pointer or keyboard moves between rows. |
41
+ | orientation | "vertical" \| "horizontal" | "vertical" | `vertical` columns rise from the bottom; `horizontal` bars run from the left, for long category names. |
42
+ | stacked | boolean \| "expand" | false | Stack the series in one bar, or `"expand"` to show each category as 100%. Unstacked series sit side by side. |
43
+ | labels | boolean \| "inside" | false | Print each bar's value at its end, or (`"inside"`, horizontal) the category name inside the bar as well. |
44
+ | colorBy | "series" \| "index" | "series" | Colour by series, or give each category its own tone (one series). |
45
+ | negativeTone | "edition" \| "collectible" \| "standard" \| "info" \| "warning" \| "primary" \| "neutral" \| "success" \| "destructive" | — | Colour negative values with this tone. |
46
+ | radius | number | 4 | Corner radius of each bar's far end, in px. Stacks round only the outer segment. |
47
+ | barWidth | number | 24 | Maximum thickness of a bar, in px: the band's leftover is air, not a fatter bar. Raise it for a bolder presentation look. |
48
+
49
+ ## Tokens used
50
+
51
+ `tone-edition`, `tone-collectible`, `tone-standard`, `tone-info`, `tone-warning`, `primary`, `muted-foreground`, `border`, `popover`, `popover-foreground`, `foreground`, `background`
52
+
53
+ ## Motion
54
+
55
+ - **arrival** · Bar · _custom (outside the recipe layer)_ — Once on screen, bars rise from the baseline in sequence on the emphasized easing (scale from the zero line) and their labels fade in. On the motion tokens; reduced motion shows the finished chart.
56
+ - **response** · Cursor + tooltip · _no motion_ — The hovered category gets a band behind it and the other bars dim at once — a lagging highlight reads as a slow chart.
57
+ - **transition** · Bar · _custom (outside the recipe layer)_ — Geometry eases from the old shape to the new one through a CSS `d` transition on the motion tokens (no recipe animates path geometry); browsers without `d` transitions update in place.
58
+ - **activity** · BarChart · _no motion_ — A chart has no indeterminate state; show a Skeleton while the data loads.
59
+
60
+ ## Accessibility
61
+
62
+ - The drawing is aria-hidden; the same numbers are in a visually hidden data table (captioned by `label`) that the chart is aria-describedby, so assistive tech reads values, not shapes.
63
+ - With a tooltip, the chart is one tab stop (role=group, aria-roledescription="chart"): arrow keys, Home and End step through the data, Escape clears, and each step is announced in a polite live region.
64
+ - Colour is never the only key: series are named in the legend, tooltip and table.
65
+
66
+ ## Related
67
+
68
+ - Components: AreaChart, PieChart, SegmentedBar, Card
69
+
70
+ ## Examples
71
+
72
+ ### Grouped series
73
+
74
+ ```tsx
75
+ import { BarChart } from "@sableui/react"
76
+
77
+ <BarChart
78
+ label="Visitors by device"
79
+ data={visitors}
80
+ index="month"
81
+ series={[
82
+ { key: "desktop", label: "Desktop" },
83
+ { key: "mobile", label: "Mobile" },
84
+ ]}
85
+ indexFormatter={(m) => m.slice(0, 3)}
86
+ />
87
+ ```
88
+
89
+ ### Horizontal, labelled inside
90
+
91
+ ```tsx
92
+ import { BarChart } from "@sableui/react"
93
+
94
+ const browsers = [
95
+ { browser: "Chrome", visitors: 275 },
96
+ { browser: "Safari", visitors: 200 },
97
+ { browser: "Firefox", visitors: 187 },
98
+ { browser: "Edge", visitors: 173 },
99
+ ]
100
+
101
+ <BarChart
102
+ label="Visitors by browser"
103
+ data={browsers}
104
+ index="browser"
105
+ series={[{ key: "visitors", label: "Visitors" }]}
106
+ orientation="horizontal"
107
+ labels="inside"
108
+ yAxis={false}
109
+ grid={false}
110
+ />
111
+ ```
112
+
113
+ ### Profit and loss
114
+
115
+ ```tsx
116
+ import { BarChart } from "@sableui/react"
117
+
118
+ <BarChart
119
+ label="Net revenue by month"
120
+ data={[
121
+ { month: "Jan", net: 186 },
122
+ { month: "Feb", net: -95 },
123
+ { month: "Mar", net: 137 },
124
+ { month: "Apr", net: -60 },
125
+ ]}
126
+ index="month"
127
+ series={[{ key: "net", label: "Net", tone: "success" }]}
128
+ negativeTone="destructive"
129
+ labels
130
+ />
131
+ ```
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Status:** stable
4
4
 
5
- The surface a message's content sits on: four treatments, an align-aware tail corner, a readable measure cap, and an optional row of reaction pills.
5
+ The surface a message's content sits on: four treatments, an align-aware tail, a readable measure cap, and an optional row of reaction pills.
6
6
 
7
7
  **Keywords:** chat bubble, message bubble, speech bubble, reactions, conversation, messaging, ai chat
8
8
 
@@ -14,7 +14,7 @@ The tinted (or untinted) surface a turn's content sits on. Kept separate from Me
14
14
 
15
15
  - Do: Use `muted` for the other party and `primary` for the reader's own messages. Two treatments is the whole vocabulary a conversation needs to say who is speaking.
16
16
  - Do: Reach for `variant="plain"` for long-form assistant output — rendered markdown, code blocks, tables. A tinted box around three paragraphs reads as a quote block rather than as a turn, and `plain` drops the measure cap with the surface.
17
- - Do: Pass the same `align` you gave the enclosing Message, so the flattened corner lands on the speaker's side.
17
+ - Do: Pass the same `align` you gave the enclosing Message, so the tail lands on the speaker's side.
18
18
  - Do: Set `tail={false}` on the continuation bubbles in a grouped run — the tail marks where a speaker starts, and repeating it on every line makes a block of one person's messages look like four separate turns.
19
19
  - Do: Let the register size it. Inside `<Density value="compact">` a Bubble sets itself in the workspace prose tier automatically — a docked agent panel needs no per-call-site sizing, and hardcoding one defeats the mechanism.
20
20
  - Don't: Do not remove the measure cap to fill a wide window. ~42rem is a reading measure; a bubble spanning a 1600px viewport is a paragraph at 140 characters per line.
@@ -26,7 +26,7 @@ The tinted (or untinted) surface a turn's content sits on. Kept separate from Me
26
26
 
27
27
  | Part | Required | Description |
28
28
  | --- | --- | --- |
29
- | Bubble | yes | The surface itself. Holds the variant, the side, and the flattened tail corner. |
29
+ | Bubble | yes | The surface itself. Holds the variant, the side, and the tail. |
30
30
  | BubbleReactions | no | The row of reaction pills, just under the bubble at the message column's small gap, so it reads as belonging to that message rather than the next row. |
31
31
  | BubbleReaction | no | One reaction pill — emoji, count (hidden at 1), and pressed state. |
32
32
 
@@ -42,8 +42,8 @@ The tinted (or untinted) surface a turn's content sits on. Kept separate from Me
42
42
  | Name | Type | Default | Description |
43
43
  | --- | --- | --- | --- |
44
44
  | variant | "muted" \| "primary" \| "outline" \| "plain" | "muted" | Surface treatment. `muted` for the other party, `primary` for the reader's own messages, `outline` for a quieter own-side, `plain` for long-form output that should not sit in a box (also drops the measure cap and padding). |
45
- | align | "start" \| "end" | "start" | Which side of the conversation the bubble is on — flattens the corner nearest the speaker. Match it to the enclosing Message. |
46
- | tail | boolean | true | Flatten the speaker-side corner. A flattened corner rather than a drawn arrow, so it holds up in every theme's radius language (flat through fully pilled) with no per-theme geometry. Turn it off for the continuation bubbles in a grouped run. |
45
+ | align | "start" \| "end" | "start" | Which side of the conversation the bubble is on — puts the tail on the speaker's side. Match it to the enclosing Message. |
46
+ | tail | boolean | true | Draw the tail: a small ear off the speaker-side top corner, which goes square to meet it. The same 5px in every theme's radius language, flat through fully pilled. The bubble keeps the ear's space either way, so turning it off for the continuation bubbles in a grouped run keeps the run aligned. |
47
47
  | ...div props | ComponentPropsWithRef<"div"> | — | Spread onto the surface element. |
48
48
 
49
49
  ## Variants
@@ -72,7 +72,7 @@ Use Button when the user commits an action, submits a form, opens UI, or confirm
72
72
 
73
73
  ## Related
74
74
 
75
- - Components: Menu, DotsMenu, Icon, Banner
75
+ - Components: Menu, OverflowMenu, Icon, Banner
76
76
  - Patterns: SettingsPage, AuthPage, DashboardPage
77
77
 
78
78
  ## Examples
@@ -34,9 +34,9 @@ Use Card for a discrete thing with its own boundary, such as one product, one no
34
34
  | Name | Type | Description |
35
35
  | --- | --- | --- |
36
36
  | CardHeader | ComponentPropsWithRef<"div"> | Top section holding CardTitle/CardDescription (and optionally CardAction). Padding follows the density knob. |
37
- | CardTitle | ComponentPropsWithRef<"div"> | The card's name (text-title-lg). |
37
+ | CardTitle | ComponentPropsWithRef<"div"> & { render?: useRender.RenderProp } | The card's name (text-title-lg). A div by default; pass `render={<h2 />}` (or h3…) to make it a heading in the page outline, at the level the card's position calls for. |
38
38
  | CardDescription | ComponentPropsWithRef<"div"> | Muted supporting line under the title. |
39
- | CardAction | ComponentPropsWithRef<"div"> | Header-aligned top-right action slot (e.g. a dots menu, switch, or close button). Place inside CardHeader, which becomes a 2-column grid when present and collapses to a single column otherwise. |
39
+ | CardAction | ComponentPropsWithRef<"div"> | Header-aligned top-right action slot (e.g. an overflow menu, switch, or close button). Place inside CardHeader, which becomes a 2-column grid when present and collapses to a single column otherwise. |
40
40
  | CardContent | ComponentPropsWithRef<"div"> & { flush?: boolean } | The body region. Padding follows the density knob on all sides — does not assume a preceding CardHeader, so it is safe to use standalone (e.g. no CardHeader in the card). When paired with CardHeader, the seam stays tight because CardHeader zeroes its own bottom padding. `flush` runs it edge to edge for a table: the Table insets its first and last columns by exactly the card padding, so it lines up with the header — never hand-set pl-6/pr-6 on columns. |
41
41
  | CardList | ComponentPropsWithRef<"div"> | Rows that run edge to edge in the card (Entity rows, most often), divided by hairlines. Insets each row to the card's own padding, so a list card lines up with every other card at any density — no hand-tuned px-* on the rows. Under a CardHeader it opens with a hairline and a gap; alone it fills the card. |
42
42
  | CardFooter | ComponentPropsWithRef<"div"> | Bottom row for actions/meta. Padding follows the density knob. |
@@ -39,7 +39,7 @@ Use CheckboxCard when an option needs more visual weight, supporting text, or ic
39
39
  | onCheckedChange | (checked: boolean, eventDetails) => void | — | Fires with the next checked state (Base UI passes eventDetails second). Pair with `checked` for a controlled card; omit both to let it manage its own state. |
40
40
  | title | ReactNode | — | The card's primary label (text-title-sm). |
41
41
  | description | ReactNode | — | Optional supporting copy beneath the title (text-body-sm, muted). |
42
- | icon | ReactNode | — | Optional leading glyph (a solid <Icon>); takes the tone colour. |
42
+ | icon | ReactNode | — | Optional glyph (a solid <Icon>) in the tone colour. Its presence switches to the two-dimensional card RadioCard uses — icon top-left, checkbox top-right, copy below — so the two cards match in one form. |
43
43
  | tone | "neutral" \| "standard" \| "collectible" \| "edition" \| "info" \| "warning" | "neutral" | Semantic colour the checked state carries across the border, checkbox box, and icon. |
44
44
  | value | string | — | Names the card inside a group-owned CheckboxGroup: the card is checked when the group's value array includes it. Also the value submitted with a form. Inside a CheckboxGroup, `name` stands in for `value` when `value` is omitted. |
45
45
  | checked / onCheckedChange / defaultChecked / disabled | Base UI Checkbox.Root props | — | Standard checkbox state. Without a group-owned value, each card is independent — drive checked/onCheckedChange per item for multi-select. |
@@ -67,7 +67,7 @@ import { Avatar, ContextCard, ContextCardContent, ContextCardTrigger, Entity } f
67
67
  ### With stat row
68
68
 
69
69
  ```tsx
70
- import { Avatar, ContextCard, ContextCardContent, ContextCardTrigger, Description, DescriptionItem, Entity } from "@sableui/react"
70
+ import { Avatar, ContextCard, ContextCardContent, ContextCardTrigger, Entity } from "@sableui/react"
71
71
 
72
- <ContextCard><ContextCardTrigger render={<a href='/u/nyx' className='text-title-sm underline-offset-2 hover:underline'>@nyx</a>} /><ContextCardContent><Entity className='[--entity-px:0] [--entity-py:0]' leading={<Avatar fallback='NY' size='lg' />} title='Nyx' subtitle='@nyx' /><Description cols='2' className='mt-4 border-t border-border pt-3'><DescriptionItem term='Posts' detail='128' /><DescriptionItem term='Followers' detail='4.2k' /></Description></ContextCardContent></ContextCard>
72
+ <ContextCard><ContextCardTrigger render={<a href='/u/nyx' className='text-title-sm underline-offset-2 hover:underline'>@nyx</a>} /><ContextCardContent><Entity className='[--entity-px:0] [--entity-py:0]' leading={<Avatar fallback='NY' size='lg' />} title='Nyx' subtitle='@nyx' /><div className='@container mt-4 border-t border-border pt-3'><dl className='sable-list-dl sable-list-dl-2'><dt>Posts</dt><dd>128</dd><dt>Followers</dt><dd>4.2k</dd></dl></div></ContextCardContent></ContextCard>
73
73
  ```
@@ -58,7 +58,7 @@ Use ContextMenu for actions that are specific to the object or area the user inv
58
58
 
59
59
  ## Related
60
60
 
61
- - Components: Menu, DotsMenu
61
+ - Components: Menu, OverflowMenu
62
62
 
63
63
  ## Examples
64
64
 
@@ -0,0 +1,79 @@
1
+ # DateTile
2
+
3
+ **Status:** stable
4
+
5
+ A calendar-page date: the short month on a primary band over the day number, in a <time> element. The leading visual of an event, booking or deadline row.
6
+
7
+ **Keywords:** date, calendar, event, day, month, schedule, booking, tile
8
+
9
+ ## Usage
10
+
11
+ Use DateTile as the leading visual when the date is what a reader scans a list or card by — events, bookings, deadlines, releases. It sits beside a title and a time line, usually in an Entity's `leading` slot or a flex row in a CardContent. For a date inside a sentence or a row's metadata, use text or RelativeTime instead.
12
+
13
+ ### Best practices
14
+
15
+ - Do: Pair the tile with a title and the full time or range in text beside it; the tile only names the day.
16
+ - Do: Keep every row of a list on DateTile so the dates line up as one column.
17
+ - Don't: Use it for timestamps ("2 hours ago", "Oct 14, 10:02") — that is RelativeTime or plain metadata text.
18
+ - Don't: Stretch it to its neighbour's height or restyle the band per row; the tile is a fixed shape so a column of them reads as one calendar.
19
+
20
+ ### Anatomy
21
+
22
+ | Part | Required | Description |
23
+ | --- | --- | --- |
24
+ | Band | yes | The short month name on the primary colour. |
25
+ | Day | yes | The day of the month, on the muted surface. |
26
+
27
+ ## Props
28
+
29
+ | Name | Type | Default | Description |
30
+ | --- | --- | --- | --- |
31
+ | date | Date | — | The day to show, read in local time. Rendered as `<time dateTime="YYYY-MM-DD">`. |
32
+ | locale | string | runtime locale | BCP 47 locale for the month name. |
33
+
34
+ ## Tokens used
35
+
36
+ `muted`, `primary`, `primary-foreground`, `foreground`
37
+
38
+ ## Motion
39
+
40
+ - **arrival** · Tile · _no motion_ — A static label; it appears with whatever row or card holds it.
41
+ - **response** · Tile · _no motion_ — Not interactive.
42
+ - **transition** · Tile · _no motion_ — It has no states to move between.
43
+ - **activity** · Tile · _no motion_ — Nothing runs in the background.
44
+
45
+ ## Accessibility
46
+
47
+ - Renders a <time> with a machine-readable `dateTime`, so the day is announced as text ("Oct 14") and parsable as a date.
48
+ - The tile names only the day — state the weekday, time or range in the text beside it, where it is read in order.
49
+
50
+ ## Related
51
+
52
+ - Components: Entity, Card, RelativeTime, Calendar
53
+
54
+ ## Examples
55
+
56
+ ### Event row
57
+
58
+ ```tsx
59
+ import { DateTile, Row, Stack } from "@sableui/react"
60
+
61
+ <Row gap={4} align="start">
62
+ <DateTile date={new Date(2026, 9, 14)} />
63
+ <Stack gap={1}>
64
+ <span className="text-title-lg">Design review</span>
65
+ <span className="text-body-sm text-muted-foreground">Tuesday, 2:00 – 2:45 PM</span>
66
+ </Stack>
67
+ </Row>
68
+ ```
69
+
70
+ ### In an Entity list
71
+
72
+ ```tsx
73
+ import { CardList, DateTile, Entity } from "@sableui/react"
74
+
75
+ <CardList>
76
+ <Entity leading={<DateTile date={new Date(2026, 9, 14)} />} title="Design review" subtitle="Tuesday, 2:00 PM" />
77
+ <Entity leading={<DateTile date={new Date(2026, 9, 21)} />} title="Launch" subtitle="Tuesday, all day" />
78
+ </CardList>
79
+ ```
@@ -38,7 +38,7 @@ Use Dialog for focused decisions, short modal forms, confirmations, and content
38
38
  | Name | Type | Description |
39
39
  | --- | --- | --- |
40
40
  | DialogContent | Base UI Popup props + size?: "default" \| "fullscreen" + showCloseButton?: boolean + scroll?: boolean + className | Backdrop + popup; portals to <body>. Composes DialogOverlay by default. size="fullscreen" is the media viewer: a near-opaque scrim (bg-overlay/95), an edge-to-edge popup that centres its children with no card chrome, motion-fade instead of motion-pop, and a safe-area-aware floating close button (Button variant="floating"); at that size showCloseButton defaults to true and scroll to false, and the media fits itself with max-h-full max-w-full object-contain. At the default size, showCloseButton (defaults to false) renders a built-in close (X) button in the top-end corner, and the header leaves room for it (Icon name="xmark"). The popup caps at the viewport height less a 1rem margin and wraps its children in a scrolling region, so a tall dialog scrolls instead of overflowing a viewport whose own scroll is locked; pass scroll={false} when the dialog owns its internal layout (a pinned header over a scrolling body) and supplies its own min-h-0 scroll region. |
41
- | DialogOverlay | Base UI Backdrop props + size?: "default" \| "fullscreen" + className | The styleable scrim/backdrop — bg-overlay/50 by default, near-opaque bg-overlay/95 at size="fullscreen". Composed into DialogContent (at the matching size) by default; exported for manual styling. |
41
+ | DialogOverlay | Base UI Backdrop props + size?: "default" \| "fullscreen" + className | The styleable scrim/backdrop — bg-overlay/(--scrim-alpha) by default (50% light, 72% dark, so it dims a near-black page as visibly as a white one), near-opaque bg-overlay/95 at size="fullscreen". Composed into DialogContent (at the matching size) by default; exported for manual styling. |
42
42
  | DialogTrigger | Base UI Dialog.Trigger props (render) | Element that opens the dialog. Compose via render prop. |
43
43
  | DialogClose | Base UI Dialog.Close props (render) | Element that dismisses the dialog. Compose via render prop. |
44
44
  | DialogHeader | ComponentProps<"div"> | Top section holding DialogTitle/DialogDescription. |
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Status:** stable
4
4
 
5
- The centered empty / error block: optional icon disc or error code, title, description and an action. tone="destructive" makes it an announced error, size="lg" a page-level state, surface="card" a self-contained card.
5
+ The centered empty / error block: optional icon or error code, title, description and an action. tone="destructive" makes it an announced error, size="lg" a page-level state, surface="card" a self-contained card.
6
6
 
7
7
  **Keywords:** empty, zero state, blank slate, no results, placeholder, onboarding, no data
8
8
 
@@ -33,12 +33,12 @@ Use EmptyState when a content area has no items, no search results, or no first-
33
33
 
34
34
  | Name | Type | Default | Description |
35
35
  | --- | --- | --- | --- |
36
- | icon | ReactNode | — | Optional leading glyph — pass an `<Icon />`. Rendered in a soft muted disc. |
36
+ | icon | ReactNode | — | Optional glyph above the title — an `<Icon />` takes the muted ink at 3xl; an image or illustration renders as given. |
37
37
  | title | ReactNode | — | The headline. Short and direct. |
38
38
  | description | ReactNode | — | Optional supporting line beneath the title. |
39
39
  | action | ReactNode | — | Optional action below the copy — typically a `<Button />`. |
40
40
  | code | ReactNode | — | A large figure above the title, such as an error code (404). |
41
- | tone | "neutral" \| "destructive" | "neutral" | destructive is an error state: a destructive icon disc (a warning by default) and role="alert" so it is announced. |
41
+ | tone | "neutral" \| "destructive" | "neutral" | destructive is an error state: a destructive icon (a warning by default) and role="alert" so it is announced. |
42
42
  | size | "md" \| "lg" | "md" | lg is page-level: the title is an h2 and the block takes more room. md sits in a panel, list or card. |
43
43
  | surface | "none" \| "card" | "none" | card draws it as a Card, for an error or empty block standing on its own in a page. |
44
44
 
@@ -54,7 +54,7 @@ Use EmptyState when a content area has no items, no search results, or no first-
54
54
 
55
55
  ## Accessibility
56
56
 
57
- - Decorative icon discs should not carry meaning the title doesn't already convey.
57
+ - Decorative icons should not carry meaning the title doesn't already convey.
58
58
  - Pair with an actionable next step so the zero-state is a path forward, not a dead end.
59
59
 
60
60
  ## Related
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Status:** stable
4
4
 
5
- A list-row record: leading visual + title/subtitle column + trailing action, with truncating text. Presentational by default (no hover); `interactive` rows gain the hover highlight, focus ring, and keyboard activation. Compact/comfortable density.
5
+ A list-row record: leading visual + title/subtitle column + trailing action, with truncating text. Presentational by default (no hover); `interactive` rows gain the hover highlight, focus ring, and keyboard activation, and `selected` marks the current one. Compact/comfortable density.
6
6
 
7
7
  **Keywords:** entity, identity, object, avatar row
8
8
 
@@ -28,6 +28,7 @@ Use Entity to show a compact identity block for a person, organization, file, pr
28
28
  | action | ReactNode | — | Trailing slot — typically a ghost icon Button or a StatusDot. |
29
29
  | interactive | boolean | false | Make the whole row a target: the leading visual and text become a native <button> whose hit area stretches over the row (focus ring, Enter/Space). The action stays outside it. Requires onSelect. |
30
30
  | onSelect | () => void | — | Activation handler for an interactive row (fired on click and Enter/Space). Required when interactive. |
31
+ | selected | boolean | false | Interactive rows only: the current row of a selectable list (the record open in a list–detail view). Sets aria-current="true" on the row's button and paints the selected fill, a step off the surface the row sits on, which holds on hover. |
31
32
  | density | "compact" \| "comfortable" | "compact" | Row padding: compact (12px × 10px, the default) or comfortable (16px × 12px), on the spacing scale. The insets read --entity-px / --entity-py first, which CardList sets from the card's own padding so rows in a card line up with its header. |
32
33
 
33
34
  ## Variants
@@ -36,17 +37,18 @@ Use Entity to show a compact identity block for a person, organization, file, pr
36
37
 
37
38
  ## Tokens used
38
39
 
39
- `muted`, `foreground`, `muted-foreground`, `ring`
40
+ `accent`, `accent-foreground`, `foreground`, `muted-foreground`, `ring`
40
41
 
41
42
  ## Accessibility
42
43
 
43
44
  - Default rows are presentational layout; semantics come from the content you place in title/subtitle/action.
44
45
  - interactive rows render a real <button> around the leading visual and text, with its ::after stretched over the whole row — native focus, a focus-visible ring, and Enter/Space. The action slot sits outside that button and above the overlay, so a Button there stays valid HTML and keeps its own clicks.
45
46
  - Give any interactive action an accessible label (e.g. aria-label on a ghost icon Button).
47
+ - A selected row announces aria-current="true" on its button, so the current record is spoken, not only shown by its fill.
46
48
 
47
49
  ## Related
48
50
 
49
- - Components: Avatar, Badge, Description
51
+ - Components: Avatar, Badge
50
52
 
51
53
  ## Examples
52
54
 
@@ -56,7 +56,7 @@ Use Fieldset to group related controls under a shared label or explanation.
56
56
 
57
57
  ## Related
58
58
 
59
- - Components: Form, Label, Description
59
+ - Components: Form, Label
60
60
 
61
61
  ## Examples
62
62