@bubble-design-system/ui 1.0.1 → 1.2.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.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Every visual decision — tone, color, brand, gray family, radius, density, typo
18
18
  >
19
19
  ```
20
20
 
21
- - **21 components** — Button, Input, Textarea, Checkbox, Radio, Switch, Select, Badge, Avatar, Divider, Modal, Toast, Tooltip, Tabs, Alert, DropdownMenu, Skeleton, Card, StatusPill, Segmented (plus Container + Grid layout primitives). Each wraps an [`@base-ui/react`](https://base-ui.com/) primitive where one exists — accessible by construction, styled with a single shipped stylesheet.
21
+ - **25 components** — Button, Input, Textarea, Checkbox, Radio, Switch, Select, Badge, Avatar, Divider, Modal, Toast, Tooltip, Tabs, Alert, DropdownMenu, Skeleton, Card, StatusPill, Segmented, Popover, DataTable, CommandPalette, Chat (plus Container + Grid layout primitives). Each wraps an [`@base-ui/react`](https://base-ui.com/) primitive where one exists — accessible by construction, styled with a single shipped stylesheet.
22
22
  - **A 3-layer, multi-theme token system** spanning color (light/dark · 3 gray families · 6 brand palettes including teal), **3 tones** (vivid · pastel · soft — soft is the signature look), radius (4 scales), density (3 scales), typography (2 font pairs), layered shadows, and motion.
23
23
  - **Live theme switching** via seven `data-*` attributes on any ancestor. Every CSS rule reads `var(--…)` at use-site, so swapping `data-tone="vivid"` for `data-tone="soft"` reflows the UI without re-rendering or rebuilding.
24
24
  - **No build dependency in consumer apps.** One CSS import. No PostCSS, no Tailwind, no preprocessor required.
@@ -37,8 +37,38 @@ The five rules every decision in Bubble traces back to:
37
37
 
38
38
  ---
39
39
 
40
+ ## Common UI Patterns
41
+
42
+ Before writing custom CSS against `var(--color-bg-*)`, `var(--radius-*)`, or `var(--shadow-*)` to build a surface, pill, menu, or banner — check this table. There is very likely already a component for it.
43
+
44
+ | You're building... | Use | Not... |
45
+ | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
46
+ | A floating card / panel / content surface | [`Card`](#card) (`variant="elevated"` or `"muted"`) | a `<div>` styled with `--color-bg-secondary` + `--radius-lg` + `--shadow-md` |
47
+ | A chat message thread (bubbles, avatars, timestamps, read receipts, reactions, compose bar) | [`Chat`](#chat) — `ChatThread` / `ChatMessage` / `ChatCompose` | a custom flex layout styled from raw tokens, with hand-rolled grouping and scroll logic |
48
+ | A notification / toast / status banner | [`Toast`](#toast) + `useToast()` | a custom `toast-container` + `setTimeout` store |
49
+ | A status indicator (online, connected, error dot) | [`StatusPill`](#statuspill) + `StatusPill.Indicator` | a `::after` pseudo-element colored with `--color-bg-success-strong` |
50
+ | A small tag, count, or label pill | [`Badge`](#badge) | a custom pill `<div>` with `--radius-full` |
51
+ | A dropdown / context menu / select-from-list | [`DropdownMenu`](#dropdownmenu) | an absolutely-positioned custom `<div>` list |
52
+ | A command palette / "/" slash-command menu / ⌘K launcher | [`CommandPalette`](#commandpalette) + `useCommandPalette()` | a custom filtered dropdown with arrow-key handling |
53
+ | A popover anchored to a trigger (info panel, form, menu) | [`Popover`](#popover) | a custom absolutely-positioned `<div>` + manual outside-click handling |
54
+ | A hover tooltip | [`Tooltip`](#tooltip) | the native `title` attribute or a custom hover `<div>` |
55
+ | A loading placeholder | [`Skeleton`](#skeleton) | a custom `@keyframes pulse` `<div>` |
56
+ | A view switcher / segmented toggle | [`Segmented`](#segmented) | a custom button group with manual active-state CSS |
57
+ | A confirm dialog / modal | [`Modal`](#modal) | a custom backdrop + centered `<div>` |
58
+ | A responsive page section / 12-col grid | [`Container`](#container--grid) + [`Grid`](#container--grid) | custom `max-width` + flex/grid CSS |
59
+ | A tabbed interface | [`Tabs`](#tabs) | a custom button row + conditional render |
60
+ | A sortable / searchable / paginated table | [`DataTable`](#datatable) | a custom `<table>` + manual sort/filter/page state |
61
+ | A horizontal or vertical rule | [`Divider`](#divider) | `border-top: 1px solid var(--color-border-*)` |
62
+ | A user/avatar icon with fallback initials | [`Avatar`](#avatar) | a circular `<div>` + `<img>` with manual error handling |
63
+ | Form fields (text, multiline, checkbox, radio, switch, select) | [`Input`](#input) / [`Textarea`](#textarea) / [`Checkbox`](#checkbox) / [`Radio` / `RadioGroup`](#radio--radiogroup) / [`Switch`](#switch) / [`Select`](#select) | native elements styled from scratch |
64
+
65
+ **If nothing in this table fits**, that's a real gap — reach for tokens directly, and consider [opening an issue](https://github.com/mushroomgead/bubble-design-system/issues) so the pattern can become component #26.
66
+
67
+ ---
68
+
40
69
  ## Table of contents
41
70
 
71
+ - [Common UI Patterns](#common-ui-patterns)
42
72
  - [Tech stack](#tech-stack)
43
73
  - [Installation](#installation)
44
74
  - [Setup](#setup)
@@ -54,16 +84,16 @@ The five rules every decision in Bubble traces back to:
54
84
 
55
85
  ## Tech stack
56
86
 
57
- | Concern | Tool |
58
- |---|---|
59
- | Framework | React 19 (works with ≥ 18.2) |
60
- | Primitives | `@base-ui/react` ≥ 1.0 (the post-rename successor to `@base-ui-components/react`) |
61
- | Styling | Plain CSS — one shipped stylesheet, hand-authored per component |
62
- | Class composition | `clsx` (re-exported as `cn()`) |
63
- | Build tool | `tsup` (ESM + CJS + dual `.d.ts`) + a 50-line Node script for CSS bundling |
64
- | Language | TypeScript 6 |
65
- | Package manager | `pnpm@10.33.0` |
66
- | Node | ≥ 20 |
87
+ | Concern | Tool |
88
+ | ----------------- | --------------------------------------------------------------------------------- |
89
+ | Framework | React 19 (works with ≥ 18.2) |
90
+ | Primitives | `@base-ui/react` ≥ 1.0 (the post-rename successor to `@base-ui-components/react`) |
91
+ | Styling | Plain CSS — one shipped stylesheet, hand-authored per component |
92
+ | Class composition | `clsx` (re-exported as `cn()`) |
93
+ | Build tool | `tsup` (ESM + CJS + dual `.d.ts`) + a 50-line Node script for CSS bundling |
94
+ | Language | TypeScript 6 |
95
+ | Package manager | `pnpm@10.33.0` |
96
+ | Node | ≥ 20 |
67
97
 
68
98
  ---
69
99
 
@@ -86,11 +116,11 @@ Then install the peer dependencies your app must have:
86
116
  npm install react react-dom @base-ui/react
87
117
  ```
88
118
 
89
- | Peer dependency | Required version |
90
- |---|---|
91
- | `react` | ≥ 18.2 |
92
- | `react-dom` | ≥ 18.2 |
93
- | `@base-ui/react` | ≥ 1.0.0 |
119
+ | Peer dependency | Required version |
120
+ | ---------------- | ---------------- |
121
+ | `react` | ≥ 18.2 |
122
+ | `react-dom` | ≥ 18.2 |
123
+ | `@base-ui/react` | ≥ 1.0.0 |
94
124
 
95
125
  ---
96
126
 
@@ -121,7 +151,11 @@ If you only want the raw CSS custom properties (no component rules), import the
121
151
 
