@usememos/ui 26.11.0 → 26.11.2

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 (50) hide show
  1. package/README.md +1 -1
  2. package/dist/components/button/index.js +1 -1
  3. package/dist/components/button/index.js.map +3 -3
  4. package/dist/components/combobox/index.js +1 -1
  5. package/dist/components/combobox/index.js.map +3 -3
  6. package/dist/components/dialog/index.js.map +2 -2
  7. package/dist/components/dialog/parts.js +1 -1
  8. package/dist/components/dialog/parts.js.map +3 -3
  9. package/dist/components/drawer/index.js +3 -0
  10. package/dist/components/drawer/index.js.map +7 -0
  11. package/dist/components/field/index.js +1 -1
  12. package/dist/components/field/index.js.map +3 -3
  13. package/dist/components/list/index.js +3 -0
  14. package/dist/components/list/index.js.map +7 -0
  15. package/dist/components/menu/classes.js +1 -1
  16. package/dist/components/menu/classes.js.map +2 -2
  17. package/dist/components/menu/index.js +1 -1
  18. package/dist/components/menu/index.js.map +3 -3
  19. package/dist/components/popover/classes.js +1 -1
  20. package/dist/components/popover/classes.js.map +3 -3
  21. package/dist/components/popover/index.js +1 -1
  22. package/dist/components/popover/index.js.map +3 -3
  23. package/dist/components/select/index.js +1 -1
  24. package/dist/components/select/index.js.map +3 -3
  25. package/dist/components/table/index.js +1 -1
  26. package/dist/components/table/index.js.map +3 -3
  27. package/dist/components/toast/index.js +1 -1
  28. package/dist/components/toast/index.js.map +3 -3
  29. package/dist/index.js +1 -1
  30. package/dist/index.js.map +3 -3
  31. package/dist/styles.css +1 -1
  32. package/dist/tokens.css +1 -1
  33. package/dist/types/components/button/index.d.ts +2 -0
  34. package/dist/types/components/combobox/index.d.ts +8 -0
  35. package/dist/types/components/dialog/index.d.ts +3 -3
  36. package/dist/types/components/dialog/parts.d.ts +9 -1
  37. package/dist/types/components/drawer/index.d.ts +42 -0
  38. package/dist/types/components/field/index.d.ts +6 -0
  39. package/dist/types/components/list/index.d.ts +16 -0
  40. package/dist/types/components/menu/classes.d.ts +4 -0
  41. package/dist/types/components/menu/index.d.ts +2 -0
  42. package/dist/types/components/popover/classes.d.ts +6 -1
  43. package/dist/types/components/popover/index.d.ts +7 -0
  44. package/dist/types/components/select/index.d.ts +5 -0
  45. package/dist/types/components/table/index.d.ts +2 -0
  46. package/dist/types/components/toast/index.d.ts +1 -1
  47. package/dist/types/index.d.ts +4 -1
  48. package/llms-full.txt +641 -36
  49. package/llms.txt +4 -1
  50. package/package.json +1 -1
package/llms-full.txt CHANGED
@@ -25,7 +25,8 @@ It is not a general-purpose UI library for outside developers. Components are ge
25
25
  | [Motion](motion.md) | Press, settle and fade timing, and reduced motion |
26
26
  | [States](states.md) | Hover, pressed, loading, disabled, invalid and focus |
27
27
  | [Icons](icons.md) | Lucide, sizes, fixed stroke widths and accessibility |
28
- | [Components](components/README.md) | The 28 components, what was left out, and one page per component |
28
+ | [Components](components/README.md) | The 30 components, what was left out, and one page per component |
29
+ | [Using the components](usage.md) | Which component to pick (overlays, controls, feedback), recipes for combining them, size, emphasis, an accessibility checklist, common mistakes and a map from other UI kits |
29
30
  | [Patterns](patterns.md) | Page, form, entry, confirmation, loading, empty state and mobile |
30
31
  | [Building on it](building.md) | Base UI, loading the CSS, scoping inside a host page, theme selection and API conventions |
31
32
  | [Building your own component](custom-components.md) | How an app builds a part the package doesn't ship: Base UI, class functions, public classes, row variables and Tailwind CSS |
@@ -69,7 +70,7 @@ It is not a general-purpose UI library for outside developers. Components are ge
69
70
  ## What to avoid
70
71
 
71
72
  - A brand hue on controls, or blue anywhere at rest except links.
72
- - Cards and boxes around content. Group with spacing, a heading and a `Separator`.
73
+ - Cards and boxes around content. Group with spacing, a heading and a `Separator`. The exceptions are rows of settings or records: a [List](components/list.md), or a [Table](components/table.md) with `bordered`.
73
74
  - Textures, glass or blur behind text, and neumorphism.
74
75
  - Pills for buttons, inputs or badges.
75
76
  - Color as the only signal. Status colors always come with a word or an icon.
@@ -342,7 +343,7 @@ Every control takes one `size` prop: `xs`, `sm` or `md` (the default). There is
342
343
  | sm | `control-sm` 24px | 8px | 14px | 4px | 13 / 18px | `radius-sm` 4px | 16px |
343
344
  | md (default) | `control-md` 32px | 12px | 16px | 6px | 14 / 20px | `radius-md` 6px | 18px |
344
345
 
345
- - Use `sm` for dense toolbars, table rows and filters inside panels, and `md` everywhere else, including marketing pages, onboarding and sheets. Emphasis comes from the `primary` variant, not from a bigger control. On touch screens the hit area, not the visual size, grows.
346
+ - Use `sm` for dense toolbars, table rows and filters inside panels, and `md` everywhere else, including marketing pages, onboarding, sheets and an app's sidebar navigation. Emphasis comes from the `primary` variant, not from a bigger control. On touch screens the hit area, not the visual size, grows.
346
347
  - Use `xs` for secondary actions beside 13px text: actions on a row or an entry, inline formatting, filters in a tight header, checkboxes in a dense list. It is the height of a Badge or a Kbd, so they line up. Don't use `xs` for forms, for navigation, or for the main action of a screen.
347
348
  - Only Button, Toggle, ToggleGroup, Checkbox, RadioGroup and Radio, Switch, Avatar, Spinner and Skeleton take `xs`. Text entry, menus, tabs and tables are too cramped at 20px, so their `size` is `sm | md`, and an `xs` inherited from a parent renders them at `sm`.
348
349
  - The icon side of a control has 2px less padding, so text and icon look centered.
@@ -416,6 +417,7 @@ Buttons, inputs and badges are never pills.
416
417
  | `tooltip-width` | 240px | Maximum width of a tooltip |
417
418
  | `toast-width` | 360px | Width of a toast from `breakpoint-sm` up |
418
419
  | `dialog-width` | 440px | Default width of a dialog |
420
+ | `drawer-width` | 320px | Default width of a drawer from the start or end edge |
419
421
 
420
422
  - `content-width` and `sidebar-width` are suggested values for an app's page layout. No component uses them. An app that needs another width uses its own value instead of overriding the token.
421
423
  - The other widths are component defaults. To change one instance, pass `className` or `style` to it.
@@ -426,7 +428,7 @@ Two breakpoints. They are values, not tokens: CSS custom properties can't be use
426
428
 
427
429
  | Breakpoint | Value | Usage |
428
430
  | --- | --- | --- |
429
- | breakpoint-sm | 640px | Below this: one column, dialogs and menus become sheets, toasts sit bottom-center |
431
+ | breakpoint-sm | 640px | Below this: one column, dialogs and menus become sheets, toasts span the top |
430
432
  | breakpoint-md | 1024px | From this: the sidebar is fixed and the content column is centered |
431
433
 
