@usememos/ui 26.11.1 → 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.
- package/README.md +1 -1
- package/dist/components/button/index.js +1 -1
- package/dist/components/button/index.js.map +3 -3
- package/dist/components/combobox/index.js +1 -1
- package/dist/components/combobox/index.js.map +3 -3
- package/dist/components/dialog/index.js.map +2 -2
- package/dist/components/dialog/parts.js +1 -1
- package/dist/components/dialog/parts.js.map +3 -3
- package/dist/components/drawer/index.js +3 -0
- package/dist/components/drawer/index.js.map +7 -0
- package/dist/components/field/index.js +1 -1
- package/dist/components/field/index.js.map +3 -3
- package/dist/components/list/index.js +3 -0
- package/dist/components/list/index.js.map +7 -0
- package/dist/components/menu/classes.js +1 -1
- package/dist/components/menu/classes.js.map +2 -2
- package/dist/components/menu/index.js +1 -1
- package/dist/components/menu/index.js.map +3 -3
- package/dist/components/popover/classes.js +1 -1
- package/dist/components/popover/classes.js.map +3 -3
- package/dist/components/popover/index.js +1 -1
- package/dist/components/popover/index.js.map +3 -3
- package/dist/components/select/index.js +1 -1
- package/dist/components/select/index.js.map +3 -3
- package/dist/components/table/index.js +1 -1
- package/dist/components/table/index.js.map +3 -3
- package/dist/components/toast/index.js +1 -1
- package/dist/components/toast/index.js.map +3 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +3 -3
- package/dist/styles.css +1 -1
- package/dist/tokens.css +1 -1
- package/dist/types/components/button/index.d.ts +2 -0
- package/dist/types/components/combobox/index.d.ts +8 -0
- package/dist/types/components/dialog/index.d.ts +3 -3
- package/dist/types/components/dialog/parts.d.ts +9 -1
- package/dist/types/components/drawer/index.d.ts +42 -0
- package/dist/types/components/field/index.d.ts +6 -0
- package/dist/types/components/list/index.d.ts +16 -0
- package/dist/types/components/menu/classes.d.ts +4 -0
- package/dist/types/components/menu/index.d.ts +2 -0
- package/dist/types/components/popover/classes.d.ts +6 -1
- package/dist/types/components/popover/index.d.ts +7 -0
- package/dist/types/components/select/index.d.ts +5 -0
- package/dist/types/components/table/index.d.ts +2 -0
- package/dist/types/components/toast/index.d.ts +1 -1
- package/dist/types/index.d.ts +4 -1
- package/llms-full.txt +276 -49
- package/llms.txt +3 -1
- package/package.json +1 -1
package/llms-full.txt
CHANGED
|
@@ -25,7 +25,7 @@ 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](components/README.md) | The 30 components, what was left out, and one page per component |
|
|
29
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 |
|
|
30
30
|
| [Patterns](patterns.md) | Page, form, entry, confirmation, loading, empty state and mobile |
|
|
31
31
|
| [Building on it](building.md) | Base UI, loading the CSS, scoping inside a host page, theme selection and API conventions |
|
|
@@ -70,7 +70,7 @@ It is not a general-purpose UI library for outside developers. Components are ge
|
|
|
70
70
|
## What to avoid
|
|
71
71
|
|
|
72
72
|
- A brand hue on controls, or blue anywhere at rest except links.
|
|
73
|
-
- 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`.
|
|
74
74
|
- Textures, glass or blur behind text, and neumorphism.
|
|
75
75
|
- Pills for buttons, inputs or badges.
|
|
76
76
|
- Color as the only signal. Status colors always come with a word or an icon.
|
|
@@ -343,7 +343,7 @@ Every control takes one `size` prop: `xs`, `sm` or `md` (the default). There is
|
|
|
343
343
|
| sm | `control-sm` 24px | 8px | 14px | 4px | 13 / 18px | `radius-sm` 4px | 16px |
|
|
344
344
|
| md (default) | `control-md` 32px | 12px | 16px | 6px | 14 / 20px | `radius-md` 6px | 18px |
|
|
345
345
|
|
|
346
|
-
- Use `sm` for dense toolbars, table rows and filters inside panels, and `md` everywhere else, including marketing pages, onboarding and
|
|
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.
|
|
347
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.
|
|
348
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`.
|
|
349
349
|
- The icon side of a control has 2px less padding, so text and icon look centered.
|
|
@@ -417,6 +417,7 @@ Buttons, inputs and badges are never pills.
|
|
|
417
417
|
| `tooltip-width` | 240px | Maximum width of a tooltip |
|
|
418
418
|
| `toast-width` | 360px | Width of a toast from `breakpoint-sm` up |
|
|
419
419
|
| `dialog-width` | 440px | Default width of a dialog |
|
|
420
|
+
| `drawer-width` | 320px | Default width of a drawer from the start or end edge |
|
|
420
421
|
|
|
421
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.
|
|
422
423
|
- The other widths are component defaults. To change one instance, pass `className` or `style` to it.
|
|
@@ -427,7 +428,7 @@ Two breakpoints. They are values, not tokens: CSS custom properties can't be use
|
|
|
427
428
|
|
|
428
429
|
| Breakpoint | Value | Usage |
|
|
429
430
|
| --- | --- | --- |
|
|
430
|
-
| breakpoint-sm | 640px | Below this: one column, dialogs and menus become sheets, toasts
|
|
431
|
+
| breakpoint-sm | 640px | Below this: one column, dialogs and menus become sheets, toasts span the top |
|
|
431
432
|
| breakpoint-md | 1024px | From this: the sidebar is fixed and the content column is centered |
|
|
432
433
|
|
|
433
434
|
```css
|
|
@@ -525,7 +526,11 @@ import { Pin, Trash2 } from "lucide-react";
|
|
|
525
526
|
- **Color is `currentColor`.** Icons follow the text color: `ink` in controls, `ink-muted` in menu rows, the tone color in toasts.
|
|
526
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.
|
|
527
528
|
- **Accessibility.** Icons are `aria-hidden`. Every icon-only Button or Toggle has an `aria-label`, and a Tooltip with the same text.
|
|
528
|
-
- **Standalone icons.** Outside a component,
|
|
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
|
+
```
|
|
529
534
|
|
|
530
535
|
## Built-in glyphs
|
|
531
536
|
|
|
@@ -540,6 +545,7 @@ These components bring their own Lucide icons, so the app doesn't pass them:
|
|
|
540
545
|
| Collapsible | `ChevronRight` (turns 90° when open) |
|
|
541
546
|
| Toast | `Info`, `CircleCheck`, `TriangleAlert`, `CircleAlert`, `X` |
|
|
542
547
|
| Field | `CircleAlert` (errors) |
|
|
548
|
+
| Dialog, Drawer | `X` |
|
|
543
549
|
| Link | `ArrowUpRight` (external links) |
|
|
544
550
|
|
|
545
551
|
## Missing icons
|
|
@@ -550,15 +556,15 @@ When Lucide doesn't have an icon, draw it on Lucide's 24px grid with 2px round s
|
|
|
550
556
|
|
|
551
557
|
# Components
|
|
552
558
|
|
|
553
|
-
|
|
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.
|
|
554
560
|
|
|
555
561
|
| Group | Components |
|
|
556
562
|
| --- | --- |
|
|
557
563
|
| Actions | [Button](button.md), [Toggle](toggle.md), [ToggleGroup](toggle-group.md), [Menu](menu.md), [Link](link.md) |
|
|
558
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) |
|
|
559
565
|
| Navigation | [Tabs](tabs.md), [Collapsible](collapsible.md) |
|
|
560
|
-
| Overlays | [Dialog](dialog.md) (and its sheet form), [Popover](popover.md), [Tooltip](tooltip.md), [Toast](toast.md) |
|
|
561
|
-
| 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) |
|
|
562
568
|
|
|
563
569
|
## Page template
|
|
564
570
|
|
|
@@ -575,7 +581,7 @@ Components follow the [API conventions](../building.md#api-conventions): parts w
|
|
|
575
581
|
|
|
576
582
|
## Left out on purpose
|
|
577
583
|
|
|
578
|
-
- **Card.** Content is grouped by spacing, headings and separators
|
|
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).
|
|
579
585
|
- **Slider, Stepper.** No setting in our products needs a continuous range. Use an Input with `type="number"` or a Select.
|
|
580
586
|
- **Chip, SegmentedControl, Accordion.** A Toggle covers filter chips, a single-choice ToggleGroup covers segmented choices, and stacked Collapsibles cover an accordion.
|
|
581
587
|
- **Later, when a screen needs them:** multi-select Combobox, ContextMenu, ScrollArea, Progress, Calendar.
|
|
@@ -599,8 +605,11 @@ The short version. [Using the components](../usage.md) has the full decision gui
|
|
|
599
605
|
| A list of actions for one thing | Menu |
|
|
600
606
|
| A few controls that belong to one thing | Popover |
|
|
601
607
|
| A decision that blocks the page | Dialog |
|
|
608
|
+
| Navigation or details that slide in from an edge | Drawer |
|
|
602
609
|
| Name an icon-only control | Tooltip |
|
|
603
610
|
| Confirm something happened | Toast |
|
|
611
|
+
| Settings rows, or a short list of records with actions | List |
|
|
612
|
+
| Data with columns | Table |
|
|
604
613
|
| A status or tag label | Badge |
|
|
605
614
|
| A clickable tag filter | Toggle |
|
|
606
615
|
| Content still loading | Skeleton |
|
|
@@ -638,7 +647,8 @@ import { Plus } from "lucide-react";
|
|
|
638
647
|
interface ButtonProps extends BaseButton.Props {
|
|
639
648
|
variant?: "primary" | "neutral" | "ghost" | "danger"; // default "neutral"
|
|
640
649
|
size?: Size;
|
|
641
|
-
icon?: IconElement;
|
|
650
|
+
icon?: IconElement; // before the label
|
|
651
|
+
iconEnd?: IconElement; // after the label: "Show more" with a chevron, "Next" with an arrow
|
|
642
652
|
loading?: boolean;
|
|
643
653
|
loadingLabel?: string; // accessible name while loading, such as "Saving"
|
|
644
654
|
}
|
|
@@ -653,6 +663,7 @@ Wraps Base UI's [Button](https://base-ui.com/react/components/button): `disabled
|
|
|
653
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.
|
|
654
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.
|
|
655
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.
|
|
656
667
|
- A link that should look like a button stays an `<a>` with `buttonClasses()`; Base UI doesn't render links through Button:
|
|
657
668
|
|
|
658
669
|
```tsx
|
|
@@ -660,11 +671,12 @@ Wraps Base UI's [Button](https://base-ui.com/react/components/button): `disabled
|
|
|
660
671
|
```
|
|
661
672
|
|
|
662
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).
|
|
663
675
|
|
|
664
676
|
## Style
|
|
665
677
|
|
|
666
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.
|
|
667
|
-
- Only an icon: square. The icon
|
|
679
|
+
- Only an icon: square. The side with an icon (`icon` at the start, `iconEnd` at the end) has 2px less padding.
|
|
668
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.
|
|
669
681
|
- `loading` keeps the width, shows a Spinner over the label, sets `aria-busy` and `aria-disabled`, ignores presses and stays focusable.
|
|
670
682
|
- Disabled: 45% opacity.
|
|
@@ -705,6 +717,7 @@ Wraps Base UI's [Toggle](https://base-ui.com/react/components/toggle): `pressed`
|
|
|
705
717
|
## Style
|
|
706
718
|
|
|
707
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.
|
|
708
721
|
|
|
709
722
|
<!-- docs/components/toggle-group.md -->
|
|
710
723
|
|
|
@@ -760,6 +773,7 @@ Base UI's props pass through: `value`, `defaultValue`, `onValueChange`, `multipl
|
|
|
760
773
|
|
|
761
774
|
## Style
|
|
762
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.
|
|
763
777
|
- `multiple`: items 2px apart.
|
|
764
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.
|
|
765
779
|
|
|
@@ -798,7 +812,7 @@ import { Ellipsis, FolderInput, Pin, Trash2 } from "lucide-react";
|
|
|
798
812
|
| `Menu.Root` | `Menu.Root` | Nothing. Groups a trigger and its popup. Takes `size` |
|
|
799
813
|
| `Menu.Trigger` | `Menu.Trigger` | The control that opens it, usually a Button through `render` |
|
|
800
814
|
| `Menu.Popup` | `Menu.Portal`, `Positioner`, `Popup` | The list, a `<div role="menu">` portaled to the body. Also a submenu's list |
|
|
801
|
-
| `Menu.Item` | `Menu.Item` | One action. Takes `icon`, `shortcut`, `danger` |
|
|
815
|
+
| `Menu.Item` | `Menu.Item` | One action. Takes `icon`, `shortcut`, `description`, `danger` |
|
|
802
816
|
| `Menu.CheckboxItem` | `Menu.CheckboxItem` with its indicator | An option that turns on or off, with a check at the end |
|
|
803
817
|
| `Menu.RadioGroup` | `Menu.RadioGroup` | Groups RadioItems; holds the value |
|
|
804
818
|
| `Menu.RadioItem` | `Menu.RadioItem` with its indicator | One choice, with a check at the end when chosen |
|
|
@@ -821,12 +835,13 @@ interface Menu.PopupProps extends BaseMenu.Popup.Props {
|
|
|
821
835
|
interface Menu.ItemProps extends BaseMenu.Item.Props {
|
|
822
836
|
icon?: IconElement;
|
|
823
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
|
|
824
839
|
danger?: boolean; // destructive: red text
|
|
825
840
|
}
|
|
826
|
-
// CheckboxItem and RadioItem take icon and
|
|
841
|
+
// CheckboxItem and RadioItem take icon, shortcut and description too. SubmenuTrigger takes icon.
|
|
827
842
|
|
|
828
843
|
function menuClasses(options?: { size?: "sm" | "md" }): {
|
|
829
|
-
popup: string; item: string; dangerItem: string; label: string;
|
|
844
|
+
popup: string; item: string; dangerItem: string; label: string; text: string; description: string;
|
|
830
845
|
shortcut: string; separator: string; groupLabel: string;
|
|
831
846
|
};
|
|
832
847
|
```
|
|
@@ -837,16 +852,18 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on
|
|
|
837
852
|
|
|
838
853
|
- Act in `onClick`. Labels are verbs in sentence case.
|
|
839
854
|
- Put destructive items last, after a Separator, with `danger`. Confirm them with an [AlertDialog](dialog.md).
|
|
840
|
-
- CheckboxItems and RadioItems stay open on click, so people can change several.
|
|
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.
|
|
841
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).
|
|
842
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).
|
|
843
859
|
- Arrow keys move the highlight; typing jumps to an item; Escape and an outside click close it; focus returns to the trigger.
|
|
844
|
-
- 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).
|
|
845
861
|
|
|
846
862
|
## Style
|
|
847
863
|
|
|
848
864
|
- `mu-popup` with `space-1` padding, at least `menu-width` wide, scrolls when taller than the space available.
|
|
849
|
-
- 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.
|
|
850
867
|
- The highlighted row uses `hover`. Disabled rows fade to 45%. `danger` rows use `danger`.
|
|
851
868
|
- A ghost trigger keeps its `hover` background while the menu is open.
|
|
852
869
|
- Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
|
|
@@ -923,7 +940,7 @@ import { Checkbox, Field, Fieldset, Input } from "@usememos/ui";
|
|
|
923
940
|
|
|
924
941
|
| Part | Base UI part | Renders |
|
|
925
942
|
| --- | --- | --- |
|
|
926
|
-
| `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` |
|
|
927
944
|
| `Field.Label` | `Field.Label` | A `<label>`: above a text control, or around a Checkbox, Switch or Radio |
|
|
928
945
|
| `Field.Item` | `Field.Item` | A `<div>` around one option of a radio or checkbox group in one field |
|
|
929
946
|
| `Field.Description` | `Field.Description` | A `<p>` of help under the control |
|
|
@@ -936,6 +953,7 @@ import { Checkbox, Field, Fieldset, Input } from "@usememos/ui";
|
|
|
936
953
|
```ts
|
|
937
954
|
interface Field.RootProps extends BaseField.Root.Props {
|
|
938
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
|
|
939
957
|
}
|
|
940
958
|
interface Fieldset.RootProps extends BaseFieldset.Root.Props {
|
|
941
959
|
size?: "sm" | "md"; // passed to every control inside
|
|
@@ -949,13 +967,23 @@ Base UI's props pass through: `name`, `invalid`, `disabled`, `validate`, `valida
|
|
|
949
967
|
- Set `invalid` from the app when the server rejects a value; use `validate` for client checks.
|
|
950
968
|
- Write the error as how to fix the problem.
|
|
951
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
|
+
```
|
|
952
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).
|
|
953
|
-
- Stack fields `space-4` apart. Group them with a heading or a Fieldset,
|
|
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.
|
|
954
981
|
- Never use a placeholder as the only label.
|
|
955
982
|
|
|
956
983
|
## Style
|
|
957
984
|
|
|
958
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.
|
|
959
987
|
- Description and error: `caption` (13px), below the control. The error hides the description and turns the control's edge `danger`.
|
|
960
988
|
|
|
961
989
|
<!-- docs/components/input.md -->
|
|
@@ -1076,7 +1104,7 @@ const visibility = { private: "Private", workspace: "Workspace", public: "Public
|
|
|
1076
1104
|
| Part | Base UI part | Renders |
|
|
1077
1105
|
| --- | --- | --- |
|
|
1078
1106
|
| `Select.Root` | `Select.Root` | Nothing. Holds the value and a hidden input for forms. Takes `size` |
|
|
1079
|
-
| `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` |
|
|
1080
1108
|
| `Select.Value` | `Select.Value` | The chosen item's label, or the placeholder |
|
|
1081
1109
|
| `Select.Popup` | `Select.Portal`, `Positioner`, `Popup`, `List`, scroll arrows | The list, portaled to the body |
|
|
1082
1110
|
| `Select.Item` | `Select.Item` with `ItemText` and `ItemIndicator` | One option, with a check at the end when chosen. Takes `icon` |
|
|
@@ -1090,6 +1118,9 @@ const visibility = { private: "Private", workspace: "Workspace", public: "Public
|
|
|
1090
1118
|
type Select.RootProps<Value, Multiple> = BaseSelect.Root.Props<Value, Multiple> & {
|
|
1091
1119
|
size?: "sm" | "md";
|
|
1092
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
|
+
}
|
|
1093
1124
|
interface Select.PopupProps extends BaseSelect.Popup.Props {
|
|
1094
1125
|
alignItemWithTrigger?: boolean; // default true: the list opens over the trigger with the chosen item on the value
|
|
1095
1126
|
side?: Side; align?: Align; // used when alignItemWithTrigger is false
|
|
@@ -1108,10 +1139,11 @@ Base UI's props pass through: `items`, `value`, `defaultValue`, `onValueChange`,
|
|
|
1108
1139
|
- Label it with a [Field](field.md), or `aria-label` on the trigger.
|
|
1109
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.
|
|
1110
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`.
|
|
1111
1143
|
|
|
1112
1144
|
## Style
|
|
1113
1145
|
|
|
1114
|
-
- 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.
|
|
1115
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`.
|
|
1116
1148
|
- Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
|
|
1117
1149
|
|
|
@@ -1157,6 +1189,7 @@ const tags = ["book", "design", "travel"];
|
|
|
1157
1189
|
| `Combobox.Group` | `Combobox.Group` | A labeled set of items |
|
|
1158
1190
|
| `Combobox.GroupLabel` | `Combobox.GroupLabel` | The name of a Group |
|
|
1159
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 |
|
|
1160
1193
|
| `Combobox.Collection` | `Combobox.Collection` | Base UI's part, unstyled: renders one group's items from grouped `items` |
|
|
1161
1194
|
| `Combobox.useFilter` | `Combobox.useFilter` | Base UI's hook: locale-aware `contains`, `startsWith`, `endsWith` for `filter` |
|
|
1162
1195
|
|
|
@@ -1191,6 +1224,26 @@ Base UI's props pass through: `items`, `value`, `defaultValue`, `onValueChange`,
|
|
|
1191
1224
|
- Use it for more than about 10 options, or when people type to find one. For fewer, use [Select](select.md).
|
|
1192
1225
|
- Typing opens the list. Up and Down move the highlight, Enter picks, Escape closes; focus stays in the input.
|
|
1193
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`.
|
|
1194
1247
|
|
|
1195
1248
|
## Style
|
|
1196
1249
|
|
|
@@ -1198,6 +1251,7 @@ Base UI's props pass through: `items`, `value`, `defaultValue`, `onValueChange`,
|
|
|
1198
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.
|
|
1199
1252
|
- Rows are a control height, text weight 400; the highlighted row uses `hover`; the chosen row has a check at the end.
|
|
1200
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.
|
|
1201
1255
|
- Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
|
|
1202
1256
|
|
|
1203
1257
|
<!-- docs/components/checkbox.md -->
|
|
@@ -1497,10 +1551,11 @@ Dialog and AlertDialog have the same parts.
|
|
|
1497
1551
|
```ts
|
|
1498
1552
|
interface Dialog.PopupProps extends BaseDialog.Popup.Props {
|
|
1499
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)
|
|
1500
1555
|
closeLabel?: string; // accessible name of the X; default "Close"
|
|
1501
1556
|
container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null; // portal target
|
|
1502
1557
|
}
|
|
1503
|
-
// AlertDialog.Popup takes sheet and container; it has no X.
|
|
1558
|
+
// AlertDialog.Popup takes sheet and container; it has no X and no variant.
|
|
1504
1559
|
```
|
|
1505
1560
|
|
|
1506
1561
|
Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `disablePointerDismissal` on the root; `initialFocus`, `finalFocus` on the popup.
|
|
@@ -1512,6 +1567,7 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `d
|
|
|
1512
1567
|
- Use AlertDialog for confirmations; see the [confirmation pattern](../patterns.md#confirmation). Open it from a Menu item with `open` and `onOpenChange`.
|
|
1513
1568
|
- Escape and a backdrop click close a Dialog. While a form has unsaved input, keep it open from `onOpenChange`.
|
|
1514
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.
|
|
1515
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.
|
|
1516
1572
|
|
|
1517
1573
|
## Style
|
|
@@ -1520,6 +1576,70 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `d
|
|
|
1520
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.
|
|
1521
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.
|
|
1522
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.
|
|
1523
1643
|
|
|
1524
1644
|
<!-- docs/components/popover.md -->
|
|
1525
1645
|
|
|
@@ -1559,9 +1679,10 @@ import { Button, Input, Popover } from "@usememos/ui";
|
|
|
1559
1679
|
interface Popover.PopupProps extends BasePopover.Popup.Props {
|
|
1560
1680
|
side?: Side; align?: Align; // Positioner props; default side "bottom"
|
|
1561
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
|
|
1562
1683
|
}
|
|
1563
1684
|
|
|
1564
|
-
function popoverClasses(): { popup: string; title: string; description: string };
|
|
1685
|
+
function popoverClasses(options?: { variant?: "panel" | "list" }): { popup: string; title: string; description: string };
|
|
1565
1686
|
```
|
|
1566
1687
|
|
|
1567
1688
|
Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on the root; `openOnHover`, `delay` on the trigger; `initialFocus`, `finalFocus` on the popup.
|
|
@@ -1570,6 +1691,7 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on
|
|
|
1570
1691
|
|
|
1571
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).
|
|
1572
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.
|
|
1573
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.
|
|
1574
1696
|
- It closes on Escape and on a click outside; focus returns to the trigger.
|
|
1575
1697
|
- An app's own floating panel, such as an emoji picker, takes `popoverClasses()`. See [Class functions](../custom-components.md#class-functions).
|
|
@@ -1578,6 +1700,7 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on
|
|
|
1578
1700
|
|
|
1579
1701
|
- `mu-popup` (`surface`, `shadow-pop`, `radius-xl`), `space-4` padding, `popover-width` wide. Children stack `space-3` apart.
|
|
1580
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.
|
|
1581
1704
|
- Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
|
|
1582
1705
|
|
|
1583
1706
|
<!-- docs/components/tooltip.md -->
|
|
@@ -1693,9 +1816,9 @@ Base UI's props pass through: `timeout` (default 5000ms) and `limit` (default 3)
|
|
|
1693
1816
|
|
|
1694
1817
|
## Style
|
|
1695
1818
|
|
|
1696
|
-
- `mu-popup`, `toast-width` wide,
|
|
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.
|
|
1697
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.
|
|
1698
|
-
- Newest in front; the ones behind peek out `space-2`
|
|
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.
|
|
1699
1822
|
|
|
1700
1823
|
<!-- docs/components/avatar.md -->
|
|
1701
1824
|
|
|
@@ -1970,7 +2093,7 @@ No Base UI part: these are styled native table elements.
|
|
|
1970
2093
|
|
|
1971
2094
|
| Part | Renders |
|
|
1972
2095
|
| --- | --- |
|
|
1973
|
-
| `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>` |
|
|
1974
2097
|
| `Table.Caption` | `<caption>`, under the table. Names it for screen readers |
|
|
1975
2098
|
| `Table.Header` | `<thead>` |
|
|
1976
2099
|
| `Table.Body` | `<tbody>` |
|
|
@@ -1982,7 +2105,8 @@ No Base UI part: these are styled native table elements.
|
|
|
1982
2105
|
|
|
1983
2106
|
```ts
|
|
1984
2107
|
interface Table.RootProps extends React.ComponentProps<"table"> {
|
|
1985
|
-
size?: "sm" | "md";
|
|
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
|
|
1986
2110
|
}
|
|
1987
2111
|
interface Table.HeadProps extends React.ComponentProps<"th"> {
|
|
1988
2112
|
num?: boolean; // right-aligned, for a column of numbers
|
|
@@ -2002,14 +2126,79 @@ Every native attribute passes through.
|
|
|
2002
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`.
|
|
2003
2127
|
- Name the table with a Caption, or `aria-label` on the root.
|
|
2004
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.
|
|
2005
2130
|
|
|
2006
2131
|
## Style
|
|
2007
2132
|
|
|
2008
|
-
- No
|
|
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.
|
|
2009
2135
|
- Heads: `label-sm` (13px, 500) in `ink-muted`, a control height tall.
|
|
2010
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.
|
|
2011
2137
|
- Cells pad `px` at the sides. Caption: `caption` in `ink-muted`, `space-2` above.
|
|
2012
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
|
+
|
|
2013
2202
|
<!-- docs/components/prose.md -->
|
|
2014
2203
|
|
|
2015
2204
|
# Prose
|
|
@@ -2088,6 +2277,7 @@ The most common mistake is reaching for the wrong overlay. Decide by what the fl
|
|
|
2088
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 |
|
|
2089
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 |
|
|
2090
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 |
|
|
2091
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" |
|
|
2092
2282
|
|
|
2093
2283
|
### Menu or Popover
|
|
@@ -2165,7 +2355,8 @@ Answer in order; the first yes decides.
|
|
|
2165
2355
|
| Details or advanced settings people rarely need | [Collapsible](components/collapsible.md) | A Dialog |
|
|
2166
2356
|
| Several sections, one open at a time | Collapsibles, controlled together | A third-party accordion |
|
|
2167
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 |
|
|
2168
|
-
| Data with columns (tokens, users, webhooks) | [Table](components/table.md) | A
|
|
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 |
|
|
2169
2360
|
| A list of content (notes, comments) | [Entries](patterns.md#entry) | A Table |
|
|
2170
2361
|
|
|
2171
2362
|
## Picking feedback
|
|
@@ -2282,17 +2473,21 @@ const [confirming, setConfirming] = useState(false);
|
|
|
2282
2473
|
### Settings that save at once
|
|
2283
2474
|
|
|
2284
2475
|
```tsx
|
|
2285
|
-
<
|
|
2286
|
-
|
|
2287
|
-
<
|
|
2288
|
-
<Field.
|
|
2289
|
-
<
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
</
|
|
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>
|
|
2294
2487
|
```
|
|
2295
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
|
+
|
|
2296
2491
|
No Save button and no toast: the Switch shows the result. If saving fails, move the Switch back and show a danger Toast.
|
|
2297
2492
|
|
|
2298
2493
|
### A toolbar
|
|
@@ -2319,6 +2514,25 @@ One `Tooltip.Provider` around the toolbar (or the app) so moving along it opens
|
|
|
2319
2514
|
- Show one or two frequent actions as xs or sm ghost Buttons beside it, each with a Tooltip.
|
|
2320
2515
|
- On an entry the trigger appears on hover; on touch screens it is always visible (see [Entry](patterns.md#entry)).
|
|
2321
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
|
+
|
|
2322
2536
|
### Inside a shadow root or a host page
|
|
2323
2537
|
|
|
2324
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).
|
|
@@ -2331,7 +2545,7 @@ In an extension or an embed, pass the same `container` to every `Popup` and to `
|
|
|
2331
2545
|
## Size
|
|
2332
2546
|
|
|
2333
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.
|
|
2334
|
-
- **Pick by place, not by importance.** `md` everywhere by default, including dialogs, forms, onboarding and
|
|
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).
|
|
2335
2549
|
- **Never make a control bigger to make it stronger.** Emphasis is the `primary` variant.
|
|
2336
2550
|
- **Don't mix sizes in one row.** A Button beside an Input has the Input's size.
|
|
2337
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.
|
|
@@ -2385,7 +2599,11 @@ Run through it for every screen.
|
|
|
2385
2599
|
| A Checkbox that saves at once | A Switch |
|
|
2386
2600
|
| A Select with 30 options | A Combobox |
|
|
2387
2601
|
| A Select with two options and room to spare | A RadioGroup |
|
|
2388
|
-
| A boxed card around each item | Whitespace, a section title, or a Separator |
|
|
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 |
|
|
2389
2607
|
| A Spinner where a list will appear | Skeletons in the shape of the list |
|
|
2390
2608
|
| `size="md"` or `"lg"` on a Button to emphasize it | `variant="primary"` at the row's size |
|
|
2391
2609
|
| `size` on every part of a Menu or Select | `size` on its Root |
|
|
@@ -2405,14 +2623,18 @@ Names and parts from other libraries and where they go.
|
|
|
2405
2623
|
| --- | --- |
|
|
2406
2624
|
| Modal, Dialog | [Dialog](components/dialog.md); `sheet` covers phones |
|
|
2407
2625
|
| ConfirmDialog, AlertDialog, `window.confirm` | [AlertDialog](components/dialog.md) |
|
|
2408
|
-
| Drawer, Sheet |
|
|
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` |
|
|
2409
2631
|
| DropdownMenu, ActionMenu | [Menu](components/menu.md) |
|
|
2410
2632
|
| ContextMenu | Not built. Put the same actions in a Menu on a visible trigger |
|
|
2411
2633
|
| HoverCard | [Popover](components/popover.md) with `openOnHover` on the trigger |
|
|
2412
2634
|
| Tooltip with a `title` or `content` prop | [Tooltip](components/tooltip.md) parts: Root, Trigger, Popup |
|
|
2413
2635
|
| Snackbar, Toaster, sonner's `toast()` | [Toast](components/toast.md) and `Toast.useToastManager().add()` |
|
|
2414
2636
|
| Toast `variant="destructive"` | `type: "danger"` |
|
|
2415
|
-
| Card, Paper, Panel | No component: spacing, a section title and [Separator](components/separator.md) |
|
|
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) |
|
|
2416
2638
|
| Divider | [Separator](components/separator.md) |
|
|
2417
2639
|
| Chip, Tag | [Badge](components/badge.md) to show, [Toggle](components/toggle.md) to filter |
|
|
2418
2640
|
| SegmentedControl, RadioButtons in a row | [ToggleGroup](components/toggle-group.md) without `multiple` |
|
|
@@ -2441,7 +2663,8 @@ How the components go together. Each product builds its screens from these patte
|
|
|
2441
2663
|
|
|
2442
2664
|
## Page
|
|
2443
2665
|
|
|
2444
|
-
- From `breakpoint-md` (1024px) up: a fixed sidebar of `sidebar-width` (240px). Below it, the sidebar is a
|
|
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.
|
|
2445
2668
|
- Content sits in a column of at most `content-width` (720px), padded `space-4` on phones and `space-6` on desktop.
|
|
2446
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.
|
|
2447
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.
|
|
@@ -2452,6 +2675,7 @@ How the components go together. Each product builds its screens from these patte
|
|
|
2452
2675
|
- Related checkboxes or switches sit in a `Fieldset`.
|
|
2453
2676
|
- Actions go at the end, right-aligned: a ghost Cancel, then the primary.
|
|
2454
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.
|
|
2455
2679
|
- Errors appear under the field, in words that say how to fix it.
|
|
2456
2680
|
|
|
2457
2681
|
## Entry
|
|
@@ -2493,7 +2717,7 @@ Under `breakpoint-sm` (640px):
|
|
|
2493
2717
|
|
|
2494
2718
|
- One column.
|
|
2495
2719
|
- Dialogs and menus become sheets.
|
|
2496
|
-
- Toasts
|
|
2720
|
+
- Toasts span the top of the screen.
|
|
2497
2721
|
- Every control keeps its 44px hit area.
|
|
2498
2722
|
- Controls stay `md`.
|
|
2499
2723
|
|
|
@@ -2559,7 +2783,7 @@ An app changes only the color, shadow and font tokens. Every other token and eve
|
|
|
2559
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).
|
|
2560
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).
|
|
2561
2785
|
- `mu-prose` styles rendered content. See [Prose](components/prose.md).
|
|
2562
|
-
- `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).
|
|
2563
2787
|
- `mu-visually-hidden` hides text visually but keeps it for screen readers.
|
|
2564
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).
|
|
2565
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).
|
|
@@ -2671,13 +2895,13 @@ A class function returns the classes that make any element look like a component
|
|
|
2671
2895
|
| `inputClasses({ size })` | A string | One-line field, such as a tag input around a native `<input>` |
|
|
2672
2896
|
| `textareaClasses({ size })` | A string | Multi-line field, such as a content-editable editor |
|
|
2673
2897
|
| `badgeClasses({ tone })` | A string | Label, such as a tag that is also a link |
|
|
2674
|
-
| `menuClasses({ size })` | `popup`, `item`, `dangerItem`, `label`, `shortcut`, `separator`, `groupLabel` | Floating list, such as an editor's suggestion list |
|
|
2675
|
-
| `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 |
|
|
2676
2900
|
|
|
2677
2901
|
State is set with attributes, never with extra classes:
|
|
2678
2902
|
|
|
2679
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.
|
|
2680
|
-
- 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.
|
|
2681
2905
|
- A popup fades in with `data-starting-style` and out with `data-ending-style`, which Base UI's popups set.
|
|
2682
2906
|
|
|
2683
2907
|
```tsx
|
|
@@ -2718,7 +2942,10 @@ Apply these classes to an app's own elements. Don't override their rules: the pr
|
|
|
2718
2942
|
| `mu-positioner` | The popup layer | A popup's positioner |
|
|
2719
2943
|
| `mu-focus` | The ink focus ring on `:focus-visible`, 1px outside the edge | Anything focusable |
|
|
2720
2944
|
| `mu-hit` | An invisible 44px touch target on `pointer: coarse` | Anything pressable smaller than 44px |
|
|
2721
|
-
| `mu-icon` | Icon size and stroke | A Lucide icon
|
|
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 |
|
|
2722
2949
|
| `mu-visually-hidden` | Hidden on screen, read by screen readers | Text for screen readers only |
|
|
2723
2950
|
| `mu-label-xs` | 12 / 16px, 500 | Labels of 20px parts |
|
|
2724
2951
|
| `mu-caption` | 13 / 18px, 400 | Secondary text |
|