122
152
  ```tsx
123
153
  // app/layout.tsx (Next.js App Router example)
124
- export default function RootLayout({ children }: { children: React.ReactNode }) {
154
+ export default function RootLayout({
155
+ children,
156
+ }: {
157
+ children: React.ReactNode;
158
+ }) {
125
159
  return (
126
160
  <html
127
161
  lang="en"
@@ -152,7 +186,9 @@ export function Example() {
152
186
  <Button variant="primary">Save</Button>
153
187
  <Divider />
154
188
  <Modal.Root>
155
- <Modal.Trigger render={<Button variant="secondary" />}>Open</Modal.Trigger>
189
+ <Modal.Trigger render={<Button variant="secondary" />}>
190
+ Open
191
+ </Modal.Trigger>
156
192
  <Modal.Content>
157
193
  <Modal.Title>Confirm</Modal.Title>
158
194
  <Modal.Description>Are you sure?</Modal.Description>
@@ -169,15 +205,15 @@ export function Example() {
169
205
 
170
206
  Seven `data-*` attributes on any ancestor element (typically `<html>` or `<body>`) re-skin every descendant at runtime, with no rebuild.
171
207
 
172
- | Attribute | Values | Default | What it controls |
173
- |---|---|---|---|
174
- | `data-theme` | `light` · `dark` | `light` | Semantic color mapping (background, text, border, shadow). |
175
- | `data-tone` | `vivid` · `pastel` · `soft` | `soft` | Surface model, palette saturation, control radius. `soft` is the signature look. |
176
- | `data-gray` | `slate` · `neutral` · `stone` | `slate` | The gray family used for surfaces and text. |
177
- | `data-brand` | `blue` · `violet` · `emerald` · `orange` · `mono` · `teal` | `teal` | The brand palette (`--brand-50` through `--brand-950`). |
178
- | `data-radius` | `default` · `sharp` · `soft` · `pill` | `default` | The corner radius scale (`--radius-xs` through `--radius-2xl`). |
179
- | `data-density` | `default` · `compact` · `comfortable` | `default` | Control heights and padding (`--control-h-*`, `--control-px-*`). |
180
- | `data-font` | `roboto` · `system` | `roboto` | The font pair (`--font-sans` / `--font-mono`). |
208
+ | Attribute | Values | Default | What it controls |
209
+ | -------------- | ---------------------------------------------------------- | --------- | -------------------------------------------------------------------------------- |
210
+ | `data-theme` | `light` · `dark` | `light` | Semantic color mapping (background, text, border, shadow). |
211
+ | `data-tone` | `vivid` · `pastel` · `soft` | `soft` | Surface model, palette saturation, control radius. `soft` is the signature look. |
212
+ | `data-gray` | `slate` · `neutral` · `stone` | `slate` | The gray family used for surfaces and text. |
213
+ | `data-brand` | `blue` · `violet` · `emerald` · `orange` · `mono` · `teal` | `teal` | The brand palette (`--brand-50` through `--brand-950`). |
214
+ | `data-radius` | `default` · `sharp` · `soft` · `pill` | `default` | The corner radius scale (`--radius-xs` through `--radius-2xl`). |
215
+ | `data-density` | `default` · `compact` · `comfortable` | `default` | Control heights and padding (`--control-h-*`, `--control-px-*`). |
216
+ | `data-font` | `roboto` · `system` | `roboto` | The font pair (`--font-sans` / `--font-mono`). |
181
217
 
182
218
  Toggle them with any approach you like — `setAttribute`, React state, a media-query listener, a server cookie:
183
219
 
@@ -196,10 +232,39 @@ Every component is exported from the package root:
196
232
 
197
233
  ```tsx
198
234
  import {
199
- Alert, Avatar, Badge, Button, Card, Checkbox, Container, Divider,
200
- DropdownMenu, Grid, Input, Modal, Radio, RadioGroup, Segmented,
201
- Select, Skeleton, StatusPill, Switch, Tabs, Textarea, Toast, Tooltip,
202
- useToast, cn,
235
+ Alert,
236
+ Avatar,
237
+ Badge,
238
+ Button,
239
+ Card,
240
+ ChatCompose,
241
+ ChatDateDivider,
242
+ ChatMessage,
243
+ ChatThread,
244
+ Checkbox,
245
+ CommandPalette,
246
+ Container,
247
+ DataTable,
248
+ Divider,
249
+ DropdownMenu,
250
+ Grid,
251
+ Input,
252
+ Modal,
253
+ Popover,
254
+ Radio,
255
+ RadioGroup,
256
+ Segmented,
257
+ Select,
258
+ Skeleton,
259
+ StatusPill,
260
+ Switch,
261
+ Tabs,
262
+ Textarea,
263
+ Toast,
264
+ Tooltip,
265
+ useCommandPalette,
266
+ useToast,
267
+ cn,
203
268
  } from "@bubble-design-system/ui";
204
269
  ```
205
270
 
@@ -217,14 +282,14 @@ Conventions shared by all components:
217
282
 
218
283
  A static informational banner with a variant-specific icon, title, and body.
219
284
 
220
- | Prop | Type | Default | Description |
221
- |---|---|---|---|
222
- | `variant` | `"info" \| "success" \| "warning" \| "danger"` | `"info"` | Visual tone and default icon. |
223
- | `icon` | `ReactNode \| false` | variant-specific | Override the default icon, or pass `false` to hide it. |
224
- | `title` | `ReactNode` | — | Optional bold header line. |
225
- | `children` | `ReactNode` | — | The body copy, rendered in the secondary text color. |
226
- | `className` | `string` | — | Extra classes (appended after the library's). |
227
- | `...props` | `HTMLAttributes<HTMLDivElement>` (minus `title`) | — | All native div attributes. |
285
+ | Prop | Type | Default | Description |
286
+ | ----------- | ------------------------------------------------ | ---------------- | ------------------------------------------------------ |
287
+ | `variant` | `"info" \| "success" \| "warning" \| "danger"` | `"info"` | Visual tone and default icon. |
288
+ | `icon` | `ReactNode \| false` | variant-specific | Override the default icon, or pass `false` to hide it. |
289
+ | `title` | `ReactNode` | — | Optional bold header line. |
290
+ | `children` | `ReactNode` | — | The body copy, rendered in the secondary text color. |
291
+ | `className` | `string` | — | Extra classes (appended after the library's). |
292
+ | `...props` | `HTMLAttributes<HTMLDivElement>` (minus `title`) | — | All native div attributes. |
228
293
 
229
294
  ```tsx
230
295
  <Alert variant="success" title="Saved">Your changes are live.</Alert>
@@ -239,19 +304,19 @@ A static informational banner with a variant-specific icon, title, and body.
239
304
 
240
305
  Compound component built on `@base-ui/react/avatar` — handles image load failure with a fallback.
241
306
 
242
- | Sub-component | Description |
243
- |---|---|
244
- | `Avatar` (root) | Sized circular surface. |
245
- | `Avatar.Image` | The image element. |
307
+ | Sub-component | Description |
308
+ | ----------------- | --------------------------------------------------- |
309
+ | `Avatar` (root) | Sized circular surface. |
310
+ | `Avatar.Image` | The image element. |
246
311
  | `Avatar.Fallback` | Shown while the image is loading or after it fails. |
247
312
 
248
313
  **`Avatar` props:**
249
314
 
250
- | Prop | Type | Default | Description |
251
- |---|---|---|---|
252
- | `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | 24 / 32 / 40 / 48 px. |
253
- | `className` | `string` | — | Extra classes. |
254
- | `...props` | Base UI `Avatar.Root` props | — | Spread to the root. |
315
+ | Prop | Type | Default | Description |
316
+ | ----------- | ------------------------------ | ------- | --------------------- |
317
+ | `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | 24 / 32 / 40 / 48 px. |
318
+ | `className` | `string` | — | Extra classes. |
319
+ | `...props` | Base UI `Avatar.Root` props | — | Spread to the root. |
255
320
 
256
321
  `Avatar.Image` and `Avatar.Fallback` accept their Base UI props plus `className`.
257
322
 
@@ -268,12 +333,12 @@ Compound component built on `@base-ui/react/avatar` — handles image load failu
268
333
 
269
334
  Small inline pill for status, counts, or labels.
270
335
 
271
- | Prop | Type | Default | Description |
272
- |---|---|---|---|
273
- | `variant` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger"` | `"neutral"` | Background and text color. |
274
- | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Pill height and padding. |
275
- | `className` | `string` | — | Extra classes. |
276
- | `...props` | `HTMLAttributes<HTMLSpanElement>` | — | Native span attributes. |
336
+ | Prop | Type | Default | Description |
337
+ | ----------- | ------------------------------------------------------------ | ----------- | -------------------------- |
338
+ | `variant` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger"` | `"neutral"` | Background and text color. |
339
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Pill height and padding. |
340
+ | `className` | `string` | — | Extra classes. |
341
+ | `...props` | `HTMLAttributes<HTMLSpanElement>` | — | Native span attributes. |
277
342
 
278
343
  ```tsx
279
344
  <Badge variant="success">Active</Badge>
@@ -286,12 +351,12 @@ Small inline pill for status, counts, or labels.
286
351
 
287
352
  Wraps `@base-ui/react/button`.
288
353
 
289
- | Prop | Type | Default | Description |
290
- |---|---|---|---|
291
- | `variant` | `"primary" \| "secondary" \| "destructive" \| "ghost"` | `"primary"` | Visual style. |
292
- | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Maps to `--control-h-*` / `--control-px-*` so density attribute affects it. |
293
- | `className` | `string` | — | Extra classes. |
294
- | `...props` | Base UI `Button` props | — | Includes `disabled`, `type`, `onClick`, etc. |
354
+ | Prop | Type | Default | Description |
355
+ | ----------- | ------------------------------------------------------ | ----------- | --------------------------------------------------------------------------- |
356
+ | `variant` | `"primary" \| "secondary" \| "destructive" \| "ghost"` | `"primary"` | Visual style. |
357
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Maps to `--control-h-*` / `--control-px-*` so density attribute affects it. |
358
+ | `className` | `string` | — | Extra classes. |
359
+ | `...props` | Base UI `Button` props | — | Includes `disabled`, `type`, `onClick`, etc. |
295
360
 
296
361
  ```tsx