432
434
  ```css
@@ -524,7 +526,11 @@ import { Pin, Trash2 } from "lucide-react";
524
526
  - **Color is `currentColor`.** Icons follow the text color: `ink` in controls, `ink-muted` in menu rows, the tone color in toasts.
525
527
  - **Icons sit in a fixed square.** In a menu row or a control, the icon square keeps labels aligned whether or not a row has an icon.
526
528
  - **Accessibility.** Icons are `aria-hidden`. Every icon-only Button or Toggle has an `aria-label`, and a Tooltip with the same text.
527
- - **Standalone icons.** Outside a component, add `className="mu-icon"` to get the same size and stroke rules.
529
+ - **Standalone icons.** Outside a component, give the icon a size class: `mu-icon-xs` (12px, 1.25px stroke), `mu-icon-sm` (14px, 1.25px) or `mu-icon-md` (16px, 1.5px). Each sets the box and the stroke together, so a heading's icon or an empty state's icon matches the icons in controls. `mu-icon` alone takes the size of the nearest size row (`mu-s-*`), or 16px outside one. Don't size an icon with a utility such as `size-4`: it changes the box but not the stroke.
530
+
531
+ ```tsx
532
+ <h2 className="mu-title"><Inbox className="mu-icon-sm" aria-hidden /> Inbox</h2>
533
+ ```
528
534
 
529
535
  ## Built-in glyphs
530
536
 
@@ -539,6 +545,7 @@ These components bring their own Lucide icons, so the app doesn't pass them:
539
545
  | Collapsible | `ChevronRight` (turns 90° when open) |
540
546
  | Toast | `Info`, `CircleCheck`, `TriangleAlert`, `CircleAlert`, `X` |
541
547
  | Field | `CircleAlert` (errors) |
548
+ | Dialog, Drawer | `X` |
542
549
  | Link | `ArrowUpRight` (external links) |
543
550
 
544
551
  ## Missing icons
@@ -549,15 +556,15 @@ When Lucide doesn't have an icon, draw it on Lucide's 24px grid with 2px round s
549
556
 
550
557
  # Components
551
558
 
552
- 28 components in five groups, chosen from what Base UI and other popular libraries provide and what our products need today. Every component is generic: it works the same in memos, the website, the web clipper and any future product.
559
+ 30 components in five groups, chosen from what Base UI and other popular libraries provide and what our products need today. Every component is generic: it works the same in memos, the website, the web clipper and any future product.
553
560
 
554
561
  | Group | Components |
555
562
  | --- | --- |
556
563
  | Actions | [Button](button.md), [Toggle](toggle.md), [ToggleGroup](toggle-group.md), [Menu](menu.md), [Link](link.md) |
557
564
  | Forms | [Field and Fieldset](field.md), [Input](input.md), [Textarea](textarea.md), [Select](select.md), [Combobox](combobox.md), [Checkbox](checkbox.md), [Switch](switch.md), [RadioGroup](radio-group.md) |
558
565
  | Navigation | [Tabs](tabs.md), [Collapsible](collapsible.md) |
559
- | Overlays | [Dialog](dialog.md) (and its sheet form), [Popover](popover.md), [Tooltip](tooltip.md), [Toast](toast.md) |
560
- | Display | [Avatar](avatar.md), [Badge](badge.md), [Kbd](kbd.md), [Separator](separator.md), [Spinner](spinner.md), [Skeleton](skeleton.md), [Table](table.md), [Prose](prose.md), plus [Lucide icons](../icons.md) |
566
+ | Overlays | [Dialog](dialog.md) (and its sheet form), [Drawer](drawer.md), [Popover](popover.md), [Tooltip](tooltip.md), [Toast](toast.md) |
567
+ | Display | [Avatar](avatar.md), [Badge](badge.md), [Kbd](kbd.md), [Separator](separator.md), [Spinner](spinner.md), [Skeleton](skeleton.md), [Table](table.md), [List](list.md), [Prose](prose.md), plus [Lucide icons](../icons.md) |
561
568
 
562
569
  ## Page template
563
570
 
@@ -574,13 +581,15 @@ Components follow the [API conventions](../building.md#api-conventions): parts w
574
581
 
575
582
  ## Left out on purpose
576
583
 
577
- - **Card.** Content is grouped by spacing, headings and separators, not boxes.
584
+ - **Card.** Content is grouped by spacing, headings and separators. The only boxes are a [List](list.md) of settings or records and a `bordered` [Table](table.md).
578
585
  - **Slider, Stepper.** No setting in our products needs a continuous range. Use an Input with `type="number"` or a Select.
579
586
  - **Chip, SegmentedControl, Accordion.** A Toggle covers filter chips, a single-choice ToggleGroup covers segmented choices, and stacked Collapsibles cover an accordion.
580
587
  - **Later, when a screen needs them:** multi-select Combobox, ContextMenu, ScrollArea, Progress, Calendar.
581
588
 
582
589
  ## Choosing between similar components
583
590
 
591
+ The short version. [Using the components](../usage.md) has the full decision guides, recipes and common mistakes.
592
+
584
593
  | Need | Use |
585
594
  | --- | --- |
586
595
  | Start an action | Button |
@@ -596,8 +605,11 @@ Components follow the [API conventions](../building.md#api-conventions): parts w
596
605
  | A list of actions for one thing | Menu |
597
606
  | A few controls that belong to one thing | Popover |
598
607
  | A decision that blocks the page | Dialog |
608
+ | Navigation or details that slide in from an edge | Drawer |
599
609
  | Name an icon-only control | Tooltip |
600
610
  | Confirm something happened | Toast |
611
+ | Settings rows, or a short list of records with actions | List |
612
+ | Data with columns | Table |
601
613
  | A status or tag label | Badge |
602
614
  | A clickable tag filter | Toggle |
603
615
  | Content still loading | Skeleton |
@@ -635,7 +647,8 @@ import { Plus } from "lucide-react";
635
647
  interface ButtonProps extends BaseButton.Props {
636
648
  variant?: "primary" | "neutral" | "ghost" | "danger"; // default "neutral"
637
649
  size?: Size;
638
- icon?: IconElement;
650
+ icon?: IconElement; // before the label
651
+ iconEnd?: IconElement; // after the label: "Show more" with a chevron, "Next" with an arrow
639
652
  loading?: boolean;
640
653
  loadingLabel?: string; // accessible name while loading, such as "Saving"
641
654
  }
@@ -650,6 +663,7 @@ Wraps Base UI's [Button](https://base-ui.com/react/components/button): `disabled
650
663
  - Label with a verb in sentence case ("Save", "Copy link"). With only an `icon`, pass `aria-label` and add a [Tooltip](tooltip.md) with the same text.
651
664
  - `primary`: the one action a view exists for, at most one per view. `neutral`: every other action. `ghost`: toolbars, dense rows, the "More actions" trigger. `danger`: confirming a destructive action inside a dialog, never as a first click.
652
665
  - Use `loading` for any action that waits on a server.
666
+ - `iconEnd` says where the action leads or that it opens something (a chevron, an arrow). Don't put an icon in `children`: the label is one text span, so it would wrap. Use `icon` for what the action is.
653
667
  - A link that should look like a button stays an `<a>` with `buttonClasses()`; Base UI doesn't render links through Button:
654
668
 
655
669
  ```tsx
@@ -657,11 +671,12 @@ Wraps Base UI's [Button](https://base-ui.com/react/components/button): `disabled
657
671
  ```
658
672
 
659
673
  - Prefer saying why an action is unavailable over disabling it.
674
+ - Over a photo or video (an image viewer's close and arrows, a cover's edit button), use `neutral` Buttons inside an element with `data-theme="dark"`, so they are dark keys whatever the app's theme. There is no media variant. See [the recipe](../usage.md#buttons-over-a-photo).
660
675
 
661
676
  ## Style
662
677
 
663
678
  - A raised key: 20, 24 or 32px tall. `primary` is an ink key with `on-primary` text; `neutral` a `key` with a soft edge; `ghost` flat `ink-muted` with `hover` on hover; `danger` a `danger-key` with `on-danger` text.
664
- - Only an icon: square. The icon side has 2px less padding.
679
+ - Only an icon: square. The side with an icon (`icon` at the start, `iconEnd` at the end) has 2px less padding.
665
680
  - Press moves the key down 0.5px and swaps its shadow for `shadow-pressed`; a ghost button sinks into a `well`. Under reduced motion the key doesn't move; the shadow still changes.
666
681
  - `loading` keeps the width, shows a Spinner over the label, sets `aria-busy` and `aria-disabled`, ignores presses and stays focusable.
667
682
  - Disabled: 45% opacity.
@@ -702,6 +717,7 @@ Wraps Base UI's [Toggle](https://base-ui.com/react/components/toggle): `pressed`
702
717
  ## Style
703
718
 
704
719
  - Off: flat `ink-muted`. On: sits in a `well` with `ink` text.
720
+ - A label shrinks with an ellipsis when its row runs out of room, as Select's value does; an icon-only toggle never shrinks. A ToggleGroup is at most as wide as its container, so long translated labels truncate instead of overflowing.
705
721
 
706
722
  <!-- docs/components/toggle-group.md -->
707
723
 
@@ -757,6 +773,7 @@ Base UI's props pass through: `value`, `defaultValue`, `onValueChange`, `multipl
757
773
 
758
774
  ## Style
759
775
 
776
+ - At most as wide as its container. When the labels don't fit, they shrink with an ellipsis; icon-only items keep their size.
760
777
  - `multiple`: items 2px apart.
761
778
  - Without `multiple`: a segmented well; the key slides to the chosen item over `duration-settle` with `ease-settle` and re-measures when an item changes width. Under reduced motion it jumps.
762
779
 
@@ -795,7 +812,7 @@ import { Ellipsis, FolderInput, Pin, Trash2 } from "lucide-react";
795
812
  | `Menu.Root` | `Menu.Root` | Nothing. Groups a trigger and its popup. Takes `size` |
796
813
  | `Menu.Trigger` | `Menu.Trigger` | The control that opens it, usually a Button through `render` |
797
814
  | `Menu.Popup` | `Menu.Portal`, `Positioner`, `Popup` | The list, a `<div role="menu">` portaled to the body. Also a submenu's list |
798
- | `Menu.Item` | `Menu.Item` | One action. Takes `icon`, `shortcut`, `danger` |
815
+ | `Menu.Item` | `Menu.Item` | One action. Takes `icon`, `shortcut`, `description`, `danger` |
799
816
  | `Menu.CheckboxItem` | `Menu.CheckboxItem` with its indicator | An option that turns on or off, with a check at the end |
800
817
  | `Menu.RadioGroup` | `Menu.RadioGroup` | Groups RadioItems; holds the value |
801
818
  | `Menu.RadioItem` | `Menu.RadioItem` with its indicator | One choice, with a check at the end when chosen |