297
362
  <Button variant="primary" size="lg" onClick={save}>Save</Button>
@@ -305,22 +370,22 @@ Wraps `@base-ui/react/button`.
305
370
 
306
371
  Compound component for floating-pill surface content.
307
372
 
308
- | Sub-component | Description |
309
- |---|---|
310
- | `Card` (root) | The floating surface. Variant controls fill + shadow. |
311
- | `Card.Header` | Row with title + optional action. |
312
- | `Card.Title` | `<h3>` heading. |
313
- | `Card.Description` | Supporting paragraph. |
314
- | `Card.Action` | Right-aligned controls inside the header. |
315
- | `Card.Body` | Main content area. |
316
- | `Card.Footer` | Bordered footer row with right-aligned controls. |
373
+ | Sub-component | Description |
374
+ | ------------------ | ----------------------------------------------------- |
375
+ | `Card` (root) | The floating surface. Variant controls fill + shadow. |
376
+ | `Card.Header` | Row with title + optional action. |
377
+ | `Card.Title` | `<h3>` heading. |
378
+ | `Card.Description` | Supporting paragraph. |
379
+ | `Card.Action` | Right-aligned controls inside the header. |
380
+ | `Card.Body` | Main content area. |
381
+ | `Card.Footer` | Bordered footer row with right-aligned controls. |
317
382
 
318
383
  **`Card` props:**
319
384
 
320
- | Prop | Type | Default | Description |
321
- |---|---|---|---|
322
- | `variant` | `"elevated" \| "muted"` | `"elevated"` | `elevated` = white surface with shadow. `muted` = `bg-secondary`, no shadow. |
323
- | `className` | `string` | — | Extra classes. |
385
+ | Prop | Type | Default | Description |
386
+ | ----------- | ----------------------- | ------------ | ---------------------------------------------------------------------------- |
387
+ | `variant` | `"elevated" \| "muted"` | `"elevated"` | `elevated` = white surface with shadow. `muted` = `bg-secondary`, no shadow. |
388
+ | `className` | `string` | — | Extra classes. |
324
389
 
325
390
  ```tsx
326
391
  <Card>
@@ -330,12 +395,16 @@ Compound component for floating-pill surface content.
330
395
  <Card.Description>White card floating on a gray page.</Card.Description>
331
396
  </div>
332
397
  <Card.Action>
333
- <Button size="sm" variant="ghost">Manage</Button>
398
+ <Button size="sm" variant="ghost">
399
+ Manage
400
+ </Button>
334
401
  </Card.Action>
335
402
  </Card.Header>
336
403
  <Card.Body>…</Card.Body>
337
404
  <Card.Footer>
338
- <Button size="sm" variant="ghost">Cancel</Button>
405
+ <Button size="sm" variant="ghost">
406
+ Cancel
407
+ </Button>
339
408
  <Button size="sm">Save</Button>
340
409
  </Card.Footer>
341
410
  </Card>
@@ -343,15 +412,86 @@ Compound component for floating-pill surface content.
343
412
 
344
413
  ---
345
414
 
415
+ ### Chat
416
+
417
+ Four standalone components for assembling a chat thread — no Base UI primitive needed, just plain markup. `ChatThread` is the scroll container, `ChatDateDivider` renders a centered separator label, `ChatMessage` is a single message bubble (with grouping, avatar, meta, reactions, and delivery status), and `ChatCompose` is an auto-growing input bar.
418
+
419
+ | Component | Description |
420
+ | ----------------- | ---------------------------------------------------------------- |
421
+ | `ChatThread` | Scrollable message-list container. |
422
+ | `ChatDateDivider` | Centered separator label (e.g. "Today"). |
423
+ | `ChatMessage` | A single message bubble — grouping/avatar/meta/reactions/status. |
424
+ | `ChatCompose` | Auto-growing textarea + send button, controlled or uncontrolled. |
425
+
426
+ **`ChatMessage` props:**
427
+
428
+ | Prop | Type | Default | Description |
429
+ | ----------- | ----------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------- |
430
+ | `side` | `"sent" \| "received"` | `"received"` | Which side of the thread the bubble renders on. |
431
+ | `position` | `"solo" \| "first" \| "middle" \| "last"` | `"solo"` | Position within a consecutive group — controls bubble-corner rounding and which messages show meta/avatar/status. |
432
+ | `avatar` | `ReactNode` | — | Avatar slot, shown for `received` messages at `solo`/`last`. |
433
+ | `name` | `string` | — | Sender name, shown at `solo`/`first` for received messages. |
434
+ | `time` | `string` | — | Timestamp, shown at `solo`/`first`. |
435
+ | `status` | `"sending" \| "sent" \| "delivered" \| "read"` | — | Delivery-status row, shown for `sent` messages at `solo`/`last`. |
436
+ | `reactions` | `{ emoji: string; count?: number; mine?: boolean }[]` | — | Reaction pills rendered under the bubble. |
437
+ | `className` | `string` | — | Extra classes. |
438
+
439
+ **`ChatCompose` props:**
440
+
441
+ | Prop | Type | Default | Description |
442
+ | ------------- | ----------------------------------------------- | -------------------- | ---------------------------------------------------------------- |
443
+ | `value` | `string` | — | Controlled value. Omit for uncontrolled (internal state). |
444
+ | `onChange` | `(e: ChangeEvent<HTMLTextAreaElement>) => void` | — | Change handler (controlled mode). |
445
+ | `onSend` | `(value: string) => void` | — | Called with the trimmed text on send (Enter or the send button). |
446
+ | `placeholder` | `string` | `"Write a message…"` | Textarea placeholder. |
447
+ | `avatar` | `ReactNode` | — | Avatar slot rendered before the input. |
448
+ | `disabled` | `boolean` | `false` | Disables the textarea and send button. |
449
+ | `className` | `string` | — | Extra classes. |
450
+
451
+ ```tsx
452
+ <ChatThread>
453
+ <ChatDateDivider>Today</ChatDateDivider>
454
+
455
+ <ChatMessage
456
+ side="received"
457
+ name="Lena Torres"
458
+ time="9:41 AM"
459
+ avatar={
460
+ <Avatar>
461
+ <Avatar.Fallback>LT</Avatar.Fallback>
462
+ </Avatar>
463
+ }
464
+ reactions={[{ emoji: "👍", count: 2, mine: true }]}
465
+ >
466
+ Hey — got a minute to look at the new Card variants?
467
+ </ChatMessage>
468
+
469
+ <ChatMessage side="sent" status="read">
470
+ Sure, pulling them up now.
471
+ </ChatMessage>
472
+ </ChatThread>
473
+
474
+ <ChatCompose
475
+ avatar={
476
+ <Avatar>
477
+ <Avatar.Fallback>AK</Avatar.Fallback>
478
+ </Avatar>
479
+ }
480
+ onSend={(text) => sendMessage(text)}
481
+ />
482
+ ```
483
+
484
+ ---
485
+
346
486
  ### Checkbox
347
487
 
348
488
  Wraps `@base-ui/react/checkbox`. Supports checked, unchecked, and indeterminate states with built-in SVG indicators.
349
489
 
350
- | Prop | Type | Default | Description |
351
- |---|---|---|---|
352
- | `size` | `"sm" \| "md" \| "lg"` | `"md"` | 16 / 18 / 20 px. |
353
- | `className` | `string` | — | Extra classes on the root button. |
354
- | `...props` | Base UI `Checkbox.Root` props | — | `checked`, `defaultChecked`, `indeterminate`, `onCheckedChange`, etc. |
490
+ | Prop | Type | Default | Description |
491
+ | ----------- | ----------------------------- | ------- | --------------------------------------------------------------------- |
492
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | 16 / 18 / 20 px. |
493
+ | `className` | `string` | — | Extra classes on the root button. |
494
+ | `...props` | Base UI `Checkbox.Root` props | — | `checked`, `defaultChecked`, `indeterminate`, `onCheckedChange`, etc. |
355
495
 
356
496
  ```tsx
357
497
  <Checkbox defaultChecked />
@@ -361,6 +501,50 @@ Wraps `@base-ui/react/checkbox`. Supports checked, unchecked, and indeterminate
361
501
 
362
502
  ---
363
503
 
504
+ ### CommandPalette
505
+
506
+ Built on `@base-ui/react/dialog`. A fuzzy-searchable, grouped command list with arrow-key navigation and Enter-to-select — the "/" or ⌘K menu pattern. Pair it with `useCommandPalette()`, a small hook that owns `open` state and registers a global `⌘K` / `Ctrl+K` listener.
507
+
508
+ **`CommandPalette` props:**
509
+
510
+ | Prop | Type | Default | Description |
511
+ | -------------- | ------------------------------------ | ------------------ | -------------------------------------------------------------------------------- |
512
+ | `open` | `boolean` | — | Whether the palette is open. |
513
+ | `onOpenChange` | `(open: boolean) => void` | — | Called when the palette should open or close. |
514
+ | `groups` | `CommandPaletteGroup[]` | `[]` | Grouped, filterable command items. |
515
+ | `placeholder` | `string` | `"Type to search"` | Search input placeholder. |
516
+ | `onSelect` | `(item: CommandPaletteItem) => void` | — | Called when an item is chosen (Enter or click), after the item's own `onSelect`. |
517
+ | `className` | `string` | — | Extra classes on the popup. |
518
+
519
+ **`CommandPaletteGroup`** — `{ label?: string; items: CommandPaletteItem[] }`
520
+
521
+ **`CommandPaletteItem`** — `{ id: string; label: string; description?: string; icon?: ReactNode; shortcut?: string; keywords?: string[]; onSelect?: () => void }`
522
+
523
+ ```tsx
524
+ const palette = useCommandPalette(); // owns `open` + ⌘K/Ctrl+K toggle
525
+
526
+ <CommandPalette
527
+ open={palette.open}
528
+ onOpenChange={palette.setOpen}
529
+ groups={[
530
+ {
531
+ label: "Navigation",
532
+ items: [
533
+ { id: "home", label: "Go home", shortcut: "G H" },
534
+ { id: "settings", label: "Settings", keywords: ["preferences"] },
535
+ ],
536
+ },
537
+ {
538
+ label: "Actions",
539
+ items: [{ id: "new", label: "New item", onSelect: () => createItem() }],
540
+ },
541
+ ]}
542
+ onSelect={(item) => console.log("selected", item.id)}
543
+ />;
544
+ ```
545
+
546
+ ---
547
+
364
548
  ### Container + Grid
365
549
 
366
550
  Layout primitives. `Container` centers content and applies page margins. `Grid` is a 12-column grid; `Grid.Col` spans columns with optional responsive overrides.
@@ -369,9 +553,15 @@ Layout primitives. `Container` centers content and applies page margins. `Grid`
369
553
  <Container size="lg">
370
554
  <Grid>
371
555
  <Grid.Col span={12}>full row</Grid.Col>
372
- <Grid.Col span={6} lgSpan={4}>half on mobile, third on lg</Grid.Col>
373
- <Grid.Col span={6} lgSpan={4}>…</Grid.Col>
374
- <Grid.Col span={12} lgSpan={4}>…</Grid.Col>
556
+ <Grid.Col span={6} lgSpan={4}>
557
+ half on mobile, third on lg
558
+ </Grid.Col>
559
+ <Grid.Col span={6} lgSpan={4}>
560
+
561
+ </Grid.Col>
562
+ <Grid.Col span={12} lgSpan={4}>
563
+
564
+ </Grid.Col>
375
565
  </Grid>
376
566
  </Container>
377
567
  ```
@@ -384,15 +574,64 @@ Layout primitives. `Container` centers content and applies page margins. `Grid`
384
574
 
385
575
  ---
386
576
 
577
+ ### DataTable
578
+
579
+ Generic, client-side `DataTable<T extends { id: string | number }>` — search across all columns, sortable columns, row selection (header select-all + per-row, reusing `Checkbox`), and pagination.
580
+
581
+ **Props:**
582
+
583
+ | Prop | Type | Default | Description |
584
+ | ------------ | ---------------------- | ------- | ------------------------------------------------------- |
585
+ | `columns` | `DataTableColumn<T>[]` | — | Column definitions, rendered in order. |
586
+ | `data` | `T[]` | — | Rows. Each row must have an `id: string \| number`. |
587
+ | `selectable` | `boolean` | `true` | Shows the select-all / per-row checkboxes. |
588
+ | `searchable` | `boolean` | `true` | Shows the search input; filters across all column keys. |
589
+ | `pageSize` | `number` | `10` | Rows per page. |
590
+ | `actions` | `ReactNode` | — | Right-aligned toolbar content (e.g. a "New" button). |
591
+ | `className` | `string` | — | Extra classes. |
592
+
593
+ **`DataTableColumn<T>`:**
594
+
595
+ | Field | Type | Description |
596
+ | ---------------- | --------------------------------------- | ---------------------------------------------- |
597
+ | `key` | `string` | Property name read off each row. |
598
+ | `label` | `string` | Column header text. |
599
+ | `sortable` | `boolean` | Default `true` — click the header to sort. |
600
+ | `render` | `(value: unknown, row: T) => ReactNode` | Custom cell renderer. |
601
+ | `width` | `string` | CSS width for the `<th>` / `<td>`. |
602
+ | `muted` / `mono` | `boolean` | Styles the cell text as secondary / monospace. |
603
+
604
+ ```tsx
605
+ <DataTable
606
+ columns={[
607
+ { key: "name", label: "Name" },
608
+ { key: "email", label: "Email", muted: true },
609
+ {
610
+ key: "status",
611
+ label: "Status",
612
+ render: (value) => (
613
+ <StatusPill intent={value === "active" ? "success" : "neutral"}>
614
+ {String(value)}
615
+ </StatusPill>
616
+ ),
617
+ },
618
+ ]}
619
+ data={users}
620
+ actions={<Button size="sm">Invite</Button>}
621
+ />
622
+ ```
623
+
624
+ ---
625
+
387
626
  ### Divider
388
627
 
389
628
  Horizontal or vertical separator with a semantic role from `@base-ui/react/separator`.
390
629
 
391
- | Prop | Type | Default | Description |
392
- |---|---|---|---|
393
- | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction. |
394
- | `className` | `string` | — | Extra classes. |
395
- | `...props` | Base UI `Separator` props | — | Spread to the underlying element. |
630
+ | Prop | Type | Default | Description |
631
+ | ------------- | ---------------------------- | -------------- | --------------------------------- |
632
+ | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction. |
633
+ | `className` | `string` | — | Extra classes. |
634
+ | `...props` | Base UI `Separator` props | — | Spread to the underlying element. |
396
635
 
397
636
  ```tsx
398
637
  <Divider />
@@ -407,27 +646,27 @@ Horizontal or vertical separator with a semantic role from `@base-ui/react/separ
407
646
 
408
647
  Compound component built on `@base-ui/react/menu`. Supports items, checkbox items, radio groups, labels, and separators.
409
648
 
410
- | Sub-component | Description |
411
- |---|---|
412
- | `DropdownMenu.Root` | The state container. |
413
- | `DropdownMenu.Trigger` | The element that opens the menu. |
414
- | `DropdownMenu.Content` | The portalled popup. |
415
- | `DropdownMenu.Item` | A selectable row. |
649
+ | Sub-component | Description |
650
+ | --------------------------- | ---------------------------------------- |
651
+ | `DropdownMenu.Root` | The state container. |
652
+ | `DropdownMenu.Trigger` | The element that opens the menu. |
653
+ | `DropdownMenu.Content` | The portalled popup. |
654
+ | `DropdownMenu.Item` | A selectable row. |
416
655
  | `DropdownMenu.CheckboxItem` | A toggleable row with a check indicator. |
417
- | `DropdownMenu.RadioGroup` | Wrapper for radio items. |
418
- | `DropdownMenu.RadioItem` | A single radio choice. |
419
- | `DropdownMenu.Group` | Logical group of items. |
420
- | `DropdownMenu.Label` | Uppercase group label. |
421
- | `DropdownMenu.Separator` | Thin horizontal divider. |
656
+ | `DropdownMenu.RadioGroup` | Wrapper for radio items. |
657
+ | `DropdownMenu.RadioItem` | A single radio choice. |
658
+ | `DropdownMenu.Group` | Logical group of items. |
659
+ | `DropdownMenu.Label` | Uppercase group label. |
660
+ | `DropdownMenu.Separator` | Thin horizontal divider. |
422
661
 
423
662
  **`DropdownMenu.Content` props:**
424
663
 
425
- | Prop | Type | Default | Description |
426
- |---|---|---|---|
427
- | `sideOffset` | `number` | `6` | Distance in px from the trigger. |
428
- | `align` | `"start" \| "center" \| "end"` | `"start"` | Alignment relative to the trigger. |
429
- | `className` | `string` | — | Extra classes on the popup. |
430
- | `...props` | Base UI `Menu.Popup` props | — | Spread to the popup. |
664
+ | Prop | Type | Default | Description |
665
+ | ------------ | ------------------------------ | --------- | ---------------------------------- |
666
+ | `sideOffset` | `number` | `6` | Distance in px from the trigger. |
667
+ | `align` | `"start" \| "center" \| "end"` | `"start"` | Alignment relative to the trigger. |
668
+ | `className` | `string` | — | Extra classes on the popup. |
669
+ | `...props` | Base UI `Menu.Popup` props | — | Spread to the popup. |
431
670
 
432
671
  ```tsx
433
672
  <DropdownMenu.Root>
@@ -448,12 +687,12 @@ Compound component built on `@base-ui/react/menu`. Supports items, checkbox item
448
687
 
449
688
  Wraps `@base-ui/react/input`. Includes built-in invalid styling via `aria-invalid`.
450
689
 