@@ -818,12 +835,13 @@ interface Menu.PopupProps extends BaseMenu.Popup.Props {
818
835
  interface Menu.ItemProps extends BaseMenu.Item.Props {
819
836
  icon?: IconElement;
820
837
  shortcut?: React.ReactNode; // at the end of the row, "⌘ D"
838
+ description?: React.ReactNode; // a line of ink-muted text under the label; the row grows to two lines
821
839
  danger?: boolean; // destructive: red text
822
840
  }
823
- // CheckboxItem and RadioItem take icon and shortcut too. SubmenuTrigger takes icon.
841
+ // CheckboxItem and RadioItem take icon, shortcut and description too. SubmenuTrigger takes icon.
824
842
 
825
843
  function menuClasses(options?: { size?: "sm" | "md" }): {
826
- popup: string; item: string; dangerItem: string; label: string;
844
+ popup: string; item: string; dangerItem: string; label: string; text: string; description: string;
827
845
  shortcut: string; separator: string; groupLabel: string;
828
846
  };
829
847
  ```
@@ -834,15 +852,18 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on
834
852
 
835
853
  - Act in `onClick`. Labels are verbs in sentence case.
836
854
  - Put destructive items last, after a Separator, with `danger`. Confirm them with an [AlertDialog](dialog.md).
837
- - CheckboxItems and RadioItems stay open on click, so people can change several.
838
- - For picking a value saved with a form, use [Select](select.md).
855
+ - CheckboxItems and RadioItems stay open on click, so people can change several. A RadioItem that picks one setting and is done (a visibility picker) takes `closeOnClick`.
856
+ - `description` explains a choice in a few words ("Only you can see it"). The item is named by its label and described by the description. Keep descriptions to one short line; they wrap when longer.
857
+ - For picking a value saved with a form, use [Select](select.md). For content that stays open while people use it (inputs, text, several controls), use [Popover](popover.md). See [Menu or Popover](../usage.md#menu-or-popover).
858
+ - To open a Dialog from an item, keep the Dialog outside the Menu and control its `open`. See [the recipe](../usage.md#a-menu-item-that-opens-a-dialog).
839
859
  - Arrow keys move the highlight; typing jumps to an item; Escape and an outside click close it; focus returns to the trigger.
840
- - An app's own floating list, such as an editor's suggestion list, takes `menuClasses()`: `popup` on the list, `item` on each row, and `data-highlighted` on the active row. See [Class functions](../custom-components.md#class-functions).
860
+ - An app's own floating list, such as an editor's suggestion list, takes `menuClasses()`: `popup` on the list, `item` on each row, and `data-highlighted` on the active row. `item` works on a `<div>`, a `<button>` or an `<a>`: it resets their width, border, fill, font and centered text. See [Class functions](../custom-components.md#class-functions).
841
861
 
842
862
  ## Style
843
863
 
844
864
  - `mu-popup` with `space-1` padding, at least `menu-width` wide, scrolls when taller than the space available.
845
- - Rows are a control height (24 or 32px), text weight 400; icons in `ink-muted`; the shortcut right-aligned in `ink-muted`.
865
+ - Rows are a control height (24 or 32px), text weight 400; icons in `ink-muted`; the shortcut right-aligned in `ink-muted`. Text starts at the start edge.
866
+ - A row with a description grows: the label, then the description in `caption` and `ink-muted`, with the row's vertical padding above and below. The icon and the check stay on the label's line.
846
867
  - The highlighted row uses `hover`. Disabled rows fade to 45%. `danger` rows use `danger`.
847
868
  - A ghost trigger keeps its `hover` background while the menu is open.
848
869
  - Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
@@ -919,7 +940,7 @@ import { Checkbox, Field, Fieldset, Input } from "@usememos/ui";
919
940
 
920
941
  | Part | Base UI part | Renders |
921
942
  | --- | --- | --- |
922
- | `Field.Root` | `Field.Root` | A `<div>` around one control. Takes `size` and passes it to the control |
943
+ | `Field.Root` | `Field.Root` | A `<div>` around one control. Takes `size` and passes it to the control, and `orientation` |
923
944
  | `Field.Label` | `Field.Label` | A `<label>`: above a text control, or around a Checkbox, Switch or Radio |
924
945
  | `Field.Item` | `Field.Item` | A `<div>` around one option of a radio or checkbox group in one field |
925
946
  | `Field.Description` | `Field.Description` | A `<p>` of help under the control |
@@ -932,6 +953,7 @@ import { Checkbox, Field, Fieldset, Input } from "@usememos/ui";
932
953
  ```ts
933
954
  interface Field.RootProps extends BaseField.Root.Props {
934
955
  size?: "sm" | "md"; // passed to the control inside
956
+ orientation?: "vertical" | "horizontal"; // default "vertical"; horizontal: label first, control at the end of the row
935
957
  }
936
958
  interface Fieldset.RootProps extends BaseFieldset.Root.Props {
937
959
  size?: "sm" | "md"; // passed to every control inside
@@ -945,13 +967,23 @@ Base UI's props pass through: `name`, `invalid`, `disabled`, `validate`, `valida
945
967
  - Set `invalid` from the app when the server rejects a value; use `validate` for client checks.
946
968
  - Write the error as how to fix the problem.
947
969
  - Put a Checkbox, Switch or Radio inside its `Field.Label`, so clicking the text toggles it.
970
+ - For a settings row (a name, an optional description, and a Switch or Select at the end), use `orientation="horizontal"` and put the control after `Field.Label` and `Field.Description`, not inside the label:
971
+
972
+ ```tsx
973
+ <Field.Root orientation="horizontal">
974
+ <Field.Label>Compact mode</Field.Label>
975
+ <Field.Description>Show more notes on one screen.</Field.Description>
976
+ <Switch checked={compact} onCheckedChange={setCompact} />
977
+ </Field.Root>
978
+ ```
948
979
  - A radio group is one field: `Field.Root` holds `Fieldset.Root render={<RadioGroup />}`, and each radio sits in a `Field.Item`. See [RadioGroup](radio-group.md).
949
- - Stack fields `space-4` apart. Group them with a heading or a Fieldset, not boxes.
980
+ - Stack fields `space-4` apart. Group them with a heading or a Fieldset. On a settings page, put horizontal Fields in a [List](list.md), one per Item.
950
981
  - Never use a placeholder as the only label.
951
982
 
952
983
  ## Style
953
984
 
954
985
  - Label: `label` style (14px, 500), `space-1` above the control; weight 400 when it wraps a Checkbox, Switch or Radio.
986
+ - Horizontal: a two-column grid. The label (weight 400) and description fill the first column; the control sits at the end, centered on them, `space-4` from the text. The error spans the row under them.
955
987
  - Description and error: `caption` (13px), below the control. The error hides the description and turns the control's edge `danger`.
956
988
 
957
989
  <!-- docs/components/input.md -->
@@ -1072,7 +1104,7 @@ const visibility = { private: "Private", workspace: "Workspace", public: "Public
1072
1104
  | Part | Base UI part | Renders |
1073
1105
  | --- | --- | --- |
1074
1106
  | `Select.Root` | `Select.Root` | Nothing. Holds the value and a hidden input for forms. Takes `size` |
1075
- | `Select.Trigger` | `Select.Trigger` with `Select.Icon` | A neutral key with a chevron at the end, a `<button role="combobox">` |
1107
+ | `Select.Trigger` | `Select.Trigger` with `Select.Icon` | A neutral key with a chevron at the end, a `<button role="combobox">`. Takes `variant` |
1076
1108
  | `Select.Value` | `Select.Value` | The chosen item's label, or the placeholder |
1077
1109
  | `Select.Popup` | `Select.Portal`, `Positioner`, `Popup`, `List`, scroll arrows | The list, portaled to the body |
1078
1110
  | `Select.Item` | `Select.Item` with `ItemText` and `ItemIndicator` | One option, with a check at the end when chosen. Takes `icon` |
@@ -1086,6 +1118,9 @@ const visibility = { private: "Private", workspace: "Workspace", public: "Public
1086
1118
  type Select.RootProps<Value, Multiple> = BaseSelect.Root.Props<Value, Multiple> & {
1087
1119
  size?: "sm" | "md";
1088
1120
  };
1121
+ interface Select.TriggerProps extends BaseSelect.Trigger.Props {
1122
+ variant?: "neutral" | "ghost"; // default "neutral"; ghost: a flat row, for a switcher in a sidebar or toolbar
1123
+ }
1089
1124
  interface Select.PopupProps extends BaseSelect.Popup.Props {
1090
1125
  alignItemWithTrigger?: boolean; // default true: the list opens over the trigger with the chosen item on the value
1091
1126
  side?: Side; align?: Align; // used when alignItemWithTrigger is false
@@ -1104,10 +1139,11 @@ Base UI's props pass through: `items`, `value`, `defaultValue`, `onValueChange`,
1104
1139
  - Label it with a [Field](field.md), or `aria-label` on the trigger.
1105
1140
  - Use [RadioGroup](radio-group.md) for two or three options with room to show them, and [Combobox](combobox.md) for more than about 10 or when people type to find.
1106
1141
  - For actions, use [Menu](menu.md).
1142
+ - `variant="ghost"` draws the trigger as a row instead of a key, for a value people switch in place, such as the current space in a sidebar. Put an icon or Avatar before `Select.Value`. In a form, keep `neutral`.
1107
1143
 
1108
1144
  ## Style
1109
1145
 
1110
- - Trigger: a neutral key ([Button](button.md) `neutral`), value left-aligned, `ChevronsUpDown` in `ink-muted`. Placeholder in `ink-muted`, weight 400. Invalid: a `danger` edge.
1146
+ - Trigger: a neutral key ([Button](button.md) `neutral`), value left-aligned, `ChevronsUpDown` in `ink-muted`. `ghost`: a ghost Button with `ink` text, `hover` on hover and while open. Placeholder in `ink-muted`, weight 400. Invalid: a `danger` edge.
1111
1147
  - List: `mu-popup`, `space-1` padding, at least `menu-width` and the trigger's width. Rows like [Menu](menu.md) items; the highlighted row uses `hover`.
1112
1148
  - Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
1113
1149
 
@@ -1153,6 +1189,7 @@ const tags = ["book", "design", "travel"];
1153
1189
  | `Combobox.Group` | `Combobox.Group` | A labeled set of items |
1154
1190
  | `Combobox.GroupLabel` | `Combobox.GroupLabel` | The name of a Group |
1155
1191
  | `Combobox.Separator` | `Combobox.Separator` | A line between groups |
1192
+ | `Combobox.Row` | `Combobox.Row` | One row of a grid, with `grid` on the root: square cells for icons or emoji |
1156
1193
  | `Combobox.Collection` | `Combobox.Collection` | Base UI's part, unstyled: renders one group's items from grouped `items` |
1157
1194
  | `Combobox.useFilter` | `Combobox.useFilter` | Base UI's hook: locale-aware `contains`, `startsWith`, `endsWith` for `filter` |
1158
1195
 
@@ -1187,6 +1224,26 @@ Base UI's props pass through: `items`, `value`, `defaultValue`, `onValueChange`,
1187
1224
  - Use it for more than about 10 options, or when people type to find one. For fewer, use [Select](select.md).
1188
1225
  - Typing opens the list. Up and Down move the highlight, Enter picks, Escape closes; focus stays in the input.
1189
1226
  - Multi-select (chips) is not built.
1227
+ - With `inline` on the root, the list sits in the page instead of a popup: put `Input`, `Empty` and `List` directly inside the root, with no `Popup`. The root is then open unless you pass `open`, and the Input drops its chevron. Use it for a picker inside a Popover or a Dialog.
1228
+ - With `grid` on the root, split the filtered items into rows of a fixed count and wrap each in `Combobox.Row`. Arrow keys then move across rows and columns, and Enter picks. Give each cell an `aria-label`. This is the icon and emoji picker:
1229
+
1230
+ ```tsx
1231
+ <Combobox.Root items={icons} inline grid value={icon} onValueChange={pick} inputValue={query} onInputValueChange={setQuery} itemToStringLabel={(item) => item.label}>
1232
+ <Combobox.Input placeholder="Find an icon" aria-label="Find an icon" />
1233
+ <Combobox.Empty>No icons found</Combobox.Empty>
1234
+ <Combobox.List aria-label="Icons">
1235
+ {rows(icons.filter((item) => contains(item.label, query)), 8).map((row, index) => (
1236
+ <Combobox.Row key={index}>
1237
+ {row.map((item) => (
1238
+ <Combobox.Item key={item.value} value={item} aria-label={item.label}>{item.icon}</Combobox.Item>
1239
+ ))}
1240
+ </Combobox.Row>
1241
+ ))}
1242
+ </Combobox.List>
1243
+ </Combobox.Root>
1244
+ ```
1245
+
1246
+ The rows are rendered by the app, so filter them with the same query: keep it in `inputValue` and `onInputValueChange`, and match with `useFilter().contains`.
1190
1247
 
1191
1248
  ## Style
1192
1249
 
@@ -1194,6 +1251,7 @@ Base UI's props pass through: `items`, `value`, `defaultValue`, `onValueChange`,
1194
1251
  - List: `mu-popup`, `space-1` padding, as wide as the input and at least `menu-width`, at most `list-height` (240px) tall, then it scrolls.
1195
1252
  - Rows are a control height, text weight 400; the highlighted row uses `hover`; the chosen row has a check at the end.
1196
1253
  - Empty text: `body` in `ink-muted`, `space-3` by `space-4` padding.
1254
+ - Grid cells: square, a control height, `inset` apart, `row-radius`; the highlighted cell uses `hover`, the chosen cell sits in a `well` like a pressed Toggle. No check.
1197
1255
  - Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
1198
1256
 
1199
1257
  <!-- docs/components/checkbox.md -->
@@ -1493,10 +1551,11 @@ Dialog and AlertDialog have the same parts.
1493
1551
  ```ts
1494
1552
  interface Dialog.PopupProps extends BaseDialog.Popup.Props {
1495
1553
  sheet?: boolean; // below breakpoint-sm, a sheet from the bottom; default true
1554
+ variant?: "panel" | "bare"; // default "panel"; bare: no padding, gap or X (Dialog only)
1496
1555
  closeLabel?: string; // accessible name of the X; default "Close"
1497
1556
  container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null; // portal target
1498
1557
  }
1499
- // AlertDialog.Popup takes sheet and container; it has no X.
1558
+ // AlertDialog.Popup takes sheet and container; it has no X and no variant.
1500
1559
  ```
1501
1560
 
1502
1561
  Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `disablePointerDismissal` on the root; `initialFocus`, `finalFocus` on the popup.
@@ -1508,6 +1567,8 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `d
1508
1567
  - Use AlertDialog for confirmations; see the [confirmation pattern](../patterns.md#confirmation). Open it from a Menu item with `open` and `onOpenChange`.
1509
1568
  - Escape and a backdrop click close a Dialog. While a form has unsaved input, keep it open from `onOpenChange`.
1510
1569
  - Focus moves into the dialog and returns to the trigger on close.
1570
+ - `variant="bare"` is for content that runs edge to edge: a search palette, a picker with its own header, an image. The content sets its own padding. Name the dialog with a `Dialog.Title` (hidden with `mu-visually-hidden` if needed) or `aria-label` on the popup; Escape and a backdrop click still close it.
1571
+ - The panel is always md: controls inside it ignore the size of a Root the Dialog is declared in (a sm Table). Pass `size` to a control inside only when it should differ.
1511
1572
 
1512
1573
  ## Style
1513
1574
 
@@ -1515,6 +1576,70 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `d
1515
1576
  - Title: `title`. Description: `body` in `ink-muted`, `space-2` under the title. The X is a small ghost Button at `space-4` from the corner.
1516
1577
  - Fades in and rises `space-1` over `duration-settle`. A sheet slides up `space-6`, with square bottom corners and full-width buttons stacked with the action on top. No movement under reduced motion.
1517
1578
  - Long content scrolls the viewport around the panel.
1579
+ - `bare`: no padding, no gap and no X; the content is clipped to the radius. Set the width with `className` when it differs from `dialog-width`.
1580
+
1581
+ <!-- docs/components/drawer.md -->
1582
+
1583
+ # Drawer
1584
+
1585
+ A panel that slides in from one edge of the screen over a dimmed backdrop, such as a navigation sidebar on a phone or the details of one item.
1586
+
1587
+ ## Example
1588
+
1589
+ ```tsx
1590
+ import { Button, Drawer } from "@usememos/ui";
1591
+ import { Menu } from "lucide-react";
1592
+
1593
+ <Drawer.Root side="start" open={open} onOpenChange={setOpen}>
1594
+ <Drawer.Trigger render={<Button variant="ghost" icon={<Menu />} aria-label="Open navigation" />} />
1595
+ <Drawer.Popup variant="bare">
1596
+ <Drawer.Title className="mu-visually-hidden">Navigation</Drawer.Title>
1597
+ <AppSidebar />
1598
+ </Drawer.Popup>
1599
+ </Drawer.Root>
1600
+ ```
1601
+
1602
+ ## Parts
1603
+
1604
+ | Part | Base UI part | Renders |
1605
+ | --- | --- | --- |
1606
+ | `Drawer.Root` | `Drawer.Root` | Nothing. Holds the open state. Takes `side` |
1607
+ | `Drawer.Trigger` | `Drawer.Trigger` | The control that opens it, usually a Button through `render` |
1608
+ | `Drawer.Popup` | `Drawer.Portal`, `Backdrop`, `Viewport`, `Popup`, `Content` | The panel against the edge, over a dimmed backdrop, portaled to the body. Adds an X unless `variant="bare"` |
1609
+ | `Drawer.Title` | `Drawer.Title` | An `<h2>` that names the drawer |
1610
+ | `Drawer.Description` | `Drawer.Description` | A `<p>` under the title |
1611
+ | `Drawer.Close` | `Drawer.Close` | A control that closes it, such as each link of a navigation drawer |
1612
+
1613
+ ## Props
1614
+
1615
+ ```ts
1616
+ interface Drawer.RootProps extends BaseDrawer.Root.Props {
1617
+ side?: "start" | "end" | "bottom"; // the edge it slides from and swipes back to; default "end". start and end follow the text direction
1618
+ }
1619
+ interface Drawer.PopupProps extends BaseDrawer.Popup.Props {
1620
+ variant?: "panel" | "bare"; // default "panel"; bare: no padding, gap or X
1621
+ closeLabel?: string; // accessible name of the X; default "Close"
1622
+ container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null; // portal target
1623
+ }
1624
+ ```
1625
+
1626
+ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `onOpenChangeComplete`, `modal`, `disablePointerDismissal` on the root; `initialFocus`, `finalFocus` on the popup. `side` replaces Base UI's `swipeDirection`; snap points are not supported.
1627
+
1628
+ ## Rules
1629
+
1630
+ - Use it for content that belongs beside the page rather than on top of it: the navigation sidebar below `breakpoint-md`, or the details of one item. For a decision or a short form, use [Dialog](dialog.md), which already becomes a sheet from the bottom on phones.
1631
+ - Name it: a `Drawer.Title`, hidden with `mu-visually-hidden` when the content names itself, or `aria-label` on the popup.
1632
+ - `variant="bare"` is for content that lays itself out, such as an app's sidebar. Set its width with `className` when the content has its own, such as `sidebar-width`.
1633
+ - Close it when people pick a destination: wrap each link in `Drawer.Close render={<a … />}`, or close it from the router.
1634
+ - Escape, a backdrop click and a swipe toward its edge close it. Focus moves in and returns to the trigger.
1635
+ - The panel is always md, as a Dialog is.
1636
+
1637
+ ## Style
1638
+
1639
+ - `surface` with `shadow-pop`, full height and `drawer-width` (320px) wide from the start or end edge, at most the screen width minus `space-12`. Square corners; from the bottom it is full width with `radius-2xl` top corners and at most the screen height minus `space-6`.
1640
+ - `panel`: content `space-5` in, stacked `space-4` apart; the X is a small ghost Button at `space-4` from the top end corner. Title: `title`. Description: `body` in `ink-muted`.
1641
+ - The backdrop is `overlay`; it fades as the drawer is swiped away.
1642
+ - Slides in from its edge over `duration-settle` with `ease-settle` and follows the finger while swiped. Under reduced motion it fades instead.
1518
1643
 
1519
1644
  <!-- docs/components/popover.md -->
1520
1645
 
@@ -1554,9 +1679,10 @@ import { Button, Input, Popover } from "@usememos/ui";
1554
1679
  interface Popover.PopupProps extends BasePopover.Popup.Props {
1555
1680
  side?: Side; align?: Align; // Positioner props; default side "bottom"
1556
1681
  container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null; // portal target
1682
+ variant?: "panel" | "list"; // default "panel"; list: a Menu's padding and no gap, for rows
1557
1683
  }
1558
1684
 
1559
- function popoverClasses(): { popup: string; title: string; description: string };
1685
+ function popoverClasses(options?: { variant?: "panel" | "list" }): { popup: string; title: string; description: string };
1560
1686
  ```
1561
1687
 
1562
1688
  Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on the root; `openOnHover`, `delay` on the trigger; `initialFocus`, `finalFocus` on the popup.
@@ -1565,6 +1691,8 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on
1565
1691
 
1566
1692
  - Use it for a few controls that belong to one thing. For a list of actions, use [Menu](menu.md). For a decision that blocks the page, use [Dialog](dialog.md).
1567
1693
  - Give it a `Popover.Title`, or `aria-label` on the popup.
1694
+ - `variant="list"` is for a panel made of rows, such as an account panel (a name, then links) or a scope picker with a header. Draw the rows with `menuClasses().item` and the lines between them with `menuClasses().separator`. When the rows are only actions, use a [Menu](menu.md) instead.
1695
+ - The panel is always md: controls inside it ignore the size of the Root around the Popover (a sm toolbar). Pass `size` to a control inside only when it should differ.
1568
1696
  - It closes on Escape and on a click outside; focus returns to the trigger.
1569
1697
  - An app's own floating panel, such as an emoji picker, takes `popoverClasses()`. See [Class functions](../custom-components.md#class-functions).
1570
1698
 
@@ -1572,6 +1700,7 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on
1572
1700
 
1573
1701
  - `mu-popup` (`surface`, `shadow-pop`, `radius-xl`), `space-4` padding, `popover-width` wide. Children stack `space-3` apart.
1574
1702
  - Title: `label`. Description: `caption` in `ink-muted`, `space-1` under the title.
1703
+ - `list`: `space-1` padding and no gap, like a Menu. A title and description inside line up with the rows' text, with `space-2` above the title and below the description.
1575
1704
  - Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
1576
1705
 
1577
1706
  <!-- docs/components/tooltip.md -->
@@ -1624,6 +1753,7 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `disabled`
1624
1753
  - Never put essential information only in a tooltip: it doesn't open on touch screens.
1625
1754
  - For text that explains something, use a [Popover](popover.md) instead.
1626
1755
  - Wrap a toolbar, or the whole app, in one `Tooltip.Provider`.
1756
+ - On a trigger that also opens a Menu, Popover or Dialog, stack the triggers: `<Tooltip.Trigger render={<Menu.Trigger render={<Button … />} />} />`. See [the recipe](../usage.md#an-icon-only-button-with-a-tooltip-and-a-menu).
1627
1757
 
1628
1758
  ## Style
1629
1759
 
@@ -1686,9 +1816,9 @@ Base UI's props pass through: `timeout` (default 5000ms) and `limit` (default 3)
1686
1816
 
1687
1817
  ## Style
1688
1818
 
1689
- - `mu-popup`, `toast-width` wide, bottom-right at `space-6` from the edges; below `breakpoint-sm`, full width at `space-4`.
1819
+ - `mu-popup`, `toast-width` wide, top-right at `space-6` from the edges; below `breakpoint-sm`, full width across the top at `space-4`, below the status bar.
1690
1820
  - A tone icon (`Info`, `CircleCheck`, `TriangleAlert`, `CircleAlert`) in `ink-muted`, `success`, `warning` or `danger`; title in `label`; description in `caption` `ink-muted`; the action as a small neutral Button; the X as a small ghost Button.
1691
- - Newest in front; the ones behind peek out `space-2` above and shrink. Hover or focus fans them out `space-3` apart. Toasts slide in from below and can be swiped away. Under reduced motion they only fade.
1821
+ - Newest in front; the ones behind peek out `space-2` below and shrink. Hover or focus fans them out downward, `space-3` apart. Toasts slide in from above and can be swiped up or to the right. Under reduced motion they only fade.
1692
1822
 
1693
1823
  <!-- docs/components/avatar.md -->
1694
1824
 
@@ -1963,7 +2093,7 @@ No Base UI part: these are styled native table elements.
1963
2093
 
1964
2094
  | Part | Renders |
1965
2095
  | --- | --- |
1966
- | `Table.Root` | A `<table>` inside a `<div>` that scrolls sideways when the table is too wide. Takes `size`. The ref and props go to the `<table>` |
2096
+ | `Table.Root` | A `<table>` inside a `<div>` that scrolls sideways when the table is too wide. Takes `size` and `bordered`. The ref and props go to the `<table>` |
1967
2097
  | `Table.Caption` | `<caption>`, under the table. Names it for screen readers |
1968
2098
  | `Table.Header` | `<thead>` |
1969
2099
  | `Table.Body` | `<tbody>` |
@@ -1975,7 +2105,8 @@ No Base UI part: these are styled native table elements.
1975
2105
 
1976
2106
  ```ts
1977
2107
  interface Table.RootProps extends React.ComponentProps<"table"> {
1978
- size?: "sm" | "md"; // sm for dense admin lists
2108
+ size?: "sm" | "md"; // sm for dense admin lists
2109
+ bordered?: boolean; // a line edge and rounded corners around the table, for a table that sits among other sections
1979
2110
  }
1980
2111
  interface Table.HeadProps extends React.ComponentProps<"th"> {
1981
2112
  num?: boolean; // right-aligned, for a column of numbers
@@ -1995,14 +2126,79 @@ Every native attribute passes through.
1995
2126
  - Put row actions in the last column as a ghost [Menu](menu.md) trigger, named for its row. Give that column's Head text in `mu-visually-hidden`.
1996
2127
  - Name the table with a Caption, or `aria-label` on the root.
1997
2128
  - Sort, select and paginate in the app. The table has no data props.
2129
+ - Set `bordered` when the table is one section of a settings or admin page, next to other sections or a [List](list.md), so it reads as one block. Leave it off when the table is the page's main content. Never put a bordered table inside a Dialog or Popover.
1998
2130
 
1999
2131
  ## Style
2000
2132
 
2001
- - No boxes or stripes: a `line` under the head row and between body rows. Body rows highlight with `hover`.
2133
+ - No stripes: a `line` under the head row and between body rows. Body rows highlight with `hover`.
2134
+ - `bordered`: `surface` fill, a 1px `line` edge and `radius-lg` on the scrolling wrapper; the Caption sits inside the edge, under a `line`. The outer columns pad `space-1` more, so the text lines up with a [List](list.md) row's.
2002
2135
  - Heads: `label-sm` (13px, 500) in `ink-muted`, a control height tall.
2003
2136
  - Cells: `caption` at sm and `body` at md, with tabular numbers. Rows are 34 or 40px; a Button in a cell overlaps the padding, so rows keep that height.
2004
2137
  - Cells pad `px` at the sides. Caption: `caption` in `ink-muted`, `space-2` above.
2005
2138
 
2139
+ <!-- docs/components/list.md -->
2140
+
2141
+ # List
2142
+
2143
+ A boxed list of rows: settings, or records such as webhooks, sessions or linked accounts.
2144
+
2145
+ ## Example
2146
+
2147
+ ```tsx
2148
+ import { Field, List, Switch } from "@usememos/ui";
2149
+
2150
+ <h2 className="mu-label" id="preferences">Preferences</h2>
2151
+ <List.Root aria-labelledby="preferences">
2152
+ <List.Item>
2153
+ <Field.Root orientation="horizontal">
2154
+ <Field.Label>Compact mode</Field.Label>
2155
+ <Field.Description>Show more notes on one screen.</Field.Description>
2156
+ <Switch checked={compact} onCheckedChange={saveCompact} />
2157
+ </Field.Root>
2158
+ </List.Item>
2159
+ <List.Item>
2160
+ <Field.Root orientation="horizontal">
2161
+ <Field.Label>Show pinned first</Field.Label>
2162
+ <Switch checked={pinnedFirst} onCheckedChange={savePinnedFirst} />
2163
+ </Field.Root>
2164
+ </List.Item>
2165
+ </List.Root>
2166
+ ```
2167
+
2168
+ ## Parts
2169
+
2170
+ No Base UI part: these are styled native list elements.
2171
+
2172
+ | Part | Renders |
2173
+ | --- | --- |
2174
+ | `List.Root` | A `<ul>` with a `line` edge and rounded corners. Takes `size` |
2175
+ | `List.Item` | An `<li>`: one row, divided from the next by a `line` |
2176
+
2177
+ ## Props
2178
+
2179
+ ```ts
2180
+ interface List.RootProps extends React.ComponentProps<"ul"> {
2181
+ size?: "sm" | "md"; // sm for dense lists; also the size of the controls inside
2182
+ }
2183
+ ```
2184
+
2185
+ Every native attribute passes through.
2186
+
2187
+ ## Rules
2188
+
2189
+ - Use it for settings rows and for short lists of records with an action or two each. Data with several columns goes in a [Table](table.md); content people read (notes, comments) goes in [entries](../patterns.md#entry), never in a List.
2190
+ - A settings row is a horizontal [Field](field.md) inside the Item. A record row lays out its own content: the record's text first, its actions at the end (a ghost [Menu](menu.md) trigger named for the row, or one or two Buttons).
2191
+ - Name the list: `aria-labelledby` pointing at the section title above it, or `aria-label`.
2192
+ - Don't nest Lists, and don't put a List inside a Dialog or Popover: those are boxes already. Use horizontal Fields stacked `space-4` apart there.
2193
+ - Rows are not links or buttons. To open a record, put a Link or a Button in the row.
2194
+ - An empty list shows the [empty state](../patterns.md#empty-state) in place of the List.
2195
+
2196
+ ## Style
2197
+
2198
+ - `surface` fill, a 1px `line` edge, `radius-lg`. No shadow: it is flat, like a field.
2199
+ - Rows pad `space-3` × `space-4` at md and `space-2` × `space-3` at sm, divided by a 1px `line`. No hover: rows don't act on click.
2200
+ - Text is `body` in `ink`.
2201
+
2006
2202
  <!-- docs/components/prose.md -->
2007
2203
 
2008
2204
  # Prose
@@ -2055,6 +2251,410 @@ Every native attribute passes through.
2055
2251
  - Tags: `ink-muted` on `well`, `radius-xs`. They read as words, not badges.
2056
2252
  - Sizes are custom properties set on `.mu-prose` in the foundation (`--prose-fs`, `--prose-lh`, heading sizes).
2057
2253
 
2254
+ <!-- docs/usage.md -->
2255
+
2256
+ # Using the components
2257
+
2258
+ How to pick the right component for a screen and put components together. Read it before building or migrating a screen in an app. Each component's own page has its props and rules; this page decides between them.
2259
+
2260
+ ## Before you build a screen
2261
+
2262
+ 1. Find the [pattern](patterns.md) the screen follows: page, form, entry, confirmation, loading, empty state, mobile. Follow it before choosing components.
2263
+ 2. Pick each component with the guides below. When two seem to fit, the first row that matches wins.
2264
+ 3. Import components from `@usememos/ui` and icons from `lucide-react`. Don't wrap a component to restyle it.
2265
+ 4. Use `className` and `style` for layout only: width, margin, grid or flex placement. Don't change a component's color, height, padding, radius, shadow, font size or focus style. If the look is wrong, the component or its page is wrong; change the package, not the screen.
2266
+ 5. A part the package doesn't ship is built with [Building your own component](custom-components.md), never by copying a component's CSS.
2267
+
2268
+ ## Picking something that floats
2269
+
2270
+ The most common mistake is reaching for the wrong overlay. Decide by what the floating thing holds and how it closes.
2271
+
2272
+ | Component | Holds | Opens on | Closes when | Blocks the page | Use it for |
2273
+ | --- | --- | --- | --- | --- | --- |
2274
+ | [Tooltip](components/tooltip.md) | Two or three words of text, and a shortcut | Hover or keyboard focus | The pointer or focus leaves | No | Naming an icon-only control |
2275
+ | [Menu](components/menu.md) | A list of commands | Click on its trigger | One command is picked, Escape, a click outside | No | Actions on one thing: "Pin", "Copy link", "Delete" |
2276
+ | [Select](components/select.md), [Combobox](components/combobox.md) | A list of values | Click or typing | One value is picked | No | Choosing a value that a form or setting keeps |
2277
+ | [Popover](components/popover.md) | Any small content: text, a few controls | Click on its trigger | Escape, a click outside, or its own Close | No | A share panel, a link editor, a filter panel, an explanation |
2278
+ | [Dialog](components/dialog.md) | A short form or a decision | An action | Its buttons, Escape, a backdrop click | Yes | Creating something, editing settings of one thing |
2279
+ | [AlertDialog](components/dialog.md) | A question and two buttons | An action | Only its buttons | Yes | Confirming a destructive or irreversible action |
2280
+ | [Drawer](components/drawer.md) | Navigation, or the details of one thing | A ghost button | Escape, a backdrop click, a swipe toward its edge, or a link inside | Yes | The sidebar on a phone; details beside a list |
2281
+ | [Toast](components/toast.md) | A sentence about what happened, at most one action | The app, after something happened | A timeout, its X, or its action | No | "Note archived" with Undo; "Couldn't sync" |
2282
+
2283
+ ### Menu or Popover
2284
+
2285
+ - **Menu: picking an item runs a command and closes the menu.** Items are verbs. Arrow keys move between items; typing jumps to one. Nothing inside a Menu takes text or holds focus.
2286
+ - **Popover: the content stays open while people use it.** It can hold text, inputs, buttons, a list of toggles. People tab through it like a small page.
2287
+ - An input, a checkbox list with an Apply button, or explanatory text inside a Menu means it should be a Popover.
2288
+ - A Popover whose content is only a list of actions should be a Menu.
2289
+ - A Menu may hold `Menu.CheckboxItem`s and `Menu.RadioItem`s for view options ("Show archived", "Sort by"). They stay open on click. Use them only for options of the view the menu belongs to, not for form values.
2290
+
2291
+ ### Menu or Select
2292
+
2293
+ - **Menu runs commands; Select keeps a value.** A Select's trigger shows the chosen value, it sits in a [Field](components/field.md) with a label, and its value is submitted with a form.
2294
+ - A "Sort by" control in a toolbar that changes a view is a Menu with a `Menu.RadioGroup`, or a [ToggleGroup](components/toggle-group.md) when the options fit in a row.
2295
+ - A "Visibility" field in a form is a Select.
2296
+
2297
+ ### Popover, Dialog or a page
2298
+
2299
+ - **Popover**: one to three controls about the thing next to it, changes that apply as they are made or are trivial to undo.
2300
+ - **Dialog**: more than three controls, a Save or Create button, or a decision that must be made before going on.
2301
+ - **A page**: more than about six fields, sections, or content people need to link to.
2302
+ - Don't open a Dialog from a Dialog. Replace the content, or close the first one. The one exception is an AlertDialog that confirms discarding the first one's input.
2303
+
2304
+ ### Tooltip or Popover
2305
+
2306
+ - **Tooltip**: a label for a control. It has no interactive content and never opens on touch screens, so it must never be the only place a fact appears.
2307
+ - **Popover**: anything people need to read, anything with a link or a button, or anything that must work on touch. For a hover card ("show the person on hover"), use a Popover with `openOnHover` on its trigger, and make sure a click shows the same thing.
2308
+
2309
+ ### Dialog or AlertDialog
2310
+
2311
+ - **AlertDialog** for "Are you sure?": deleting, discarding unsaved input, revoking a token. It has no X and ignores the backdrop, so people must answer.
2312
+ - **Dialog** for everything else. A Dialog with a form keeps itself open while it has unsaved input (see [Recipes](#form-in-a-dialog)).
2313
+ - Don't confirm actions that can be undone. Run them and show a Toast with Undo instead.
2314
+
2315
+ ### Toast or an inline message
2316
+
2317
+ - **Toast**: the result of an action people just took, when the screen doesn't already show it. "Note archived" (the note disappeared), "Link copied" (nothing else changed).
2318
+ - **Inline**: anything tied to a place on the screen. A field's error goes in `Field.Error` under the field. A page that fails to load says so in the page, with a Retry button.
2319
+ - Don't toast what the screen already shows. A saved setting whose Switch moved needs no toast.
2320
+ - A toast never asks a question. A decision is an AlertDialog.
2321
+
2322
+ ## Picking a control
2323
+
2324
+ ### Starting an action or going somewhere
2325
+
2326
+ | Need | Use | Not |
2327
+ | --- | --- | --- |
2328
+ | Run an action | [Button](components/button.md) | A Link with an `onClick` |
2329
+ | Go to another page | [Link](components/link.md) | A Button with `navigate()` in `onClick` |
2330
+ | Go to another page, styled as an action ("Get started") | An `<a>` (or router link) with `buttonClasses()` | `Button` with `render={<a />}` |
2331
+ | Several actions on one thing | A ghost Button that opens a [Menu](components/menu.md) | A row of icon buttons for rare actions |
2332
+ | Turn one option on or off in a toolbar | [Toggle](components/toggle.md) | A Button that changes its own label |
2333
+
2334
+ ### Choosing a value
2335
+
2336
+ Answer in order; the first yes decides.
2337
+
2338
+ 1. **Does it save the moment it changes (a settings page)?** On or off: [Switch](components/switch.md). One of a few: [Select](components/select.md) that saves in `onValueChange`.
2339
+ 2. **Is it on or off, saved with a form?** [Checkbox](components/checkbox.md).
2340
+ 3. **Is it one of 2 or 3 options, with room to show them or each needing a description?** [RadioGroup](components/radio-group.md).
2341
+ 4. **Is it one of up to about 10 options?** [Select](components/select.md).
2342
+ 5. **Is it one of a long list, or do people know what they're looking for and type it?** [Combobox](components/combobox.md).
2343
+ 6. **Is it several of a few options?** Checkboxes in a [Fieldset](components/field.md).
2344
+ 7. **Is it a view or filter on the current page, not a stored value?** One choice: [ToggleGroup](components/toggle-group.md) without `multiple`. Stacking filters: [Toggle](components/toggle.md)s, or a ToggleGroup with `multiple`.
2345
+ 8. **Is it text?** One line: [Input](components/input.md). A few plain lines: [Textarea](components/textarea.md). Formatted content: the editor (`@usememos/editor`) with [Prose](components/prose.md).
2346
+ 9. **Is it a number?** An Input with `type="number"` and `inputMode="numeric"`, or a Select of the sensible values. There is no Slider or Stepper.
2347
+
2348
+ ### Grouping and moving between sections
2349
+
2350
+ | Need | Use | Not |
2351
+ | --- | --- | --- |
2352
+ | Sections of one page (Account, Notifications) | [Tabs](components/tabs.md) | ToggleGroup |
2353
+ | A view or filter of the same content (All, Pinned, Archived) | [ToggleGroup](components/toggle-group.md) without `multiple` | Tabs |
2354
+ | Navigation between pages | [Link](components/link.md)s in the app's sidebar | Tabs |
2355
+ | Details or advanced settings people rarely need | [Collapsible](components/collapsible.md) | A Dialog |
2356
+ | Several sections, one open at a time | Collapsibles, controlled together | A third-party accordion |
2357
+ | Separating groups of content | `space-6` of whitespace, then a section title, then a [Separator](components/separator.md) if still unclear | A box, border or card |
2358
+ | Data with columns (tokens, users, webhooks) | [Table](components/table.md), `bordered` when it is one section among others | A List with columns faked in each row |
2359
+ | Settings rows, or a few records with an action each | [List](components/list.md) | Separate boxes for each row |
2360
+ | A list of content (notes, comments) | [Entries](patterns.md#entry) | A Table |
2361
+
2362
+ ## Picking feedback
2363
+
2364
+ | Situation | Use |
2365
+ | --- | --- |
2366
+ | A button's action is waiting on a server | The Button's `loading`, with `loadingLabel` ("Saving") |
2367
+ | A list or page is loading | [Skeleton](components/skeleton.md)s in the shape of the content, and `aria-busy` on the region |
2368
+ | Something short is running with no control to put it in | [Spinner](components/spinner.md) with a `label` |
2369
+ | An action finished and the screen doesn't show it | [Toast](components/toast.md), neutral or `success` |
2370
+ | An action failed | Toast with `type: "danger"` and `timeout: 0`; a field's own error in `Field.Error` instead |
2371
+ | A field's value is wrong | `invalid` on `Field.Root` and a `Field.Error` that says how to fix it |
2372
+ | A thing has a status (Synced, Expired) | [Badge](components/badge.md) with a tone and a word |
2373
+ | A list is empty | The [empty state](patterns.md#empty-state) pattern |
2374
+ | An action is about to destroy something | [AlertDialog](components/dialog.md), then a Toast after it runs |
2375
+
2376
+ Waits under about 300ms show nothing. Status colors (`success`, `warning`, `danger`) appear only with words or an icon, never as the only signal.
2377
+
2378
+ ## Recipes
2379
+
2380
+ ### Trigger through `render`
2381
+
2382
+ Every `Trigger` and `Close` renders the element given in `render`. Pass the Button there; don't put it inside.
2383
+
2384
+ ```tsx
2385
+ // Do: one <button>
2386
+ <Menu.Trigger render={<Button variant="ghost" icon={<Ellipsis />} aria-label="More actions" />} />
2387
+
2388
+ // Don't: a <button> inside a <button>
2389
+ <Menu.Trigger><Button variant="ghost" icon={<Ellipsis />} aria-label="More actions" /></Menu.Trigger>
2390
+ ```
2391
+
2392
+ ### An icon-only button with a tooltip and a menu
2393
+
2394
+ Stack the triggers with `render`. One `<button>` gets both behaviors.
2395
+
2396
+ ```tsx
2397
+ <Menu.Root>
2398
+ <Tooltip.Root>
2399
+ <Tooltip.Trigger
2400
+ render={<Menu.Trigger render={<Button variant="ghost" icon={<Ellipsis />} aria-label="More actions" />} />}
2401
+ />
2402
+ <Tooltip.Popup>More actions</Tooltip.Popup>
2403
+ </Tooltip.Root>
2404
+ <Menu.Popup align="end">…</Menu.Popup>
2405
+ </Menu.Root>
2406
+ ```
2407
+
2408
+ The same works for a Dialog or Popover trigger: `<Tooltip.Trigger render={<Popover.Trigger render={<Button … />} />} />`.
2409
+
2410
+ ### A menu item that opens a dialog
2411
+
2412
+ Keep the Dialog outside the Menu and control it with `open`. The Menu closes when the item is picked, so a Dialog inside `Menu.Popup` would unmount with it.
2413
+
2414
+ ```tsx
2415
+ const [confirming, setConfirming] = useState(false);
2416
+
2417
+ <Menu.Root>
2418
+ <Menu.Trigger render={<Button variant="ghost" icon={<Ellipsis />} aria-label="More actions" />} />
2419
+ <Menu.Popup align="end">
2420
+ <Menu.Item icon={<Pin />} onClick={pin}>Pin</Menu.Item>
2421
+ <Menu.Separator />
2422
+ <Menu.Item icon={<Trash2 />} danger onClick={() => setConfirming(true)}>Delete</Menu.Item>
2423
+ </Menu.Popup>
2424
+ </Menu.Root>
2425
+
2426
+ <AlertDialog.Root open={confirming} onOpenChange={setConfirming}>
2427
+ <AlertDialog.Popup>
2428
+ <AlertDialog.Title>Delete this note?</AlertDialog.Title>
2429
+ <AlertDialog.Description>It moves to the trash for 30 days.</AlertDialog.Description>
2430
+ <AlertDialog.Footer>
2431
+ <AlertDialog.Close render={<Button variant="ghost">Cancel</Button>} />
2432
+ <Button variant="danger" loading={deleting} loadingLabel="Deleting" onClick={remove}>Delete note</Button>
2433
+ </AlertDialog.Footer>
2434
+ </AlertDialog.Popup>
2435
+ </AlertDialog.Root>
2436
+ ```
2437
+
2438
+ `remove` closes the dialog with `setConfirming(false)` once the server answers, then shows a Toast. Use `AlertDialog.Close render={<Button variant="danger" />}` only when the action can't fail.
2439
+
2440
+ ### Form in a dialog
2441
+
2442
+ ```tsx
2443
+ <Dialog.Root
2444
+ open={open}
2445
+ onOpenChange={(next) => {
2446
+ if (!next && dirty) return; // keep unsaved input; Cancel resets and closes
2447
+ setOpen(next);
2448
+ }}
2449
+ >
2450
+ <Dialog.Trigger render={<Button>Create access token</Button>} />
2451
+ <Dialog.Popup>
2452
+ <Dialog.Title>Create access token</Dialog.Title>
2453
+ <Dialog.Description>Use it to call the API from scripts.</Dialog.Description>
2454
+ <form id="create-token" onSubmit={create}>
2455
+ <Field.Root invalid={!!error}>
2456
+ <Field.Label>Description</Field.Label>
2457
+ <Input value={description} onValueChange={setDescription} />
2458
+ <Field.Error match>{error}</Field.Error>
2459
+ </Field.Root>
2460
+ </form>
2461
+ <Dialog.Footer>
2462
+ <Button variant="ghost" onClick={cancel}>Cancel</Button>
2463
+ <Button variant="primary" type="submit" form="create-token" loading={saving} loadingLabel="Creating">Create</Button>
2464
+ </Dialog.Footer>
2465
+ </Dialog.Popup>
2466
+ </Dialog.Root>
2467
+ ```
2468
+
2469
+ - One Field per control. The submit button is `primary`, named with its verb, and shows `loading`.
2470
+ - On a server error, keep the dialog open and show the error on the field it belongs to. Close it only on success.
2471
+ - On phones the dialog becomes a sheet by itself. Don't build a separate mobile layout.
2472
+
2473
+ ### Settings that save at once
2474
+
2475
+ ```tsx
2476
+ <h2 className="mu-label" id="notifications">Notifications</h2>
2477
+ <List.Root aria-labelledby="notifications">
2478
+ <List.Item>
2479
+ <Field.Root orientation="horizontal">
2480
+ <Field.Label>Email me when I'm mentioned</Field.Label>
2481
+ <Field.Description>Sent at most once an hour.</Field.Description>
2482
+ <Switch checked={mentions} onCheckedChange={saveMentions} />
2483
+ </Field.Root>
2484
+ </List.Item>
2485
+ <List.Item>…</List.Item>
2486
+ </List.Root>
2487
+ ```
2488
+
2489
+ A horizontal Field puts the setting's name first and the Switch or Select at the end of the row. A [List](components/list.md) boxes the rows of one section and divides them with lines. Inside a Dialog, leave out the List and stack the Fields. For a short list of options under one heading (notification types), a Switch or Checkbox inside its `Field.Label` also works.
2490
+
2491
+ No Save button and no toast: the Switch shows the result. If saving fails, move the Switch back and show a danger Toast.
2492
+
2493
+ ### A toolbar
2494
+
2495
+ ```tsx
2496
+ <Tooltip.Provider>
2497
+ <ToggleGroup multiple size="sm" value={marks} onValueChange={setMarks} aria-label="Formatting">
2498
+ <Tooltip.Root>
2499
+ <Tooltip.Trigger render={<Toggle value="bold" icon={<Bold />} aria-label="Bold" />} />
2500
+ <Tooltip.Popup shortcut="⌘ B">Bold</Tooltip.Popup>
2501
+ </Tooltip.Root>
2502
+ …
2503
+ <Separator orientation="vertical" />
2504
+ …
2505
+ </ToggleGroup>
2506
+ </Tooltip.Provider>
2507
+ ```
2508
+
2509
+ One `Tooltip.Provider` around the toolbar (or the app) so moving along it opens the next tooltip at once.
2510
+
2511
+ ### Row and entry actions
2512
+
2513
+ - Put rare actions in a ghost "More actions" Menu trigger at the end of the row. Name it for the row: `aria-label={`Actions for ${token.name}`}`.
2514
+ - Show one or two frequent actions as xs or sm ghost Buttons beside it, each with a Tooltip.
2515
+ - On an entry the trigger appears on hover; on touch screens it is always visible (see [Entry](patterns.md#entry)).
2516
+
2517
+ ### Buttons over a photo
2518
+
2519
+ Controls over an image or a video sit in a dark-theme scope, so they read on any picture in both app themes:
2520
+
2521
+ ```tsx
2522
+ <figure className="app-viewer">
2523
+ <img src={src} alt={alt} />
2524
+ <div className="app-viewer-controls" data-theme="dark">
2525
+ <Button icon={<X />} aria-label="Close" onClick={close} />
2526
+ <Button icon={<ChevronLeft />} aria-label="Previous image" onClick={previous} />
2527
+ <Button icon={<ChevronRight />} aria-label="Next image" onClick={next} />
2528
+ </div>
2529
+ </figure>
2530
+ ```
2531
+
2532
+ - `data-theme="dark"` on any element sets the Dark tokens for everything inside it, so the Buttons are dark `neutral` keys with light icons. Don't use `ghost` there: it has no fill and disappears on a busy picture.
2533
+ - Leave the scope's own background transparent; the keys carry their own fill and shadow.
2534
+ - A full-screen viewer is a Dialog with `variant="bare"`; put the scope inside its Popup.
2535
+
2536
+ ### Inside a shadow root or a host page
2537
+
2538
+ In an extension or an embed, pass the same `container` to every `Popup` and to `Toast.Viewport`, so popups mount inside the shadow root and pick up its styles. See [Scoping inside a host page](building.md#scoping-inside-a-host-page).
2539
+
2540
+ ```tsx
2541
+ <Menu.Popup container={shadowRoot}>…</Menu.Popup>
2542
+ <Toast.Viewport container={shadowRoot} />
2543
+ ```
2544
+
2545
+ ## Size
2546
+
2547
+ - **Set `size` once, on the outermost part that takes it.** `Menu.Root`, `Select.Root`, `Combobox.Root`, `Tabs.Root`, `Collapsible.Root`, `Table.Root`, `Field.Root`, `Fieldset.Root`, `RadioGroup` and `ToggleGroup` pass their size to every part and every Button, Toggle, Checkbox or Input inside them. Don't repeat it on the parts.
2548
+ - **Pick by place, not by importance.** `md` everywhere by default, including dialogs, forms, onboarding, marketing pages and the app's sidebar (its navigation rows and its main button). `sm` for dense toolbars, table rows and filters inside panels. `xs` for secondary actions beside 13px text (entry footers, row actions). See [Control sizes](layout.md#control-sizes).
2549
+ - **Never make a control bigger to make it stronger.** Emphasis is the `primary` variant.
2550
+ - **Don't mix sizes in one row.** A Button beside an Input has the Input's size.
2551
+ - **Dialogs and Popovers start at md.** Their panels are md wherever they are declared, so a Dialog inside a `Table.Root size="sm"` still has md Buttons and fields. Set `size` inside the panel only for a part that should differ.
2552
+ - **Menus follow their surroundings.** A Menu, Select or Combobox without its own `size` takes the size of the Root around it, so a menu opened from a sm toolbar has sm rows.
2553
+ - Text entry, menus, tabs and tables have no `xs`; an inherited `xs` renders them at `sm`.
2554
+
2555
+ ## Emphasis and color
2556
+
2557
+ - **One `primary` Button per view.** A view is a page, a dialog or a popover. Everything else is `neutral`, or `ghost` in toolbars and rows.
2558
+ - **`danger` confirms; it never starts.** The first click on "Delete" is a Menu item with `danger` or a neutral Button; the `danger` Button lives in the AlertDialog that follows.
2559
+ - **Dialog footers** are a ghost Cancel, then the action, named with its verb ("Delete note", "Create"), never "OK" or "Yes".
2560
+ - **Tone is status only.** Badge and Toast tones say success, warning or danger. Don't use them to highlight, decorate or color-code categories.
2561
+ - **Links are the only blue.** Don't color text, icons or backgrounds with `accent`.
2562
+
2563
+ ## Accessibility checklist
2564
+
2565
+ Run through it for every screen.
2566
+
2567
+ - [ ] Every control has a name: a `Field.Label`, a visible label, or `aria-label`. Placeholders are never names.
2568
+ - [ ] Every icon-only Button and Toggle has an `aria-label` and a Tooltip with the same text.
2569
+ - [ ] Every group has a name: `aria-label` on a ToggleGroup and a `Tabs.List`; a `Fieldset.Legend`; a `Table.Caption` or `aria-label` on a Table; a `Popover.Title` or `aria-label`; a `Dialog.Title`.
2570
+ - [ ] Checkboxes, Switches and Radios sit inside their `Field.Label`, so the text is clickable.
2571
+ - [ ] Repeated triggers are named for their row ("Actions for Backup token"), not just "More actions".
2572
+ - [ ] No fact appears only in a Tooltip, only in color, or only in a placeholder.
2573
+ - [ ] Loading regions have `aria-busy`; loading Buttons have a `loadingLabel`.
2574
+ - [ ] Errors are in `Field.Error` or a danger Toast with `timeout: 0`, worded as how to fix them.
2575
+ - [ ] No `outline: none`, custom focus ring or `tabIndex` on a component. Focus, keyboard and ARIA come from Base UI; don't add key handlers that fight them.
2576
+ - [ ] Nothing pressable is built from a `<div>`. If the package has no component for it, build it on Base UI ([Building your own component](custom-components.md)).
2577
+
2578
+ ## Common mistakes
2579
+
2580
+ | Don't | Do |
2581
+ | --- | --- |
2582
+ | An Input or checkbox list inside a Menu | A Popover |
2583
+ | A Popover holding only a list of actions | A Menu |
2584
+ | A Menu to choose a form value | A Select in a Field |
2585
+ | A Dialog for one or two quick controls | A Popover |
2586
+ | A Dialog for "Are you sure?" | An AlertDialog |
2587
+ | A confirmation for an action that can be undone | Run it, then a Toast with Undo |
2588
+ | A Tooltip with a link, a button or a paragraph | A Popover |
2589
+ | A Tooltip on text that isn't a control | Visible text, or a Popover |
2590
+ | A Toast for a field's error | `Field.Error` under the field |
2591
+ | A success Toast after every Switch | Nothing; the Switch shows it |
2592
+ | A Button that navigates | A Link, or an anchor with `buttonClasses()` |
2593
+ | `<Button render={<a href="…" />}>` | `<a className={buttonClasses()} href="…">` |
2594
+ | A Button nested inside a `Trigger` | `render={<Button … />}` on the Trigger |
2595
+ | A clickable Badge | A Toggle (filters) or a Link with `badgeClasses()` (tag pages) |
2596
+ | Tabs to filter a list | A ToggleGroup without `multiple` |
2597
+ | A ToggleGroup to switch page sections | Tabs |
2598
+ | A Switch in a form with a Save button | A Checkbox |
2599
+ | A Checkbox that saves at once | A Switch |
2600
+ | A Select with 30 options | A Combobox |
2601
+ | A Select with two options and room to spare | A RadioGroup |
2602
+ | A boxed card around each item | Whitespace, a section title, or a Separator. Rows of settings or records go in one [List](components/list.md) |
2603
+ | An icon after a Button's label inside `children` | `iconEnd` |
2604
+ | A `<button>` with its own classes as a row in a Popover | `menuClasses().item`, in a Popover with `variant="list"` |
2605
+ | A Dialog with `className` that removes its padding and X | `variant="bare"` |
2606
+ | Tailwind `size-4` on a standalone icon | `mu-icon-sm` or `mu-icon-md`, which set the stroke too |
2607
+ | A Spinner where a list will appear | Skeletons in the shape of the list |
2608
+ | `size="md"` or `"lg"` on a Button to emphasize it | `variant="primary"` at the row's size |
2609
+ | `size` on every part of a Menu or Select | `size` on its Root |
2610
+ | Two `primary` Buttons in one view | One `primary`, the rest `neutral` |
2611
+ | `danger` Button as the first click | A `danger` Menu item, then an AlertDialog |
2612
+ | A placeholder as the only label | `Field.Label`, or `aria-label` |
2613
+ | Color as the only signal of status | A Badge or Toast with a word and an icon |
2614
+ | `className` that changes a component's color, height or radius | Use the component as it is, or change the package |
2615
+ | Copying a component's CSS to make a lookalike | Base UI with a class function or public classes |
2616
+ | A `<div onClick>` as a control | A component, or Base UI's part with a class function |
2617
+
2618
+ ## Migrating from another UI kit
2619
+
2620
+ Names and parts from other libraries and where they go.
2621
+
2622
+ | Other kits | Memos UI |
2623
+ | --- | --- |
2624
+ | Modal, Dialog | [Dialog](components/dialog.md); `sheet` covers phones |
2625
+ | ConfirmDialog, AlertDialog, `window.confirm` | [AlertDialog](components/dialog.md) |
2626
+ | Drawer, Sheet | [Drawer](components/drawer.md) from an edge; a Dialog already becomes a bottom sheet below 640px |
2627
+ | Command palette, Spotlight | Dialog with `variant="bare"` around an inline [Combobox](components/combobox.md) |
2628
+ | Lightbox, image viewer | Dialog with `variant="bare"`, its controls as neutral Buttons in a `data-theme="dark"` scope ([recipe](#buttons-over-a-photo)) |
2629
+ | List group, settings card, DataList | [List](components/list.md) |
2630
+ | Emoji or icon picker | Combobox with `inline` and `grid` |
2631
+ | DropdownMenu, ActionMenu | [Menu](components/menu.md) |
2632
+ | ContextMenu | Not built. Put the same actions in a Menu on a visible trigger |
2633
+ | HoverCard | [Popover](components/popover.md) with `openOnHover` on the trigger |
2634
+ | Tooltip with a `title` or `content` prop | [Tooltip](components/tooltip.md) parts: Root, Trigger, Popup |
2635
+ | Snackbar, Toaster, sonner's `toast()` | [Toast](components/toast.md) and `Toast.useToastManager().add()` |
2636
+ | Toast `variant="destructive"` | `type: "danger"` |
2637
+ | Card, Paper, Panel | No component: spacing, a section title and [Separator](components/separator.md). For a boxed group of rows, [List](components/list.md) |
2638
+ | Divider | [Separator](components/separator.md) |
2639
+ | Chip, Tag | [Badge](components/badge.md) to show, [Toggle](components/toggle.md) to filter |
2640
+ | SegmentedControl, RadioButtons in a row | [ToggleGroup](components/toggle-group.md) without `multiple` |
2641
+ | Accordion | [Collapsible](components/collapsible.md)s |
2642
+ | Slider, NumberInput, Stepper | Input `type="number"` or a Select |
2643
+ | Autocomplete, SearchSelect | [Combobox](components/combobox.md) |
2644
+ | NativeSelect, `<select>` | [Select](components/select.md) |
2645
+ | Label, FormLabel | `Field.Label` |
2646
+ | FormMessage, HelperText | `Field.Error`, `Field.Description` |
2647
+ | FormItem, FormControl | `Field.Root` |
2648
+ | Loader, CircularProgress | [Spinner](components/spinner.md), or a control's `loading` |
2649
+ | Progress bar | Not built. Use Skeletons for loading, or text ("3 of 10") |
2650
+ | Typography, Text, Heading | The type classes (`mu-body`, `mu-caption`, `mu-heading`) from [Typography](typography.md) |
2651
+ | Markdown or rich-text body | [Prose](components/prose.md) |
2652
+ | `variant="secondary"`, `"outline"` on a button | `neutral`; `ghost` in toolbars |
2653
+ | `variant="destructive"` on a button | `danger`, only inside a confirmation |
2654
+ | `size="lg"` | `md`; emphasis comes from `primary` |
2655
+ | `options={[…]}`, `items={[…]}` data props | Parts: `Select.Item`, `Menu.Item`, `Tabs.Tab`. Select and Combobox still take `items` on the Root for labels and filtering |
2656
+ | `asChild` | `render={<… />}` |
2657
+
2058
2658
  <!-- docs/patterns.md -->
2059
2659
 
2060
2660
  # Patterns
@@ -2063,7 +2663,8 @@ How the components go together. Each product builds its screens from these patte
2063
2663
 
2064
2664
  ## Page
2065
2665
 
2066
- - From `breakpoint-md` (1024px) up: a fixed sidebar of `sidebar-width` (240px). Below it, the sidebar is a sheet opened from a ghost button.
2666
+ - From `breakpoint-md` (1024px) up: a fixed sidebar of `sidebar-width` (240px). Below it, the sidebar is a [Drawer](components/drawer.md) from the start edge (`side="start"`, `variant="bare"`) opened from a ghost button.
2667
+ - Sidebar rows (navigation links, a space switcher, the main "New" button) are md: 32px tall, `label` text, md icons. There is no 28px row; don't build one with custom padding.
2067
2668
  - Content sits in a column of at most `content-width` (720px), padded `space-4` on phones and `space-6` on desktop.
2068
2669
  - A page header is a `heading` with an optional `body` line under it, and the page's one primary action at the right. `heading` appears once per page.
2069
2670
  - Sections inside a page have a `label` title (14px, 500) with `space-6` above it and `space-2` below. Section titles are never larger than the text around them; space and grouping set them apart. A section's rows use `label` for the name and `caption` in `ink-muted` for the description.
@@ -2074,6 +2675,7 @@ How the components go together. Each product builds its screens from these patte
2074
2675
  - Related checkboxes or switches sit in a `Fieldset`.
2075
2676
  - Actions go at the end, right-aligned: a ghost Cancel, then the primary.
2076
2677
  - A form that saves at once (settings) uses switches and has no Save button. A form with a Save button uses checkboxes.
2678
+ - On a settings page, each section is a title and a [List](components/list.md) of horizontal Fields: the setting's name at the start, its Switch or Select at the end. A section of records (tokens, webhooks) is a List, or a `bordered` [Table](components/table.md) when the records have columns.
2077
2679
  - Errors appear under the field, in words that say how to fix it.
2078
2680
 
2079
2681
  ## Entry
@@ -2115,7 +2717,7 @@ Under `breakpoint-sm` (640px):
2115
2717
 
2116
2718
  - One column.
2117
2719
  - Dialogs and menus become sheets.
2118
- - Toasts sit bottom-center.
2720
+ - Toasts span the top of the screen.
2119
2721
  - Every control keeps its 44px hit area.
2120
2722
  - Controls stay `md`.
2121
2723
 
@@ -2181,7 +2783,7 @@ An app changes only the color, shadow and font tokens. Every other token and eve
2181
2783
  - `mu-positioner` puts a popup's positioner on `layer-popup`, and `mu-popup-motion` makes a popup fade in and rise `space-1` (no rise under reduced motion).
2182
2784
  - The depth classes `mu-recessed`, `mu-key`, `mu-raised` and `mu-popup` apply a whole depth role. Use them for new parts before writing new shadows. See [Depth](depth.md).
2183
2785
  - `mu-prose` styles rendered content. See [Prose](components/prose.md).
2184
- - `mu-icon` applies icon size and stroke rules outside a component.
2786
+ - `mu-icon` applies icon size and stroke rules outside a component, at the size of the nearest size row; `mu-icon-xs`, `mu-icon-sm` and `mu-icon-md` set a fixed size on their own. See [Icons](icons.md).
2185
2787
  - `mu-visually-hidden` hides text visually but keeps it for screen readers.
2186
2788
  - A size row class sets the `--mu-row-*` variables (height, padding, text, radius, gap, icon) that every part inside reads. See [Row variables](custom-components.md#row-variables).
2187
2789
  - Component part classes (`mu-menu-item`) are internal. An app reaches a component's look through its class function (`menuClasses()`). See [Class functions](custom-components.md#class-functions).
@@ -2293,13 +2895,13 @@ A class function returns the classes that make any element look like a component
2293
2895
  | `inputClasses({ size })` | A string | One-line field, such as a tag input around a native `<input>` |
2294
2896
  | `textareaClasses({ size })` | A string | Multi-line field, such as a content-editable editor |
2295
2897
  | `badgeClasses({ tone })` | A string | Label, such as a tag that is also a link |
2296
- | `menuClasses({ size })` | `popup`, `item`, `dangerItem`, `label`, `shortcut`, `separator`, `groupLabel` | Floating list, such as an editor's suggestion list |
2297
- | `popoverClasses()` | `popup`, `title`, `description` | Floating panel, such as an emoji picker |
2898
+ | `menuClasses({ size })` | `popup`, `item`, `dangerItem`, `label`, `text`, `description`, `shortcut`, `separator`, `groupLabel` | Floating list, such as an editor's suggestion list, or rows inside a Popover with `variant="list"` |
2899
+ | `popoverClasses({ variant })` | `popup`, `title`, `description` | Floating panel, such as an emoji picker |
2298
2900
 
2299
2901
  State is set with attributes, never with extra classes:
2300
2902
 
2301
2903
  - A field darkens its edge to `focus-edge` when it or anything inside it has focus. `aria-invalid="true"` shows the error edge, and `aria-disabled="true"` dims it.
2302
- - A menu row is highlighted while it has `data-highlighted`, and dimmed with `data-disabled`. Base UI sets both; a list the app drives itself sets them on the active row.
2904
+ - A menu row is highlighted while it has `data-highlighted`, and dimmed with `data-disabled`. Base UI sets both; a list the app drives itself sets them on the active row. `item` works on a `<div>`, `<button>` or `<a>`. For a two-line row, wrap `label` and `description` in a `text` span.
2303
2905
  - A popup fades in with `data-starting-style` and out with `data-ending-style`, which Base UI's popups set.
2304
2906
 
2305
2907
  ```tsx
@@ -2340,7 +2942,10 @@ Apply these classes to an app's own elements. Don't override their rules: the pr
2340
2942
  | `mu-positioner` | The popup layer | A popup's positioner |
2341
2943
  | `mu-focus` | The ink focus ring on `:focus-visible`, 1px outside the edge | Anything focusable |
2342
2944
  | `mu-hit` | An invisible 44px touch target on `pointer: coarse` | Anything pressable smaller than 44px |
2343
- | `mu-icon` | Icon size and stroke | A Lucide icon outside a control |
2945
+ | `mu-icon` | Icon size and stroke from the nearest size row, 16px outside one | A Lucide icon inside an app's own row |
2946
+ | `mu-icon-xs` | A 12px icon with a 1.25px stroke | A Lucide icon beside 12px or 13px text, outside a control |
2947
+ | `mu-icon-sm` | A 14px icon with a 1.25px stroke | A Lucide icon beside 13px or 14px text, outside a control |
2948
+ | `mu-icon-md` | A 16px icon with a 1.5px stroke | A Lucide icon beside 14px or 16px text, outside a control |
2344
2949
  | `mu-visually-hidden` | Hidden on screen, read by screen readers | Text for screen readers only |
2345
2950
  | `mu-label-xs` | 12 / 16px, 500 | Labels of 20px parts |
2346
2951
  | `mu-caption` | 13 / 18px, 400 | Secondary text |