451
- | Prop | Type | Default | Description |
452
- |---|---|---|---|
453
- | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Density-aware control height. |
454
- | `invalid` | `boolean` | — | Sets `aria-invalid` and applies the danger border + focus ring. |
455
- | `className` | `string` | — | Extra classes. |
456
- | `...props` | Base UI `Input` props (minus `size`) | — | All native input attributes: `value`, `placeholder`, `type`, `onChange`, `disabled`, etc. |
690
+ | Prop | Type | Default | Description |
691
+ | ----------- | ------------------------------------ | ------- | ----------------------------------------------------------------------------------------- |
692
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Density-aware control height. |
693
+ | `invalid` | `boolean` | — | Sets `aria-invalid` and applies the danger border + focus ring. |
694
+ | `className` | `string` | — | Extra classes. |
695
+ | `...props` | Base UI `Input` props (minus `size`) | — | All native input attributes: `value`, `placeholder`, `type`, `onChange`, `disabled`, etc. |
457
696
 
458
697
  ```tsx
459
698
  <Input placeholder="Email" />
@@ -466,22 +705,22 @@ Wraps `@base-ui/react/input`. Includes built-in invalid styling via `aria-invali
466
705
 
467
706
  Compound component built on `@base-ui/react/dialog`. Renders into a portal, with backdrop blur and scale-in animation.
468
707
 
469
- | Sub-component | Description |
470
- |---|---|
471
- | `Modal.Root` | Open-state container. |
472
- | `Modal.Trigger` | Element that opens the modal. |
473
- | `Modal.Close` | Element that closes the modal. |
474
- | `Modal.Content` | The portalled popup with backdrop. |
475
- | `Modal.Title` | Heading text. |
476
- | `Modal.Description` | Sub-text under the title. |
708
+ | Sub-component | Description |
709
+ | ------------------- | ---------------------------------- |
710
+ | `Modal.Root` | Open-state container. |
711
+ | `Modal.Trigger` | Element that opens the modal. |
712
+ | `Modal.Close` | Element that closes the modal. |
713
+ | `Modal.Content` | The portalled popup with backdrop. |
714
+ | `Modal.Title` | Heading text. |
715
+ | `Modal.Description` | Sub-text under the title. |
477
716
 
478
717
  **`Modal.Content` props:**
479
718
 
480
- | Prop | Type | Default | Description |
481
- |---|---|---|---|
482
- | `className` | `string` | — | Extra classes on the popup. |
483
- | `backdropClassName` | `string` | — | Extra classes on the backdrop. |
484
- | `...props` | Base UI `Dialog.Popup` props | — | Spread to the popup. |
719
+ | Prop | Type | Default | Description |
720
+ | ------------------- | ---------------------------- | ------- | ------------------------------ |
721
+ | `className` | `string` | — | Extra classes on the popup. |
722
+ | `backdropClassName` | `string` | — | Extra classes on the backdrop. |
723
+ | `...props` | Base UI `Dialog.Popup` props | — | Spread to the popup. |
485
724
 
486
725
  `Modal.Title` and `Modal.Description` accept their Base UI props plus `className`.
487
726
 
@@ -491,7 +730,14 @@ Compound component built on `@base-ui/react/dialog`. Renders into a portal, with
491
730
  <Modal.Content>
492
731
  <Modal.Title>Delete project?</Modal.Title>
493
732
  <Modal.Description>This action cannot be undone.</Modal.Description>
494
- <div style={{ marginTop: "1rem", display: "flex", justifyContent: "flex-end", gap: "0.5rem" }}>
733
+ <div
734
+ style={{
735
+ marginTop: "1rem",
736
+ display: "flex",
737
+ justifyContent: "flex-end",
738
+ gap: "0.5rem",
739
+ }}
740
+ >
495
741
  <Modal.Close render={<Button variant="secondary">Cancel</Button>} />
496
742
  <Button variant="destructive">Delete</Button>
497
743
  </div>
@@ -501,29 +747,80 @@ Compound component built on `@base-ui/react/dialog`. Renders into a portal, with
501
747
 
502
748
  ---
503
749
 
750
+ ### Popover
751
+
752
+ Compound component built on `@base-ui/react/popover`. Like `Tooltip`, but for richer content (forms, lists, multi-element panels) that stays open until dismissed.
753
+
754
+ | Sub-component | Description |
755
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------ |
756
+ | `Popover.Root` | State container. |
757
+ | `Popover.Trigger` | The element that opens the popover. |
758
+ | `Popover.Content` | The portalled popup — pre-wires Portal → Positioner → Popup, plus an optional arrow. |
759
+ | `Popover.Title` / `Popover.Description` | Accessible heading / description, wired to ARIA via Base UI. |
760
+ | `Popover.Close` | Closes the popover when clicked. |
761
+ | `Popover.Header` / `Popover.Body` / `Popover.Footer` | Layout slots for `Content`'s children. |
762
+
763
+ **`Popover.Content` props:**
764
+
765
+ | Prop | Type | Default | Description |
766
+ | ------------ | ---------------------------------------- | ---------- | ---------------------------------------- |
767
+ | `side` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Preferred side. |
768
+ | `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment along the side. |
769
+ | `sideOffset` | `number` | `10` | Distance from the trigger. |
770
+ | `showArrow` | `boolean` | `true` | Renders a caret pointing at the trigger. |
771
+ | `className` | `string` | — | Extra classes on the popup. |
772
+
773
+ ```tsx
774
+ <Popover.Root>
775
+ <Popover.Trigger render={<Button variant="secondary">Filters</Button>} />
776
+ <Popover.Content>
777
+ <Popover.Header>
778
+ <Popover.Title>Column visibility</Popover.Title>
779
+ </Popover.Header>
780
+ <Popover.Body>…</Popover.Body>
781
+ <Popover.Footer>
782
+ <Popover.Close
783
+ render={
784
+ <Button size="sm" variant="ghost">
785
+ Reset
786
+ </Button>
787
+ }
788
+ />
789
+ <Button size="sm">Apply</Button>
790
+ </Popover.Footer>
791
+ </Popover.Content>
792
+ </Popover.Root>
793
+ ```
794
+
795
+ ---
796
+
504
797
  ### Radio / RadioGroup
505
798
 
506
799
  Wraps `@base-ui/react/radio` and `@base-ui/react/radio-group`.
507
800
 
508
801
  **`Radio` props:**
509
802
 
510
- | Prop | Type | Default | Description |
511
- |---|---|---|---|
512
- | `size` | `"sm" \| "md" \| "lg"` | `"md"` | 16 / 18 / 20 px. |
513
- | `className` | `string` | — | Extra classes. |
514
- | `...props` | Base UI `Radio.Root` props | — | `value`, `disabled`, etc. |
803
+ | Prop | Type | Default | Description |
804
+ | ----------- | -------------------------- | ------- | ------------------------- |
805
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | 16 / 18 / 20 px. |
806
+ | `className` | `string` | — | Extra classes. |
807
+ | `...props` | Base UI `Radio.Root` props | — | `value`, `disabled`, etc. |
515
808
 
516
809
  **`RadioGroup` props:**
517
810
 
518
- | Prop | Type | Default | Description |
519
- |---|---|---|---|
520
- | `className` | `string` | — | Override the default vertical stack. |
521
- | `...props` | Base UI `RadioGroup` props | — | `value`, `defaultValue`, `onValueChange`. |
811
+ | Prop | Type | Default | Description |
812
+ | ----------- | -------------------------- | ------- | ----------------------------------------- |
813
+ | `className` | `string` | — | Override the default vertical stack. |
814
+ | `...props` | Base UI `RadioGroup` props | — | `value`, `defaultValue`, `onValueChange`. |
522
815
 
523
816
  ```tsx
524
817
  <RadioGroup defaultValue="email">
525
- <label><Radio value="email" /> Email</label>
526
- <label><Radio value="sms" /> SMS</label>
818
+ <label>
819
+ <Radio value="email" /> Email
820
+ </label>
821
+ <label>
822
+ <Radio value="sms" /> SMS
823
+ </label>
527
824
  </RadioGroup>
528
825
  ```
529
826
 
@@ -533,19 +830,19 @@ Wraps `@base-ui/react/radio` and `@base-ui/react/radio-group`.
533
830
 
534
831
  Compound component built on `@base-ui/react/toggle-group` (single-select). The selected item rises as a white floating pill.
535
832
 
536
- | Sub-component | Description |
537
- |---|---|
833
+ | Sub-component | Description |
834
+ | ------------------ | ----------------- |
538
835
  | `Segmented` (root) | The toggle group. |
539
- | `Segmented.Item` | A single segment. |
836
+ | `Segmented.Item` | A single segment. |
540
837
 
541
838
  **`Segmented` props:**
542
839
 
543
- | Prop | Type | Default | Description |
544
- |---|---|---|---|
545
- | `value` | `string` | — | Controlled selected value. |
546
- | `defaultValue` | `string` | — | Uncontrolled initial value. |
547
- | `onValueChange` | `(value: string) => void` | — | Fired with the new value. |
548
- | `size` | `"sm" \| "md" \| "lg"` | `"md"` | 24 / 28 / 32 px. |
840
+ | Prop | Type | Default | Description |
841
+ | --------------- | ------------------------- | ------- | --------------------------- |
842
+ | `value` | `string` | — | Controlled selected value. |
843
+ | `defaultValue` | `string` | — | Uncontrolled initial value. |
844
+ | `onValueChange` | `(value: string) => void` | — | Fired with the new value. |
845
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | 24 / 28 / 32 px. |
549
846
 
550
847
  ```tsx
551
848
  <Segmented value={range} onValueChange={setRange}>
@@ -561,13 +858,13 @@ Compound component built on `@base-ui/react/toggle-group` (single-select). The s
561
858
 
562
859
  Compound component built on `@base-ui/react/select`.
563
860
 
564
- | Sub-component | Description |
565
- |---|---|
566
- | `Select.Root` | State container (`value`, `onValueChange`). |
567
- | `Select.Trigger` | The clickable trigger; renders a chevron icon. |
568
- | `Select.Value` | The selected value's display. |
569
- | `Select.Content` | The portalled popup. |
570
- | `Select.Item` | A selectable row with a check indicator when selected. |
861
+ | Sub-component | Description |
862
+ | ---------------- | ------------------------------------------------------ |
863
+ | `Select.Root` | State container (`value`, `onValueChange`). |
864
+ | `Select.Trigger` | The clickable trigger; renders a chevron icon. |
865
+ | `Select.Value` | The selected value's display. |
866
+ | `Select.Content` | The portalled popup. |
867
+ | `Select.Item` | A selectable row with a check indicator when selected. |
571
868
 
572
869
  **`Select.Trigger` props:** `size` = `"sm" | "md" | "lg"` (default `"md"`), plus `className`.
573
870
 
@@ -594,11 +891,11 @@ Compound component built on `@base-ui/react/select`.
594
891
 
595
892
  Loading placeholder with a pulse animation.
596
893
 
597
- | Prop | Type | Default | Description |
598
- |---|---|---|---|
599
- | `shape` | `"line" \| "circle" \| "block"` | `"line"` | Default dimensions and radius. |
600
- | `className` | `string` | — | Override width/height/radius via your own CSS. |
601
- | `...props` | `HTMLAttributes<HTMLDivElement>` | — | Native div attributes. |
894
+ | Prop | Type | Default | Description |
895
+ | ----------- | -------------------------------- | -------- | ---------------------------------------------- |
896
+ | `shape` | `"line" \| "circle" \| "block"` | `"line"` | Default dimensions and radius. |
897
+ | `className` | `string` | — | Override width/height/radius via your own CSS. |
898
+ | `...props` | `HTMLAttributes<HTMLDivElement>` | — | Native div attributes. |
602
899
 
603
900
  ```tsx
604
901
  <Skeleton />
@@ -612,11 +909,11 @@ Loading placeholder with a pulse animation.
612
909
 
613
910
  Compound floating-pill component for status indicators. Intent drives chip + label color via CSS custom properties.
614
911
 
615
- | Sub-component | Description |
616
- |---|---|
617
- | `StatusPill` (root) | The pill surface. |
912
+ | Sub-component | Description |
913
+ | ---------------------- | ----------------------------------------------------------- |
914
+ | `StatusPill` (root) | The pill surface. |
618
915
  | `StatusPill.Indicator` | The leading colored chip. Children render an optional icon. |
619
- | `StatusPill.Label` | The colored text label. |
916
+ | `StatusPill.Label` | The colored text label. |
620
917
 
621
918
  **`StatusPill` props:** `intent` = `"neutral" | "success" | "warning" | "danger" | "info"` (default `"neutral"`).
622
919
 
@@ -633,11 +930,11 @@ Compound floating-pill component for status indicators. Intent drives chip + lab
633
930
 
634
931
  Wraps `@base-ui/react/switch` — a thumb that slides on `data-[checked]`.
635
932
 
636
- | Prop | Type | Default | Description |
637
- |---|---|---|---|
638
- | `size` | `"sm" \| "md" \| "lg"` | `"md"` | 16 / 20 / 24 px tall. |
639
- | `className` | `string` | — | Extra classes on the root. |
640
- | `...props` | Base UI `Switch.Root` props | — | `checked`, `defaultChecked`, `onCheckedChange`, `disabled`. |
933
+ | Prop | Type | Default | Description |
934
+ | ----------- | --------------------------- | ------- | ----------------------------------------------------------- |
935
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | 16 / 20 / 24 px tall. |
936
+ | `className` | `string` | — | Extra classes on the root. |
937
+ | `...props` | Base UI `Switch.Root` props | — | `checked`, `defaultChecked`, `onCheckedChange`, `disabled`. |
641
938
 
642
939
  ```tsx
643
940
  <Switch defaultChecked />
@@ -650,12 +947,12 @@ Wraps `@base-ui/react/switch` — a thumb that slides on `data-[checked]`.
650
947
 
651
948
  Compound component built on `@base-ui/react/tabs`. The list renders an animated indicator that slides between active tabs.
652
949
 
653
- | Sub-component | Description |
654
- |---|---|
950
+ | Sub-component | Description |
951
+ | ------------- | --------------------------------------------------------------- |
655
952
  | `Tabs` (root) | State container; pass `value`, `defaultValue`, `onValueChange`. |
656
- | `Tabs.List` | Horizontal tab strip with a sliding indicator. |
657
- | `Tabs.Tab` | A single tab button. |
658
- | `Tabs.Panel` | The panel paired with a tab `value`. |
953
+ | `Tabs.List` | Horizontal tab strip with a sliding indicator. |
954
+ | `Tabs.Tab` | A single tab button. |
955
+ | `Tabs.Panel` | The panel paired with a tab `value`. |
659
956
 
660
957
  All sub-components accept their Base UI props plus `className`.
661
958
 
@@ -678,12 +975,12 @@ All sub-components accept their Base UI props plus `className`.
678
975
 
679
976
  A thin styled `<textarea>` mirroring Input's API.
680
977
 
681
- | Prop | Type | Default | Description |
682
- |---|---|---|---|
683
- | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Density-aware control padding. |
684
- | `invalid` | `boolean` | — | Sets `aria-invalid` and applies the danger border + focus ring. |
685
- | `className` | `string` | — | Extra classes. |
686
- | `...props` | `TextareaHTMLAttributes` (minus `size`) | — | All native textarea attributes. |
978
+ | Prop | Type | Default | Description |
979
+ | ----------- | --------------------------------------- | ------- | --------------------------------------------------------------- |
980
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Density-aware control padding. |
981
+ | `invalid` | `boolean` | — | Sets `aria-invalid` and applies the danger border + focus ring. |
982
+ | `className` | `string` | — | Extra classes. |
983
+ | `...props` | `TextareaHTMLAttributes` (minus `size`) | — | All native textarea attributes. |
687
984
 
688
985
  ```tsx
689
986
  <Textarea placeholder="Notes" rows={4} />
@@ -696,12 +993,12 @@ A thin styled `<textarea>` mirroring Input's API.
696
993
 
697
994
  Built on `@base-ui/react/toast`. Provides a `<Toast.Provider>` boundary, a `<Toast.Viewport>` for positioning, a pre-built `<Toast.Toaster>` that renders the queue, and the `useToast()` hook to push toasts.
698
995
 
699
- | Sub-component | Description |
700
- |---|---|
701
- | `Toast.Provider` | Wrap your app to enable toasts. |
702
- | `Toast.Viewport` | Positioned region where toasts mount (bottom-right by default). |
703
- | `Toast.Toaster` | Pre-styled queue renderer — drop this inside `Provider`. |
704
- | `useToast()` | Hook returning Base UI's toast manager (`.add({ title, description })`). |
996
+ | Sub-component | Description |
997
+ | ---------------- | ------------------------------------------------------------------------ |
998
+ | `Toast.Provider` | Wrap your app to enable toasts. |
999
+ | `Toast.Viewport` | Positioned region where toasts mount (bottom-right by default). |
1000
+ | `Toast.Toaster` | Pre-styled queue renderer — drop this inside `Provider`. |
1001
+ | `useToast()` | Hook returning Base UI's toast manager (`.add({ title, description })`). |
705
1002
 
706
1003
  ```tsx
707
1004
  import { Toast, useToast, Button } from "@bubble-design-system/ui";
@@ -718,7 +1015,11 @@ function Root({ children }) {
718
1015
  function SaveButton() {
719
1016
  const toast = useToast();
720
1017
  return (
721
- <Button onClick={() => toast.add({ title: "Saved", description: "Changes are live." })}>
1018
+ <Button
1019
+ onClick={() =>
1020
+ toast.add({ title: "Saved", description: "Changes are live." })
1021
+ }
1022
+ >
722
1023
  Save
723
1024
  </Button>
724
1025
  );
@@ -731,21 +1032,21 @@ function SaveButton() {
731
1032
 
732
1033
  Compound component built on `@base-ui/react/tooltip`. Requires a `Tooltip.Provider` ancestor (typically once at the app root).
733
1034
 
734
- | Sub-component | Description |
735
- |---|---|
736
- | `Tooltip.Provider` | App-level provider. |
737
- | `Tooltip.Root` | Single tooltip state container. |
738
- | `Tooltip.Trigger` | The hovered/focused element. |
739
- | `Tooltip.Content` | The portalled popup. |
1035
+ | Sub-component | Description |
1036
+ | ------------------ | ------------------------------- |
1037
+ | `Tooltip.Provider` | App-level provider. |
1038
+ | `Tooltip.Root` | Single tooltip state container. |
1039
+ | `Tooltip.Trigger` | The hovered/focused element. |
1040
+ | `Tooltip.Content` | The portalled popup. |
740
1041
 
741
1042
  **`Tooltip.Content` props:**
742
1043
 
743
- | Prop | Type | Default | Description |
744
- |---|---|---|---|
745
- | `side` | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Preferred side. |
746
- | `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment along the side. |
747
- | `sideOffset` | `number` | `6` | Distance from the trigger. |
748
- | `className` | `string` | — | Extra classes on the popup. |
1044
+ | Prop | Type | Default | Description |
1045
+ | ------------ | ---------------------------------------- | ---------- | --------------------------- |
1046
+ | `side` | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Preferred side. |
1047
+ | `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment along the side. |
1048
+ | `sideOffset` | `number` | `6` | Distance from the trigger. |
1049
+ | `className` | `string` | — | Extra classes on the popup. |
749
1050
 
750
1051
  ```tsx
751
1052
  <Tooltip.Provider>
@@ -773,71 +1074,74 @@ cn("my-card", isActive && "my-card--active", className);
773
1074
 
774
1075
  ## Design tokens
775
1076
 
1077
+ > **About to write `var(--color-bg-*)` / `var(--radius-*)` / `var(--shadow-*)` on a hand-authored `<div>`?**
1078
+ > Tokens are how you _theme_ Bubble's components and the escape hatch for genuine gaps — they are not a second, parallel "build it yourself" API. Check [Common UI Patterns](#common-ui-patterns) first; the surface/pill/menu/banner you're about to build very likely already exists as a themed, accessible component.
1079
+
776
1080
  All tokens are CSS custom properties defined in `src/tokens.css`. Reference them directly in your CSS with `var(--…)`. The semantic tokens (those prefixed `--color-bg-*`, `--color-text-*`, `--color-border-*`) re-resolve automatically when an ancestor `data-*` attribute changes.
777
1081
 
778
1082
  ### Color tokens
779
1083
 
780
1084
  Semantic colors map to primitive palettes and are remapped by `[data-theme]`, `[data-gray]`, and `[data-brand]`.
781
1085
 
782
- | Token | Purpose |
783
- |---|---|
784
- | `--color-bg-primary` | Page background. |
785
- | `--color-bg-secondary` | Secondary surface (cards on page). |
786
- | `--color-bg-tertiary` | Tertiary surface (inset wells). |
787
- | `--color-bg-inverse` | Inverted surface (tooltip, toast). |
788
- | `--color-bg-brand` | Brand primary fill. |
789
- | `--color-bg-brand-hover` | Brand hover state. |
790
- | `--color-bg-brand-active` | Brand pressed state. |
791
- | `--color-bg-brand-subtle` | Tinted brand surface (info backgrounds, badges). |
792
- | `--color-bg-success` | Soft success surface. |
793
- | `--color-bg-success-strong` | Solid success fill. |
794
- | `--color-bg-warning` | Soft warning surface. |
795
- | `--color-bg-warning-strong` | Solid warning fill. |
796
- | `--color-bg-danger` | Soft danger surface. |
797
- | `--color-bg-danger-strong` | Solid danger fill (destructive buttons). |
798
- | `--color-bg-danger-hover` | Danger hover state. |
799
- | `--color-bg-info` | Info alert surface. |
800
- | `--color-bg-hover` | Neutral hover. |
801
- | `--color-bg-pressed` | Neutral pressed. |
802
- | `--color-bg-disabled` | Disabled surface. |
803
- | `--color-text-primary` | Body text. |
804
- | `--color-text-secondary` | Supporting text. |
805
- | `--color-text-tertiary` | Placeholder, hints. |
806
- | `--color-text-disabled` | Disabled text. |
807
- | `--color-text-inverse` | Text on `bg-inverse`. |
808
- | `--color-text-brand` | Brand-colored text (links). |
809
- | `--color-text-success` | Success copy. |
810
- | `--color-text-warning` | Warning copy. |
811
- | `--color-text-danger` | Error copy. |
812
- | `--color-text-on-brand` | Text on `bg-brand`. |
813
- | `--color-text-on-danger` | Text on `bg-danger-strong`. |
814
- | `--color-text-on-success` | Text on `bg-success-strong`. |
815
- | `--color-border-primary` | Default form/control border. |
816
- | `--color-border-secondary` | Section dividers, cards. |
817
- | `--color-border-tertiary` | Subtle inner dividers. |
818
- | `--color-border-brand` | Selected/active accent. |
819
- | `--color-border-success` | Success accent. |
820
- | `--color-border-warning` | Warning accent. |
821
- | `--color-border-danger` | Error accent (invalid inputs). |
822
- | `--color-border-focus` | Focus ring color. |
1086
+ | Token | Purpose |
1087
+ | --------------------------- | ------------------------------------------------ |
1088
+ | `--color-bg-primary` | Page background. |
1089
+ | `--color-bg-secondary` | Secondary surface (cards on page). |
1090
+ | `--color-bg-tertiary` | Tertiary surface (inset wells). |
1091
+ | `--color-bg-inverse` | Inverted surface (tooltip, toast). |
1092
+ | `--color-bg-brand` | Brand primary fill. |
1093
+ | `--color-bg-brand-hover` | Brand hover state. |
1094
+ | `--color-bg-brand-active` | Brand pressed state. |
1095
+ | `--color-bg-brand-subtle` | Tinted brand surface (info backgrounds, badges). |
1096
+ | `--color-bg-success` | Soft success surface. |
1097
+ | `--color-bg-success-strong` | Solid success fill. |
1098
+ | `--color-bg-warning` | Soft warning surface. |
1099
+ | `--color-bg-warning-strong` | Solid warning fill. |
1100
+ | `--color-bg-danger` | Soft danger surface. |
1101
+ | `--color-bg-danger-strong` | Solid danger fill (destructive buttons). |
1102
+ | `--color-bg-danger-hover` | Danger hover state. |
1103
+ | `--color-bg-info` | Info alert surface. |
1104
+ | `--color-bg-hover` | Neutral hover. |
1105
+ | `--color-bg-pressed` | Neutral pressed. |
1106
+ | `--color-bg-disabled` | Disabled surface. |
1107
+ | `--color-text-primary` | Body text. |
1108
+ | `--color-text-secondary` | Supporting text. |
1109
+ | `--color-text-tertiary` | Placeholder, hints. |
1110
+ | `--color-text-disabled` | Disabled text. |
1111
+ | `--color-text-inverse` | Text on `bg-inverse`. |
1112
+ | `--color-text-brand` | Brand-colored text (links). |
1113
+ | `--color-text-success` | Success copy. |
1114
+ | `--color-text-warning` | Warning copy. |
1115
+ | `--color-text-danger` | Error copy. |
1116
+ | `--color-text-on-brand` | Text on `bg-brand`. |
1117
+ | `--color-text-on-danger` | Text on `bg-danger-strong`. |
1118
+ | `--color-text-on-success` | Text on `bg-success-strong`. |
1119
+ | `--color-border-primary` | Default form/control border. |
1120
+ | `--color-border-secondary` | Section dividers, cards. |
1121
+ | `--color-border-tertiary` | Subtle inner dividers. |
1122
+ | `--color-border-brand` | Selected/active accent. |
1123
+ | `--color-border-success` | Success accent. |
1124
+ | `--color-border-warning` | Warning accent. |
1125
+ | `--color-border-danger` | Error accent (invalid inputs). |
1126
+ | `--color-border-focus` | Focus ring color. |
823
1127
 
824
1128
  ### Primitive palettes
825
1129
 
826
1130
  These are the raw color swatches that the semantic tokens reference. You usually shouldn't touch them directly, but they're exposed if you need to.
827
1131
 
828
- | Family | Stops | Notes |
829
- |---|---|---|
830
- | `--slate-*` | 50–950 | Default gray family. |
831
- | `--neutral-*` | 50–950 | True neutral (no temperature). |
832
- | `--stone-*` | 50–950 | Warm gray. |
833
- | `--blue-*` | 50–950 | Brand option. |
834
- | `--violet-*` | 50–950 | Brand option. |
835
- | `--emerald-*` | 50–950 | Brand option. |
836
- | `--orange-*` | 50–950 | Brand option. |
837
- | `--green-*` | 50–950 | Success palette. |
838
- | `--amber-*` | 50–950 | Warning palette. |
839
- | `--red-*` | 50–950 | Danger palette. |
840
- | `--white`, `--black` | — | Pure values. |
1132
+ | Family | Stops | Notes |
1133
+ | -------------------- | ------ | ------------------------------ |
1134
+ | `--slate-*` | 50–950 | Default gray family. |
1135
+ | `--neutral-*` | 50–950 | True neutral (no temperature). |
1136
+ | `--stone-*` | 50–950 | Warm gray. |
1137
+ | `--blue-*` | 50–950 | Brand option. |
1138
+ | `--violet-*` | 50–950 | Brand option. |
1139
+ | `--emerald-*` | 50–950 | Brand option. |
1140
+ | `--orange-*` | 50–950 | Brand option. |
1141
+ | `--green-*` | 50–950 | Success palette. |
1142
+ | `--amber-*` | 50–950 | Warning palette. |
1143
+ | `--red-*` | 50–950 | Danger palette. |
1144
+ | `--white`, `--black` | — | Pure values. |
841
1145
 
842
1146
  Aliases follow the active `data-*` attribute: `--gray-*` resolves to whichever gray family is selected, `--brand-*` to whichever brand. The `mono` and `teal` brands have special-case treatment for contrast on light/dark themes.
843
1147
 
@@ -845,15 +1149,15 @@ Aliases follow the active `data-*` attribute: `--gray-*` resolves to whichever g
845
1149
 
846
1150
  Selected by `[data-radius]`. Each scale rewrites the same custom properties.
847
1151
 
848
- | Token | default | sharp | soft | pill |
849
- |---|---|---|---|---|
850
- | `--radius-xs` | 2px | 0px | 4px | 4px |
851
- | `--radius-sm` | 4px | 1px | 8px | 9999px |
852
- | `--radius-md` | 6px | 2px | 12px | 9999px |
853
- | `--radius-lg` | 8px | 3px | 14px | 9999px |
854
- | `--radius-xl` | 12px | 4px | 18px | 18px |
855
- | `--radius-2xl` | 16px | 6px | 24px | 22px |
856
- | `--radius-full` | 9999px | 9999px | 9999px | 9999px |
1152
+ | Token | default | sharp | soft | pill |
1153
+ | --------------- | ------- | ------ | ------ | ------ |
1154
+ | `--radius-xs` | 2px | 0px | 4px | 4px |
1155
+ | `--radius-sm` | 4px | 1px | 8px | 9999px |
1156
+ | `--radius-md` | 6px | 2px | 12px | 9999px |
1157
+ | `--radius-lg` | 8px | 3px | 14px | 9999px |
1158
+ | `--radius-xl` | 12px | 4px | 18px | 18px |
1159
+ | `--radius-2xl` | 16px | 6px | 24px | 22px |
1160
+ | `--radius-full` | 9999px | 9999px | 9999px | 9999px |
857
1161
 
858
1162
  Plus `--ctrl-radius` — the control radius used by Button/Input/Select. Pills under `[data-tone="soft"]`, `--radius-md` elsewhere.
859
1163
 
@@ -861,33 +1165,33 @@ Plus `--ctrl-radius` — the control radius used by Button/Input/Select. Pills u
861
1165
 
862
1166
  Light theme uses cool slate tints; dark theme uses opaque black. The `soft` tone adds an inset white top-highlight on md/lg/xl. The focus ring tracks the brand color.
863
1167
 
864
- | Token | Purpose |
865
- |---|---|
866
- | `--shadow-xs` | Hairline lift. |
867
- | `--shadow-sm` | Subtle card. |
868
- | `--shadow-md` | Standard card. |
869
- | `--shadow-lg` | Popover, dropdown. |
870
- | `--shadow-xl` | Modal. |
1168
+ | Token | Purpose |
1169
+ | ---------------- | -------------------------------- |
1170
+ | `--shadow-xs` | Hairline lift. |
1171
+ | `--shadow-sm` | Subtle card. |
1172
+ | `--shadow-md` | Standard card. |
1173
+ | `--shadow-lg` | Popover, dropdown. |
1174
+ | `--shadow-xl` | Modal. |
871
1175
  | `--shadow-focus` | Focus ring (3–4px brand-tinted). |
872
1176
 
873
1177
  ### Typography
874
1178
 
875
- | Token | Value |
876
- |---|---|
877
- | `--font-size-xs` | 0.75rem |
878
- | `--font-size-sm` | 0.875rem |
879
- | `--font-size-md` | 1rem |
880
- | `--font-size-lg` | 1.125rem |
881
- | `--font-size-xl` | 1.25rem |
882
- | `--font-size-2xl` | 1.5rem |
883
- | `--font-size-3xl` | 1.875rem |
884
- | `--font-size-4xl` | 2.25rem |
885
- | `--font-size-5xl` | 3rem |
886
- | `--font-size-6xl` | 3.75rem |
887
- | `--line-height-tight` / `snug` / `normal` / `relaxed` | 1.15 / 1.3 / 1.5 / 1.65 |
888
- | `--letter-tight` / `snug` / `normal` / `wide` | -0.022em / -0.012em / 0 / 0.04em |
889
- | `--font-weight-regular` / `medium` / `semibold` / `bold` | 400 / 500 / 600 / 700 |
890
- | `--font-sans` / `--font-mono` | Set by `[data-font]` |
1179
+ | Token | Value |
1180
+ | -------------------------------------------------------- | -------------------------------- |
1181
+ | `--font-size-xs` | 0.75rem |
1182
+ | `--font-size-sm` | 0.875rem |
1183
+ | `--font-size-md` | 1rem |
1184
+ | `--font-size-lg` | 1.125rem |
1185
+ | `--font-size-xl` | 1.25rem |
1186
+ | `--font-size-2xl` | 1.5rem |
1187
+ | `--font-size-3xl` | 1.875rem |
1188
+ | `--font-size-4xl` | 2.25rem |
1189
+ | `--font-size-5xl` | 3rem |
1190
+ | `--font-size-6xl` | 3.75rem |
1191
+ | `--line-height-tight` / `snug` / `normal` / `relaxed` | 1.15 / 1.3 / 1.5 / 1.65 |
1192
+ | `--letter-tight` / `snug` / `normal` / `wide` | -0.022em / -0.012em / 0 / 0.04em |
1193
+ | `--font-weight-regular` / `medium` / `semibold` / `bold` | 400 / 500 / 600 / 700 |
1194
+ | `--font-sans` / `--font-mono` | Set by `[data-font]` |
891
1195
 
892
1196
  ### Spacing
893
1197
 
@@ -897,30 +1201,30 @@ Light theme uses cool slate tints; dark theme uses opaque black. The `soft` tone
897
1201
 
898
1202
  Selected by `[data-density]`.
899
1203
 
900
- | Token | default | compact | comfortable |
901
- |---|---|---|---|
902
- | `--control-h-sm` | 28px | 24px | 32px |
903
- | `--control-h-md` | 36px | 30px | 42px |
904
- | `--control-h-lg` | 44px | 38px | 52px |
905
- | `--control-px-sm` | 10px | 8px | 12px |
906
- | `--control-px-md` | 14px | 12px | 18px |
907
- | `--control-px-lg` | 18px | 16px | 22px |
908
- | `--card-p` | 24px | 16px | 32px |
909
- | `--row-gap` | 16px | 12px | 20px |
1204
+ | Token | default | compact | comfortable |
1205
+ | ----------------- | ------- | ------- | ----------- |
1206
+ | `--control-h-sm` | 28px | 24px | 32px |
1207
+ | `--control-h-md` | 36px | 30px | 42px |
1208
+ | `--control-h-lg` | 44px | 38px | 52px |
1209
+ | `--control-px-sm` | 10px | 8px | 12px |
1210
+ | `--control-px-md` | 14px | 12px | 18px |
1211
+ | `--control-px-lg` | 18px | 16px | 22px |
1212
+ | `--card-p` | 24px | 16px | 32px |
1213
+ | `--row-gap` | 16px | 12px | 20px |
910
1214
 
911
1215
  ### Motion
912
1216
 
913
- | Token | Value |
914
- |---|---|
915
- | `--duration-instant` | 50ms |
916
- | `--duration-fast` | 120ms |
917
- | `--duration-normal` | 200ms |
918
- | `--duration-slow` | 320ms |
919
- | `--duration-slower` | 500ms |
920
- | `--ease-linear` | `linear` |
921
- | `--ease-out` | `cubic-bezier(0.16, 1, 0.3, 1)` |
922
- | `--ease-in-out` | `cubic-bezier(0.65, 0, 0.35, 1)` |
923
- | `--ease-spring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` |
1217
+ | Token | Value |
1218
+ | -------------------- | ----------------------------------- |
1219
+ | `--duration-instant` | 50ms |
1220
+ | `--duration-fast` | 120ms |
1221
+ | `--duration-normal` | 200ms |
1222
+ | `--duration-slow` | 320ms |
1223
+ | `--duration-slower` | 500ms |
1224
+ | `--ease-linear` | `linear` |
1225
+ | `--ease-out` | `cubic-bezier(0.16, 1, 0.3, 1)` |
1226
+ | `--ease-in-out` | `cubic-bezier(0.65, 0, 0.35, 1)` |
1227
+ | `--ease-spring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` |
924
1228
 
925
1229
  ### Browsing tokens visually
926
1230