@usememos/ui 26.11.1 → 26.11.3
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/badge/index.js.map +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/form/index.js +3 -0
- package/dist/components/form/index.js.map +7 -0
- 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/components/toggle-group/index.js +1 -1
- package/dist/components/toggle-group/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/badge/index.d.ts +2 -2
- 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 +14 -1
- package/dist/types/components/drawer/index.d.ts +44 -0
- package/dist/types/components/field/index.d.ts +6 -0
- package/dist/types/components/form/index.d.ts +28 -0
- package/dist/types/components/list/index.d.ts +26 -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 +10 -0
- package/dist/types/components/toast/index.d.ts +1 -1
- package/dist/types/components/toggle-group/index.d.ts +2 -0
- package/dist/types/index.d.ts +5 -1
- package/llms-full.txt +487 -68
- package/llms.txt +4 -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 31 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.
|
|
@@ -267,7 +267,7 @@ Each depth class applies a whole role in one class. Use them for new parts befor
|
|
|
267
267
|
|
|
268
268
|
- Use few sizes and few weights. App text uses four sizes (13, 14, 16 and 20px) and two weights (400 and 500); 12px is only for the labels of 20px parts (see `label-xs`). Hierarchy comes from text color, spacing and grouping first, then from weight, and from size last.
|
|
269
269
|
- Weight 400 is for running text and values people typed or chose. Weight 500 is for labels, controls, titles and headings. The UI uses no 600 or 700. The one exception is [Prose](components/prose.md): bold text and headings that people wrote are 600, because 500 does not read as bold in system fonts.
|
|
270
|
-
- 20px (`heading`) is for the page title only, once per page. 32px (`display`) is for the hero headline on a website only, once per page. Nothing else is larger than 16px, and 16px is only for `title` and Prose text.
|
|
270
|
+
- 20px (`heading`) is for the page title only, once per page. 32px (`display`) is for the hero headline on a website only, once per page. Nothing else in the UI is larger than 16px, and 16px is only for `title` and Prose text. Headings that people write inside Prose have their own sizes (see [Prose](#prose)).
|
|
271
271
|
- A section title inside a page or panel is `label` (14px, 500), set apart by space above it, not by a larger size. See [Page](patterns.md#page).
|
|
272
272
|
- Secondary text (descriptions, helper text, metadata) is `caption` (13px) in `ink-muted`. No text is smaller than 13px, except `label-xs` inside 20px parts.
|
|
273
273
|
- Control text scales with the control size: 12px at xs, 13px at sm, 14px at md.
|
|
@@ -277,16 +277,18 @@ Each depth class applies a whole role in one class. Use them for new parts befor
|
|
|
277
277
|
- Selected and unselected states never change weight, so labels don't shift.
|
|
278
278
|
- Adding a size, a weight or a style needs a written reason in this page and the maintainer's sign-off.
|
|
279
279
|
- The platform's UI font for everything (San Francisco on Apple, Segoe UI on Windows, Roboto on Android), smoothed with antialiasing. The package ships no font files.
|
|
280
|
+
- Titles and headings (`title`, `heading`, `display`, and headings in Prose) use `text-wrap: balance`, so a heading that wraps does not leave one word on its last line. Prose paragraphs and list items use `text-wrap: pretty`. Browsers without these wrap as usual.
|
|
281
|
+
- Typed text in fields is 16px on touch screens (`pointer: coarse`), whatever the field's size: below 16px, iOS Safari zooms the page in when a field takes focus. The field keeps its height; its label and placeholder layout do not change.
|
|
280
282
|
|
|
281
283
|
## Fonts
|
|
282
284
|
|
|
283
285
|
| Family | Stack |
|
|
284
286
|
| --- | --- |
|
|
285
|
-
| `--mu-font-sans` |
|
|
286
|
-
| `--mu-font-display` |
|
|
287
|
+
| `--mu-font-sans` | `-apple-system, BlinkMacSystemFont, "Segoe UI (mu)", system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` |
|
|
288
|
+
| `--mu-font-display` | `-apple-system, BlinkMacSystemFont, "Segoe UI (mu)", system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` |
|
|
287
289
|
| `--mu-font-mono` | `ui-monospace, "SF Mono", Menlo, Consolas, monospace` |
|
|
288
290
|
|
|
289
|
-
The package ships no font files and loads no remote font: text uses the system UI font, so it looks native on each platform and costs no download. An app that wants another font sets `--mu-font-sans`, `--mu-font-display` or `--mu-font-mono` after loading `tokens.css`, and loads the font itself. These are the only type tokens an app may set: sizes, line heights, weights and letter spacing are fixed (see [What an app can change](color.md#what-an-app-can-change)). Code in [Prose](components/prose.md) uses `font-mono`, the platform's monospace font by default.
|
|
291
|
+
The package ships no font files and loads no remote font: text uses the system UI font, so it looks native on each platform and costs no download. Do not import or define another font family. The one exception is `"Segoe UI (mu)"`, an alias of the installed Windows UI font that the stylesheet defines with `local()` sources only, so it loads no file. Segoe UI has Regular, Semibold and Bold faces but no Medium, so with a plain stack the browser draws weight 500 at 400 and every label on Windows loses its weight. The alias draws 500 and 600 with the installed Semibold face. It comes after `-apple-system` and `BlinkMacSystemFont`, so Apple platforms still get San Francisco, and before `system-ui`, which is plain Segoe UI on Windows. An app that wants another font sets `--mu-font-sans`, `--mu-font-display` or `--mu-font-mono` after loading `tokens.css`, and loads the font itself. These are the only type tokens an app may set: sizes, line heights, weights and letter spacing are fixed (see [What an app can change](color.md#what-an-app-can-change)). Code in [Prose](components/prose.md) uses `font-mono`, the platform's monospace font by default.
|
|
290
292
|
|
|
291
293
|
## Scale
|
|
292
294
|
|
|
@@ -327,7 +329,7 @@ The scale used to have 12 sizes and 3 weights. These styles were removed; use th
|
|
|
327
329
|
|
|
328
330
|
## Prose
|
|
329
331
|
|
|
330
|
-
Rendered content (notes, comments, changelogs, website pages) uses the `Prose` styles, not the scale above. It uses
|
|
332
|
+
Rendered content (notes, comments, changelogs, website pages) uses the `Prose` styles, not the scale above. It uses 16px text (14px at `sm`) and 13px table heads, and has three heading sizes: 24px `h1`, 20px `h2`, and 16px `h3`–`h6`. 24px appears nowhere else. The reason: notes have sections and subsections, and when `h2` and `h3` were both the size of the text they read as one level, and as bold sentences. At `sm`, `h1` is 16px and `h2`–`h6` are 14px. See [Prose](components/prose.md).
|
|
331
333
|
|
|
332
334
|
<!-- docs/layout.md -->
|
|
333
335
|
|
|
@@ -343,7 +345,7 @@ Every control takes one `size` prop: `xs`, `sm` or `md` (the default). There is
|
|
|
343
345
|
| sm | `control-sm` 24px | 8px | 14px | 4px | 13 / 18px | `radius-sm` 4px | 16px |
|
|
344
346
|
| md (default) | `control-md` 32px | 12px | 16px | 6px | 14 / 20px | `radius-md` 6px | 18px |
|
|
345
347
|
|
|
346
|
-
- 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.
|
|
348
|
+
- Use `sm` for dense toolbars, table rows and filters inside panels, and `md` everywhere else, including marketing pages, onboarding and sheets. A sidebar with 28px rows puts the `mu-compact` class on itself: md controls and rows inside are `control-compact` (28px) tall, with md text, icons, padding and radius (see [A sidebar navigation row](usage.md#a-sidebar-navigation-row)). It is the only height between sizes, and it is not a `size` value. Emphasis comes from the `primary` variant, not from a bigger control. On touch screens the hit area, not the visual size, grows.
|
|
347
349
|
- 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
350
|
- 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
351
|
- The icon side of a control has 2px less padding, so text and icon look centered.
|
|
@@ -396,6 +398,7 @@ Buttons, inputs and badges are never pills.
|
|
|
396
398
|
| `control-xs` | 20px | xs control height; Badge and Kbd |
|
|
397
399
|
| `control-sm` | 24px | sm control height |
|
|
398
400
|
| `control-md` | 32px | Default control height |
|
|
401
|
+
| `control-compact` | 28px | md height inside `mu-compact`, for an app's sidebar |
|
|
399
402
|
| `inset` | 2px | Well-to-key inset at every size |
|
|
400
403
|
| `hit-area` | 44px | Minimum touch target on `pointer: coarse`, added invisibly around a control |
|
|
401
404
|
| `focus-width` | 2px | Width of the keyboard focus ring |
|
|
@@ -417,6 +420,7 @@ Buttons, inputs and badges are never pills.
|
|
|
417
420
|
| `tooltip-width` | 240px | Maximum width of a tooltip |
|
|
418
421
|
| `toast-width` | 360px | Width of a toast from `breakpoint-sm` up |
|
|
419
422
|
| `dialog-width` | 440px | Default width of a dialog |
|
|
423
|
+
| `drawer-width` | 320px | Default width of a drawer from the start or end edge |
|
|
420
424
|
|
|
421
425
|
- `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
426
|
- The other widths are component defaults. To change one instance, pass `className` or `style` to it.
|
|
@@ -427,7 +431,7 @@ Two breakpoints. They are values, not tokens: CSS custom properties can't be use
|
|
|
427
431
|
|
|
428
432
|
| Breakpoint | Value | Usage |
|
|
429
433
|
| --- | --- | --- |
|
|
430
|
-
| breakpoint-sm | 640px | Below this: one column, dialogs and menus become sheets, toasts
|
|
434
|
+
| breakpoint-sm | 640px | Below this: one column, dialogs and menus become sheets, toasts span the top |
|
|
431
435
|
| breakpoint-md | 1024px | From this: the sidebar is fixed and the content column is centered |
|
|
432
436
|
|
|
433
437
|
```css
|
|
@@ -525,7 +529,11 @@ import { Pin, Trash2 } from "lucide-react";
|
|
|
525
529
|
- **Color is `currentColor`.** Icons follow the text color: `ink` in controls, `ink-muted` in menu rows, the tone color in toasts.
|
|
526
530
|
- **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
531
|
- **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,
|
|
532
|
+
- **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.
|
|
533
|
+
|
|
534
|
+
```tsx
|
|
535
|
+
<h2 className="mu-title"><Inbox className="mu-icon-sm" aria-hidden /> Inbox</h2>
|
|
536
|
+
```
|
|
529
537
|
|
|
530
538
|
## Built-in glyphs
|
|
531
539
|
|
|
@@ -540,6 +548,7 @@ These components bring their own Lucide icons, so the app doesn't pass them:
|
|
|
540
548
|
| Collapsible | `ChevronRight` (turns 90° when open) |
|
|
541
549
|
| Toast | `Info`, `CircleCheck`, `TriangleAlert`, `CircleAlert`, `X` |
|
|
542
550
|
| Field | `CircleAlert` (errors) |
|
|
551
|
+
| Dialog, Drawer | `X` |
|
|
543
552
|
| Link | `ArrowUpRight` (external links) |
|
|
544
553
|
|
|
545
554
|
## Missing icons
|
|
@@ -550,15 +559,15 @@ When Lucide doesn't have an icon, draw it on Lucide's 24px grid with 2px round s
|
|
|
550
559
|
|
|
551
560
|
# Components
|
|
552
561
|
|
|
553
|
-
|
|
562
|
+
31 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
563
|
|
|
555
564
|
| Group | Components |
|
|
556
565
|
| --- | --- |
|
|
557
566
|
| Actions | [Button](button.md), [Toggle](toggle.md), [ToggleGroup](toggle-group.md), [Menu](menu.md), [Link](link.md) |
|
|
558
|
-
| 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) |
|
|
567
|
+
| Forms | [Form](form.md), [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
568
|
| 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) |
|
|
569
|
+
| Overlays | [Dialog](dialog.md) (and its sheet form), [Drawer](drawer.md), [Popover](popover.md), [Tooltip](tooltip.md), [Toast](toast.md) |
|
|
570
|
+
| 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
571
|
|
|
563
572
|
## Page template
|
|
564
573
|
|
|
@@ -575,7 +584,7 @@ Components follow the [API conventions](../building.md#api-conventions): parts w
|
|
|
575
584
|
|
|
576
585
|
## Left out on purpose
|
|
577
586
|
|
|
578
|
-
- **Card.** Content is grouped by spacing, headings and separators
|
|
587
|
+
- **Card.** Content is grouped by spacing, headings and separators. The only boxes are a [List](list.md) of settings or records, a `bordered` [Table](table.md) and a `bordered` [Form](form.md) for a small form among them.
|
|
579
588
|
- **Slider, Stepper.** No setting in our products needs a continuous range. Use an Input with `type="number"` or a Select.
|
|
580
589
|
- **Chip, SegmentedControl, Accordion.** A Toggle covers filter chips, a single-choice ToggleGroup covers segmented choices, and stacked Collapsibles cover an accordion.
|
|
581
590
|
- **Later, when a screen needs them:** multi-select Combobox, ContextMenu, ScrollArea, Progress, Calendar.
|
|
@@ -599,8 +608,12 @@ The short version. [Using the components](../usage.md) has the full decision gui
|
|
|
599
608
|
| A list of actions for one thing | Menu |
|
|
600
609
|
| A few controls that belong to one thing | Popover |
|
|
601
610
|
| A decision that blocks the page | Dialog |
|
|
611
|
+
| Navigation or details that slide in from an edge | Drawer |
|
|
602
612
|
| Name an icon-only control | Tooltip |
|
|
603
613
|
| Confirm something happened | Toast |
|
|
614
|
+
| Settings rows, or a short list of records with actions | List |
|
|
615
|
+
| Data with columns | Table |
|
|
616
|
+
| A small form (heading, a few fields, buttons) among Lists on a settings page | Form with `bordered` |
|
|
604
617
|
| A status or tag label | Badge |
|
|
605
618
|
| A clickable tag filter | Toggle |
|
|
606
619
|
| Content still loading | Skeleton |
|
|
@@ -638,7 +651,8 @@ import { Plus } from "lucide-react";
|
|
|
638
651
|
interface ButtonProps extends BaseButton.Props {
|
|
639
652
|
variant?: "primary" | "neutral" | "ghost" | "danger"; // default "neutral"
|
|
640
653
|
size?: Size;
|
|
641
|
-
icon?: IconElement;
|
|
654
|
+
icon?: IconElement; // before the label
|
|
655
|
+
iconEnd?: IconElement; // after the label: "Show more" with a chevron, "Next" with an arrow
|
|
642
656
|
loading?: boolean;
|
|
643
657
|
loadingLabel?: string; // accessible name while loading, such as "Saving"
|
|
644
658
|
}
|
|
@@ -653,6 +667,7 @@ Wraps Base UI's [Button](https://base-ui.com/react/components/button): `disabled
|
|
|
653
667
|
- 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
668
|
- `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
669
|
- Use `loading` for any action that waits on a server.
|
|
670
|
+
- `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
671
|
- A link that should look like a button stays an `<a>` with `buttonClasses()`; Base UI doesn't render links through Button:
|
|
657
672
|
|
|
658
673
|
```tsx
|
|
@@ -660,11 +675,12 @@ Wraps Base UI's [Button](https://base-ui.com/react/components/button): `disabled
|
|
|
660
675
|
```
|
|
661
676
|
|
|
662
677
|
- Prefer saying why an action is unavailable over disabling it.
|
|
678
|
+
- 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
679
|
|
|
664
680
|
## Style
|
|
665
681
|
|
|
666
682
|
- 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
|
|
683
|
+
- Only an icon: square. The side with an icon (`icon` at the start, `iconEnd` at the end) has 2px less padding.
|
|
668
684
|
- 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
685
|
- `loading` keeps the width, shows a Spinner over the label, sets `aria-busy` and `aria-disabled`, ignores presses and stays focusable.
|
|
670
686
|
- Disabled: 45% opacity.
|
|
@@ -705,6 +721,7 @@ Wraps Base UI's [Toggle](https://base-ui.com/react/components/toggle): `pressed`
|
|
|
705
721
|
## Style
|
|
706
722
|
|
|
707
723
|
- Off: flat `ink-muted`. On: sits in a `well` with `ink` text.
|
|
724
|
+
- 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
725
|
|
|
709
726
|
<!-- docs/components/toggle-group.md -->
|
|
710
727
|
|
|
@@ -744,7 +761,8 @@ import { Bold, Code, Italic } from "lucide-react";
|
|
|
744
761
|
|
|
745
762
|
```ts
|
|
746
763
|
interface ToggleGroupProps extends BaseToggleGroup.Props {
|
|
747
|
-
size?: Size;
|
|
764
|
+
size?: Size; // passed to every Toggle inside
|
|
765
|
+
fullWidth?: boolean; // fills the container; labeled Toggles share the width equally
|
|
748
766
|
}
|
|
749
767
|
```
|
|
750
768
|
|
|
@@ -757,9 +775,11 @@ Base UI's props pass through: `value`, `defaultValue`, `onValueChange`, `multipl
|
|
|
757
775
|
- Without `multiple`: filters and view switches that don't change the whole page. For sections of a page, use Tabs.
|
|
758
776
|
- Base UI lets people press the chosen item to clear it. When one item must stay chosen, ignore an empty value, as in the example.
|
|
759
777
|
- Arrow keys move focus between items; Space or Enter presses one.
|
|
778
|
+
- Set `fullWidth` when the group is the only control on its row and should line up with the edges, such as a view switch across a popover. Don't set `className="w-full"` and `flex-1` on the Toggles yourself.
|
|
760
779
|
|
|
761
780
|
## Style
|
|
762
781
|
|
|
782
|
+
- At most as wide as its container. With `fullWidth`, exactly as wide, and labeled Toggles take equal shares (icon-only ones keep their size). When the labels don't fit, they shrink with an ellipsis; icon-only items keep their size.
|
|
763
783
|
- `multiple`: items 2px apart.
|
|
764
784
|
- 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
785
|
|
|
@@ -798,7 +818,7 @@ import { Ellipsis, FolderInput, Pin, Trash2 } from "lucide-react";
|
|
|
798
818
|
| `Menu.Root` | `Menu.Root` | Nothing. Groups a trigger and its popup. Takes `size` |
|
|
799
819
|
| `Menu.Trigger` | `Menu.Trigger` | The control that opens it, usually a Button through `render` |
|
|
800
820
|
| `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` |
|
|
821
|
+
| `Menu.Item` | `Menu.Item` | One action. Takes `icon`, `shortcut`, `description`, `danger` |
|
|
802
822
|
| `Menu.CheckboxItem` | `Menu.CheckboxItem` with its indicator | An option that turns on or off, with a check at the end |
|
|
803
823
|
| `Menu.RadioGroup` | `Menu.RadioGroup` | Groups RadioItems; holds the value |
|
|
804
824
|
| `Menu.RadioItem` | `Menu.RadioItem` with its indicator | One choice, with a check at the end when chosen |
|
|
@@ -821,12 +841,13 @@ interface Menu.PopupProps extends BaseMenu.Popup.Props {
|
|
|
821
841
|
interface Menu.ItemProps extends BaseMenu.Item.Props {
|
|
822
842
|
icon?: IconElement;
|
|
823
843
|
shortcut?: React.ReactNode; // at the end of the row, "⌘ D"
|
|
844
|
+
description?: React.ReactNode; // a line of ink-muted text under the label; the row grows to two lines
|
|
824
845
|
danger?: boolean; // destructive: red text
|
|
825
846
|
}
|
|
826
|
-
// CheckboxItem and RadioItem take icon and
|
|
847
|
+
// CheckboxItem and RadioItem take icon, shortcut and description too. SubmenuTrigger takes icon.
|
|
827
848
|
|
|
828
849
|
function menuClasses(options?: { size?: "sm" | "md" }): {
|
|
829
|
-
popup: string; item: string; dangerItem: string; label: string;
|
|
850
|
+
popup: string; item: string; dangerItem: string; label: string; text: string; description: string;
|
|
830
851
|
shortcut: string; separator: string; groupLabel: string;
|
|
831
852
|
};
|
|
832
853
|
```
|
|
@@ -837,16 +858,18 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on
|
|
|
837
858
|
|
|
838
859
|
- Act in `onClick`. Labels are verbs in sentence case.
|
|
839
860
|
- 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.
|
|
861
|
+
- 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`.
|
|
862
|
+
- `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
863
|
- 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
864
|
- 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
865
|
- 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).
|
|
866
|
+
- 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
867
|
|
|
846
868
|
## Style
|
|
847
869
|
|
|
848
870
|
- `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`.
|
|
871
|
+
- 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.
|
|
872
|
+
- 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
873
|
- The highlighted row uses `hover`. Disabled rows fade to 45%. `danger` rows use `danger`.
|
|
851
874
|
- A ghost trigger keeps its `hover` background while the menu is open.
|
|
852
875
|
- Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
|
|
@@ -890,6 +913,72 @@ Provide `href` and `children`.
|
|
|
890
913
|
|
|
891
914
|
- `accent` text that underlines on hover: the one place the hue appears at rest. `muted` uses `ink-muted`.
|
|
892
915
|
|
|
916
|
+
<!-- docs/components/form.md -->
|
|
917
|
+
|
|
918
|
+
# Form
|
|
919
|
+
|
|
920
|
+
A `<form>` with a title, its fields and its buttons. `bordered` boxes a small form among the [Lists](list.md) of a settings page.
|
|
921
|
+
|
|
922
|
+
## Example
|
|
923
|
+
|
|
924
|
+
```tsx
|
|
925
|
+
import { Button, Field, Form, Input, Switch } from "@usememos/ui";
|
|
926
|
+
|
|
927
|
+
<Form.Root bordered onSubmit={addRule}>
|
|
928
|
+
<Form.Title>Add a tag rule</Form.Title>
|
|
929
|
+
<Form.Description>Tag new notes that match a word.</Form.Description>
|
|
930
|
+
<Field.Root name="tag">
|
|
931
|
+
<Field.Label>Tag</Field.Label>
|
|
932
|
+
<Input required />
|
|
933
|
+
</Field.Root>
|
|
934
|
+
<Field.Root orientation="horizontal">
|
|
935
|
+
<Field.Label>Apply to existing notes</Field.Label>
|
|
936
|
+
<Switch name="existing" />
|
|
937
|
+
</Field.Root>
|
|
938
|
+
<Form.Footer>
|
|
939
|
+
<Button variant="ghost" onClick={cancel}>Cancel</Button>
|
|
940
|
+
<Button variant="primary" type="submit">Add rule</Button>
|
|
941
|
+
</Form.Footer>
|
|
942
|
+
</Form.Root>
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
## Parts
|
|
946
|
+
|
|
947
|
+
| Part | Base UI part | Renders |
|
|
948
|
+
| --- | --- | --- |
|
|
949
|
+
| `Form.Root` | `Form` | A `<form>`. Takes `bordered`. Named by its Title |
|
|
950
|
+
| `Form.Title` | none (ours) | An `<h2>` in `label` style. Takes `render` for another heading level |
|
|
951
|
+
| `Form.Description` | none (ours) | A `<p>` under the title |
|
|
952
|
+
| `Form.Footer` | none (ours) | A `<div>` that right-aligns the buttons |
|
|
953
|
+
|
|
954
|
+
## Props
|
|
955
|
+
|
|
956
|
+
```ts
|
|
957
|
+
interface Form.RootProps extends BaseForm.Props {
|
|
958
|
+
bordered?: boolean; // a List's edge around the form, and the Footer as a canvas bar
|
|
959
|
+
}
|
|
960
|
+
interface Form.TitleProps extends React.ComponentProps<"h2"> {
|
|
961
|
+
render?: React.ReactElement; // such as <h3 /> under a page section
|
|
962
|
+
}
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
Base UI's props pass through: `onFormSubmit`, `errors`, `validationMode` and `actionsRef`, as do the native `<form>` attributes (`onSubmit`, `action`).
|
|
966
|
+
|
|
967
|
+
## Rules
|
|
968
|
+
|
|
969
|
+
- Set `bordered` for a small form (a heading, two to four fields and its buttons) on a settings page, next to [Lists](list.md) or a `bordered` [Table](table.md), such as "Add a tag rule" under the rules it adds to. Leave it off for a form that is the page's main content.
|
|
970
|
+
- Never put a bordered Form inside a Dialog, Popover or Drawer: those are boxes already. A form in a Dialog uses the Dialog's own Title and Footer; see [Form in a dialog](../usage.md#form-in-a-dialog).
|
|
971
|
+
- Don't box single settings: a setting that saves at once is a horizontal [Field](field.md) in a List.
|
|
972
|
+
- Footer: a ghost Cancel, then one `primary` Button with `type="submit"` named with its verb ("Add rule", never "OK" or "Submit").
|
|
973
|
+
- Give each Field a `name`; errors from the server go in `errors`, keyed by name, and appear under their fields.
|
|
974
|
+
- Name it with a Title, or `aria-label` on the root when there is no visible heading.
|
|
975
|
+
|
|
976
|
+
## Style
|
|
977
|
+
|
|
978
|
+
- Children stack `space-4` apart. Title: `label` (14px, 500); Description: `caption` in `ink-muted`, `space-1` under the title. Footer: buttons `space-2` apart at the end.
|
|
979
|
+
- `bordered`: `surface` fill, a 1px `line` edge, `radius-lg`, `space-4` padding, no shadow, as a List. The Footer runs to the edges as a `canvas` bar under a `line`, padded `space-3` × `space-4`, rounded with the box at the bottom.
|
|
980
|
+
- The form is always md: controls inside it ignore the size of a parent Root.
|
|
981
|
+
|
|
893
982
|
<!-- docs/components/field.md -->
|
|
894
983
|
|
|
895
984
|
# Field and Fieldset
|
|
@@ -923,7 +1012,7 @@ import { Checkbox, Field, Fieldset, Input } from "@usememos/ui";
|
|
|
923
1012
|
|
|
924
1013
|
| Part | Base UI part | Renders |
|
|
925
1014
|
| --- | --- | --- |
|
|
926
|
-
| `Field.Root` | `Field.Root` | A `<div>` around one control. Takes `size` and passes it to the control |
|
|
1015
|
+
| `Field.Root` | `Field.Root` | A `<div>` around one control. Takes `size` and passes it to the control, and `orientation` |
|
|
927
1016
|
| `Field.Label` | `Field.Label` | A `<label>`: above a text control, or around a Checkbox, Switch or Radio |
|
|
928
1017
|
| `Field.Item` | `Field.Item` | A `<div>` around one option of a radio or checkbox group in one field |
|
|
929
1018
|
| `Field.Description` | `Field.Description` | A `<p>` of help under the control |
|
|
@@ -936,6 +1025,7 @@ import { Checkbox, Field, Fieldset, Input } from "@usememos/ui";
|
|
|
936
1025
|
```ts
|
|
937
1026
|
interface Field.RootProps extends BaseField.Root.Props {
|
|
938
1027
|
size?: "sm" | "md"; // passed to the control inside
|
|
1028
|
+
orientation?: "vertical" | "horizontal"; // default "vertical"; horizontal: label first, control at the end of the row
|
|
939
1029
|
}
|
|
940
1030
|
interface Fieldset.RootProps extends BaseFieldset.Root.Props {
|
|
941
1031
|
size?: "sm" | "md"; // passed to every control inside
|
|
@@ -949,13 +1039,23 @@ Base UI's props pass through: `name`, `invalid`, `disabled`, `validate`, `valida
|
|
|
949
1039
|
- Set `invalid` from the app when the server rejects a value; use `validate` for client checks.
|
|
950
1040
|
- Write the error as how to fix the problem.
|
|
951
1041
|
- Put a Checkbox, Switch or Radio inside its `Field.Label`, so clicking the text toggles it.
|
|
1042
|
+
- 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:
|
|
1043
|
+
|
|
1044
|
+
```tsx
|
|
1045
|
+
<Field.Root orientation="horizontal">
|
|
1046
|
+
<Field.Label>Compact mode</Field.Label>
|
|
1047
|
+
<Field.Description>Show more notes on one screen.</Field.Description>
|
|
1048
|
+
<Switch checked={compact} onCheckedChange={setCompact} />
|
|
1049
|
+
</Field.Root>
|
|
1050
|
+
```
|
|
952
1051
|
- 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,
|
|
1052
|
+
- 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
1053
|
- Never use a placeholder as the only label.
|
|
955
1054
|
|
|
956
1055
|
## Style
|
|
957
1056
|
|
|
958
1057
|
- Label: `label` style (14px, 500), `space-1` above the control; weight 400 when it wraps a Checkbox, Switch or Radio.
|
|
1058
|
+
- Horizontal: a two-column grid. The label (weight 400, as every settings row name is; see [Page](../patterns.md#page)) 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
1059
|
- Description and error: `caption` (13px), below the control. The error hides the description and turns the control's edge `danger`.
|
|
960
1060
|
|
|
961
1061
|
<!-- docs/components/input.md -->
|
|
@@ -999,7 +1099,7 @@ Wraps Base UI's [Input](https://base-ui.com/react/components/input), so inside a
|
|
|
999
1099
|
## Style
|
|
1000
1100
|
|
|
1001
1101
|
- Flat `surface` with a 1px `edge` and a faint inner shadow from the top. Never a gray well, so it never reads as disabled.
|
|
1002
|
-
- Typed text weight 400; placeholder `ink-muted`.
|
|
1102
|
+
- Typed text weight 400; placeholder `ink-muted`. On touch screens typed text is 16px at every size, so iOS Safari does not zoom in on focus; the field keeps its height.
|
|
1003
1103
|
- Hover darkens the edge to `ink-muted`; focus turns it into a 1.5px `focus-edge` (ink), on click and keyboard alike. There is no ring around the field. The background never changes.
|
|
1004
1104
|
- The side with a prefix or suffix has 2px less padding.
|
|
1005
1105
|
|
|
@@ -1042,6 +1142,7 @@ Base UI has no textarea part, so this renders Base UI's `Field.Control` as a `<t
|
|
|
1042
1142
|
## Style
|
|
1043
1143
|
|
|
1044
1144
|
- Looks like [Input](input.md). Resizes vertically only.
|
|
1145
|
+
- On touch screens typed text is 16px on a 20px line at every size, like Input, so iOS Safari does not zoom in on focus.
|
|
1045
1146
|
|
|
1046
1147
|
<!-- docs/components/select.md -->
|
|
1047
1148
|
|
|
@@ -1076,7 +1177,7 @@ const visibility = { private: "Private", workspace: "Workspace", public: "Public
|
|
|
1076
1177
|
| Part | Base UI part | Renders |
|
|
1077
1178
|
| --- | --- | --- |
|
|
1078
1179
|
| `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"
|
|
1180
|
+
| `Select.Trigger` | `Select.Trigger` with `Select.Icon` | A neutral key with a chevron at the end, a `<button role="combobox">`. Takes `variant` |
|
|
1080
1181
|
| `Select.Value` | `Select.Value` | The chosen item's label, or the placeholder |
|
|
1081
1182
|
| `Select.Popup` | `Select.Portal`, `Positioner`, `Popup`, `List`, scroll arrows | The list, portaled to the body |
|
|
1082
1183
|
| `Select.Item` | `Select.Item` with `ItemText` and `ItemIndicator` | One option, with a check at the end when chosen. Takes `icon` |
|
|
@@ -1090,6 +1191,9 @@ const visibility = { private: "Private", workspace: "Workspace", public: "Public
|
|
|
1090
1191
|
type Select.RootProps<Value, Multiple> = BaseSelect.Root.Props<Value, Multiple> & {
|
|
1091
1192
|
size?: "sm" | "md";
|
|
1092
1193
|
};
|
|
1194
|
+
interface Select.TriggerProps extends BaseSelect.Trigger.Props {
|
|
1195
|
+
variant?: "neutral" | "ghost"; // default "neutral"; ghost: a flat row, for a switcher in a sidebar or toolbar
|
|
1196
|
+
}
|
|
1093
1197
|
interface Select.PopupProps extends BaseSelect.Popup.Props {
|
|
1094
1198
|
alignItemWithTrigger?: boolean; // default true: the list opens over the trigger with the chosen item on the value
|
|
1095
1199
|
side?: Side; align?: Align; // used when alignItemWithTrigger is false
|
|
@@ -1108,10 +1212,11 @@ Base UI's props pass through: `items`, `value`, `defaultValue`, `onValueChange`,
|
|
|
1108
1212
|
- Label it with a [Field](field.md), or `aria-label` on the trigger.
|
|
1109
1213
|
- 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
1214
|
- For actions, use [Menu](menu.md).
|
|
1215
|
+
- `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. In a sidebar with `mu-compact` it is 28px tall, like the rows around it. Put an icon or Avatar before `Select.Value`. In a form, keep `neutral`.
|
|
1111
1216
|
|
|
1112
1217
|
## Style
|
|
1113
1218
|
|
|
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.
|
|
1219
|
+
- 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
1220
|
- 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
1221
|
- Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
|
|
1117
1222
|
|
|
@@ -1157,6 +1262,7 @@ const tags = ["book", "design", "travel"];
|
|
|
1157
1262
|
| `Combobox.Group` | `Combobox.Group` | A labeled set of items |
|
|
1158
1263
|
| `Combobox.GroupLabel` | `Combobox.GroupLabel` | The name of a Group |
|
|
1159
1264
|
| `Combobox.Separator` | `Combobox.Separator` | A line between groups |
|
|
1265
|
+
| `Combobox.Row` | `Combobox.Row` | One row of a grid, with `grid` on the root: square cells for icons or emoji |
|
|
1160
1266
|
| `Combobox.Collection` | `Combobox.Collection` | Base UI's part, unstyled: renders one group's items from grouped `items` |
|
|
1161
1267
|
| `Combobox.useFilter` | `Combobox.useFilter` | Base UI's hook: locale-aware `contains`, `startsWith`, `endsWith` for `filter` |
|
|
1162
1268
|
|
|
@@ -1191,6 +1297,26 @@ Base UI's props pass through: `items`, `value`, `defaultValue`, `onValueChange`,
|
|
|
1191
1297
|
- Use it for more than about 10 options, or when people type to find one. For fewer, use [Select](select.md).
|
|
1192
1298
|
- Typing opens the list. Up and Down move the highlight, Enter picks, Escape closes; focus stays in the input.
|
|
1193
1299
|
- Multi-select (chips) is not built.
|
|
1300
|
+
- 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.
|
|
1301
|
+
- 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:
|
|
1302
|
+
|
|
1303
|
+
```tsx
|
|
1304
|
+
<Combobox.Root items={icons} inline grid value={icon} onValueChange={pick} inputValue={query} onInputValueChange={setQuery} itemToStringLabel={(item) => item.label}>
|
|
1305
|
+
<Combobox.Input placeholder="Find an icon" aria-label="Find an icon" />
|
|
1306
|
+
<Combobox.Empty>No icons found</Combobox.Empty>
|
|
1307
|
+
<Combobox.List aria-label="Icons">
|
|
1308
|
+
{rows(icons.filter((item) => contains(item.label, query)), 8).map((row, index) => (
|
|
1309
|
+
<Combobox.Row key={index}>
|
|
1310
|
+
{row.map((item) => (
|
|
1311
|
+
<Combobox.Item key={item.value} value={item} aria-label={item.label}>{item.icon}</Combobox.Item>
|
|
1312
|
+
))}
|
|
1313
|
+
</Combobox.Row>
|
|
1314
|
+
))}
|
|
1315
|
+
</Combobox.List>
|
|
1316
|
+
</Combobox.Root>
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
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
1320
|
|
|
1195
1321
|
## Style
|
|
1196
1322
|
|
|
@@ -1198,6 +1324,7 @@ Base UI's props pass through: `items`, `value`, `defaultValue`, `onValueChange`,
|
|
|
1198
1324
|
- 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
1325
|
- Rows are a control height, text weight 400; the highlighted row uses `hover`; the chosen row has a check at the end.
|
|
1200
1326
|
- Empty text: `body` in `ink-muted`, `space-3` by `space-4` padding.
|
|
1327
|
+
- 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
1328
|
- Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
|
|
1202
1329
|
|
|
1203
1330
|
<!-- docs/components/checkbox.md -->
|
|
@@ -1497,10 +1624,12 @@ Dialog and AlertDialog have the same parts.
|
|
|
1497
1624
|
```ts
|
|
1498
1625
|
interface Dialog.PopupProps extends BaseDialog.Popup.Props {
|
|
1499
1626
|
sheet?: boolean; // below breakpoint-sm, a sheet from the bottom; default true
|
|
1627
|
+
variant?: "panel" | "bare"; // default "panel"; bare: no padding, gap or X (Dialog only)
|
|
1628
|
+
fullScreen?: boolean; // covers the whole screen with square corners, at every width (Dialog only)
|
|
1500
1629
|
closeLabel?: string; // accessible name of the X; default "Close"
|
|
1501
1630
|
container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null; // portal target
|
|
1502
1631
|
}
|
|
1503
|
-
// AlertDialog.Popup takes sheet and container; it has no X.
|
|
1632
|
+
// AlertDialog.Popup takes sheet and container; it has no X, no variant and no fullScreen.
|
|
1504
1633
|
```
|
|
1505
1634
|
|
|
1506
1635
|
Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `disablePointerDismissal` on the root; `initialFocus`, `finalFocus` on the popup.
|
|
@@ -1512,6 +1641,9 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `d
|
|
|
1512
1641
|
- Use AlertDialog for confirmations; see the [confirmation pattern](../patterns.md#confirmation). Open it from a Menu item with `open` and `onOpenChange`.
|
|
1513
1642
|
- Escape and a backdrop click close a Dialog. While a form has unsaved input, keep it open from `onOpenChange`.
|
|
1514
1643
|
- Focus moves into the dialog and returns to the trigger on close.
|
|
1644
|
+
- `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.
|
|
1645
|
+
- Every Dialog has the same backdrop, `overlay`, including a search palette. There is no prop to lighten or remove it.
|
|
1646
|
+
- `fullScreen` is for content that needs the whole screen, such as an image viewer (`variant="bare" fullScreen`). It replaces the sheet on phones. The content handles the safe-area insets. Don't add `fixed inset-0` or `rounded-none` yourself.
|
|
1515
1647
|
- 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
1648
|
|
|
1517
1649
|
## Style
|
|
@@ -1520,6 +1652,72 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `d
|
|
|
1520
1652
|
- 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
1653
|
- 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
1654
|
- Long content scrolls the viewport around the panel.
|
|
1655
|
+
- `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`.
|
|
1656
|
+
- `fullScreen`: the viewport's padding goes, and the popup fills it with square corners.
|
|
1657
|
+
|
|
1658
|
+
<!-- docs/components/drawer.md -->
|
|
1659
|
+
|
|
1660
|
+
# Drawer
|
|
1661
|
+
|
|
1662
|
+
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.
|
|
1663
|
+
|
|
1664
|
+
## Example
|
|
1665
|
+
|
|
1666
|
+
```tsx
|
|
1667
|
+
import { Button, Drawer } from "@usememos/ui";
|
|
1668
|
+
import { Menu } from "lucide-react";
|
|
1669
|
+
|
|
1670
|
+
<Drawer.Root side="start" open={open} onOpenChange={setOpen}>
|
|
1671
|
+
<Drawer.Trigger render={<Button variant="ghost" icon={<Menu />} aria-label="Open navigation" />} />
|
|
1672
|
+
<Drawer.Popup variant="bare">
|
|
1673
|
+
<Drawer.Title className="mu-visually-hidden">Navigation</Drawer.Title>
|
|
1674
|
+
<AppSidebar />
|
|
1675
|
+
</Drawer.Popup>
|
|
1676
|
+
</Drawer.Root>
|
|
1677
|
+
```
|
|
1678
|
+
|
|
1679
|
+
## Parts
|
|
1680
|
+
|
|
1681
|
+
| Part | Base UI part | Renders |
|
|
1682
|
+
| --- | --- | --- |
|
|
1683
|
+
| `Drawer.Root` | `Drawer.Root` | Nothing. Holds the open state. Takes `side` |
|
|
1684
|
+
| `Drawer.Trigger` | `Drawer.Trigger` | The control that opens it, usually a Button through `render` |
|
|
1685
|
+
| `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"` |
|
|
1686
|
+
| `Drawer.Title` | `Drawer.Title` | An `<h2>` that names the drawer |
|
|
1687
|
+
| `Drawer.Description` | `Drawer.Description` | A `<p>` under the title |
|
|
1688
|
+
| `Drawer.Close` | `Drawer.Close` | A control that closes it, such as each link of a navigation drawer |
|
|
1689
|
+
|
|
1690
|
+
## Props
|
|
1691
|
+
|
|
1692
|
+
```ts
|
|
1693
|
+
interface Drawer.RootProps extends BaseDrawer.Root.Props {
|
|
1694
|
+
side?: "start" | "end" | "bottom"; // the edge it slides from and swipes back to; default "end". start and end follow the text direction
|
|
1695
|
+
}
|
|
1696
|
+
interface Drawer.PopupProps extends BaseDrawer.Popup.Props {
|
|
1697
|
+
variant?: "panel" | "bare"; // default "panel"; bare: no padding, gap or X
|
|
1698
|
+
closeLabel?: string; // accessible name of the X, or of bare's screen-reader Close; default "Close"
|
|
1699
|
+
container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null; // portal target
|
|
1700
|
+
}
|
|
1701
|
+
```
|
|
1702
|
+
|
|
1703
|
+
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.
|
|
1704
|
+
|
|
1705
|
+
## Rules
|
|
1706
|
+
|
|
1707
|
+
- 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.
|
|
1708
|
+
- Name it: a `Drawer.Title`, hidden with `mu-visually-hidden` when the content names itself, or `aria-label` on the popup.
|
|
1709
|
+
- `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`. From the start or end, its content is exactly the drawer's height: give the app's sidebar `height: 100%` and let a region inside it scroll (`flex: 1; min-height: 0; overflow-y: auto`). Don't set `100dvh` or add a Close or an edge yourself: bare has a Close for screen readers (out of the tab order; keyboards use Escape) and the drawer draws its own edge.
|
|
1710
|
+
- Close it when people pick a destination: wrap each link in `Drawer.Close render={<a … />}`, or close it from the router.
|
|
1711
|
+
- Escape, a backdrop click and a swipe toward its edge close it. Focus moves in and returns to the trigger.
|
|
1712
|
+
- The panel is always md, as a Dialog is.
|
|
1713
|
+
|
|
1714
|
+
## Style
|
|
1715
|
+
|
|
1716
|
+
- `surface` with `shadow-pop`, full height and `drawer-width` (320px) wide from the start or end edge, with a 1px `line` edge on its inner side (the side facing the page), as a fixed sidebar has, 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`.
|
|
1717
|
+
- `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`.
|
|
1718
|
+
- `bare`: no padding, gap or X. A visually hidden `Drawer.Close` named `closeLabel` follows the content, so screen readers on touch screens can close it.
|
|
1719
|
+
- The backdrop is `overlay`; it fades as the drawer is swiped away.
|
|
1720
|
+
- Slides in from its edge over `duration-settle` with `ease-settle` and follows the finger while swiped. Under reduced motion it fades instead.
|
|
1523
1721
|
|
|
1524
1722
|
<!-- docs/components/popover.md -->
|
|
1525
1723
|
|
|
@@ -1559,9 +1757,10 @@ import { Button, Input, Popover } from "@usememos/ui";
|
|
|
1559
1757
|
interface Popover.PopupProps extends BasePopover.Popup.Props {
|
|
1560
1758
|
side?: Side; align?: Align; // Positioner props; default side "bottom"
|
|
1561
1759
|
container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null; // portal target
|
|
1760
|
+
variant?: "panel" | "list"; // default "panel"; list: a Menu's padding and no gap, for rows
|
|
1562
1761
|
}
|
|
1563
1762
|
|
|
1564
|
-
function popoverClasses(): { popup: string; title: string; description: string };
|
|
1763
|
+
function popoverClasses(options?: { variant?: "panel" | "list" }): { popup: string; title: string; description: string };
|
|
1565
1764
|
```
|
|
1566
1765
|
|
|
1567
1766
|
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 +1769,7 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on
|
|
|
1570
1769
|
|
|
1571
1770
|
- 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
1771
|
- Give it a `Popover.Title`, or `aria-label` on the popup.
|
|
1772
|
+
- `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. For 28px rows beside a sidebar with `mu-compact`, put `mu-compact` on the popup too: `className="mu-compact"`.
|
|
1573
1773
|
- 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
1774
|
- It closes on Escape and on a click outside; focus returns to the trigger.
|
|
1575
1775
|
- An app's own floating panel, such as an emoji picker, takes `popoverClasses()`. See [Class functions](../custom-components.md#class-functions).
|
|
@@ -1578,6 +1778,7 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on
|
|
|
1578
1778
|
|
|
1579
1779
|
- `mu-popup` (`surface`, `shadow-pop`, `radius-xl`), `space-4` padding, `popover-width` wide. Children stack `space-3` apart.
|
|
1580
1780
|
- Title: `label`. Description: `caption` in `ink-muted`, `space-1` under the title.
|
|
1781
|
+
- `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
1782
|
- Fades in and rises `space-1` over `duration-fade`; no rise under reduced motion.
|
|
1582
1783
|
|
|
1583
1784
|
<!-- docs/components/tooltip.md -->
|
|
@@ -1693,9 +1894,9 @@ Base UI's props pass through: `timeout` (default 5000ms) and `limit` (default 3)
|
|
|
1693
1894
|
|
|
1694
1895
|
## Style
|
|
1695
1896
|
|
|
1696
|
-
- `mu-popup`, `toast-width` wide,
|
|
1897
|
+
- `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
1898
|
- 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`
|
|
1899
|
+
- 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
1900
|
|
|
1700
1901
|
<!-- docs/components/avatar.md -->
|
|
1701
1902
|
|
|
@@ -1756,31 +1957,32 @@ import { Hash, Lock } from "lucide-react";
|
|
|
1756
1957
|
<Badge icon={<Hash />}>reading</Badge>
|
|
1757
1958
|
<Badge tone="outline" icon={<Lock />}>Private</Badge>
|
|
1758
1959
|
<Badge tone="success">Synced</Badge>
|
|
1960
|
+
<Badge tone="primary">New</Badge>
|
|
1759
1961
|
```
|
|
1760
1962
|
|
|
1761
1963
|
## Props
|
|
1762
1964
|
|
|
1763
1965
|
```ts
|
|
1764
1966
|
interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
1765
|
-
tone?: Tone | "outline"; // default "neutral"
|
|
1967
|
+
tone?: Tone | "outline" | "primary"; // default "neutral"
|
|
1766
1968
|
icon?: IconElement;
|
|
1767
1969
|
children: React.ReactNode; // one or two words
|
|
1768
1970
|
render?: React.ReactElement;
|
|
1769
1971
|
}
|
|
1770
1972
|
|
|
1771
|
-
function badgeClasses(options?: { tone?: Tone | "outline" }): string;
|
|
1973
|
+
function badgeClasses(options?: { tone?: Tone | "outline" | "primary" }): string;
|
|
1772
1974
|
```
|
|
1773
1975
|
|
|
1774
1976
|
## Rules
|
|
1775
1977
|
|
|
1776
|
-
- Tags: `neutral` with a `Hash` icon. Visibility: `outline` with its icon. Status: `success`, `warning` or `danger`, always with a word, never color alone.
|
|
1978
|
+
- Tags: `neutral` with a `Hash` icon. Visibility: `outline` with its icon. Status: `success`, `warning` or `danger`, always with a word, never color alone. A label that should stand out, such as New on a feature: `primary`, in ink, the system's one emphasis. Use it for one badge in a view at most, and never for a status.
|
|
1777
1979
|
- A badge is not a button. A clickable tag filter is a [Toggle](toggle.md).
|
|
1778
1980
|
- An app's own element that should look like a badge, such as a tag that is also a link, takes `badgeClasses()`. See [Class functions](../custom-components.md#class-functions).
|
|
1779
1981
|
|
|
1780
1982
|
## Style
|
|
1781
1983
|
|
|
1782
1984
|
- `control-xs` (20px) tall, `radius-sm` (never a pill), `label-xs` text (12px, 500), optional `icon-xs` (12px) icon at a 1.25px stroke. It lines up with xs controls.
|
|
1783
|
-
- `neutral`: `ink` on `well`. Status tones: their `-soft` background with full-strength text.
|
|
1985
|
+
- `neutral`: `ink` on `well`. `primary`: `on-primary` on `primary`, the colors of a primary Button. Status tones: their `-soft` background with full-strength text.
|
|
1784
1986
|
|
|
1785
1987
|
<!-- docs/components/kbd.md -->
|
|
1786
1988
|
|
|
@@ -1970,19 +2172,21 @@ No Base UI part: these are styled native table elements.
|
|
|
1970
2172
|
|
|
1971
2173
|
| Part | Renders |
|
|
1972
2174
|
| --- | --- |
|
|
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>` |
|
|
2175
|
+
| `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
2176
|
| `Table.Caption` | `<caption>`, under the table. Names it for screen readers |
|
|
1975
2177
|
| `Table.Header` | `<thead>` |
|
|
1976
2178
|
| `Table.Body` | `<tbody>` |
|
|
1977
2179
|
| `Table.Row` | `<tr>` |
|
|
1978
2180
|
| `Table.Head` | `<th scope="col">`. Takes `num` |
|
|
1979
2181
|
| `Table.Cell` | `<td>`. Takes `num` and `muted` |
|
|
2182
|
+
| `Table.Actions` | A `<div>` that lays two or more row actions side by side in one Cell |
|
|
1980
2183
|
|
|
1981
2184
|
## Props
|
|
1982
2185
|
|
|
1983
2186
|
```ts
|
|
1984
2187
|
interface Table.RootProps extends React.ComponentProps<"table"> {
|
|
1985
|
-
size?: "sm" | "md";
|
|
2188
|
+
size?: "sm" | "md"; // sm for dense admin lists
|
|
2189
|
+
bordered?: boolean; // a line edge and rounded corners around the table, for a table that sits among other sections
|
|
1986
2190
|
}
|
|
1987
2191
|
interface Table.HeadProps extends React.ComponentProps<"th"> {
|
|
1988
2192
|
num?: boolean; // right-aligned, for a column of numbers
|
|
@@ -2000,16 +2204,106 @@ Every native attribute passes through.
|
|
|
2000
2204
|
- Use it for data with columns. Lists of content (notes, comments) are [entries](../patterns.md#entry), not rows.
|
|
2001
2205
|
- Set `num` on a numeric column's Head and on its Cells.
|
|
2002
2206
|
- 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`.
|
|
2207
|
+
- Two or three frequent actions, such as Edit and Delete, go in a `Table.Actions` inside that Cell, as ghost Buttons. Put a single Button straight in the Cell:
|
|
2208
|
+
|
|
2209
|
+
```tsx
|
|
2210
|
+
<Table.Cell num>
|
|
2211
|
+
<Table.Actions>
|
|
2212
|
+
<Button variant="ghost" size="sm" icon={<Pencil />} aria-label={`Edit ${hook.name}`} />
|
|
2213
|
+
<Button variant="ghost" size="sm" icon={<Trash2 />} aria-label={`Delete ${hook.name}`} />
|
|
2214
|
+
</Table.Actions>
|
|
2215
|
+
</Table.Cell>
|
|
2216
|
+
```
|
|
2003
2217
|
- Name the table with a Caption, or `aria-label` on the root.
|
|
2004
2218
|
- Sort, select and paginate in the app. The table has no data props.
|
|
2219
|
+
- 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
2220
|
|
|
2006
2221
|
## Style
|
|
2007
2222
|
|
|
2008
|
-
- No
|
|
2223
|
+
- No stripes: a `line` under the head row and between body rows. Body rows highlight with `hover`.
|
|
2224
|
+
- `bordered`: `surface` fill, a 1px `line` edge and `radius-lg` on the scrolling wrapper; the head row has a `canvas` fill, so it reads as the head inside the box; 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
2225
|
- Heads: `label-sm` (13px, 500) in `ink-muted`, a control height tall.
|
|
2010
|
-
- 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.
|
|
2226
|
+
- Cells: `caption` at sm and `body` at md, with tabular numbers. Rows are 34 or 40px; a Button in a cell, or a `Table.Actions` group of them (`space-1` apart), overlaps the padding, so rows keep that height.
|
|
2011
2227
|
- Cells pad `px` at the sides. Caption: `caption` in `ink-muted`, `space-2` above.
|
|
2012
2228
|
|
|
2229
|
+
<!-- docs/components/list.md -->
|
|
2230
|
+
|
|
2231
|
+
# List
|
|
2232
|
+
|
|
2233
|
+
A boxed list of rows: settings, or records such as webhooks, sessions or linked accounts.
|
|
2234
|
+
|
|
2235
|
+
## Example
|
|
2236
|
+
|
|
2237
|
+
```tsx
|
|
2238
|
+
import { Field, List, Switch } from "@usememos/ui";
|
|
2239
|
+
|
|
2240
|
+
<h2 className="mu-label" id="preferences">Preferences</h2>
|
|
2241
|
+
<List.Root aria-labelledby="preferences">
|
|
2242
|
+
<List.Item>
|
|
2243
|
+
<Field.Root orientation="horizontal">
|
|
2244
|
+
<Field.Label>Compact mode</Field.Label>
|
|
2245
|
+
<Field.Description>Show more notes on one screen.</Field.Description>
|
|
2246
|
+
<Switch checked={compact} onCheckedChange={saveCompact} />
|
|
2247
|
+
</Field.Root>
|
|
2248
|
+
</List.Item>
|
|
2249
|
+
<List.Item>
|
|
2250
|
+
<Field.Root orientation="horizontal">
|
|
2251
|
+
<Field.Label>Show pinned first</Field.Label>
|
|
2252
|
+
<Switch checked={pinnedFirst} onCheckedChange={savePinnedFirst} />
|
|
2253
|
+
</Field.Root>
|
|
2254
|
+
</List.Item>
|
|
2255
|
+
</List.Root>
|
|
2256
|
+
```
|
|
2257
|
+
|
|
2258
|
+
## Parts
|
|
2259
|
+
|
|
2260
|
+
No Base UI part: these are styled native list elements.
|
|
2261
|
+
|
|
2262
|
+
| Part | Renders |
|
|
2263
|
+
| --- | --- |
|
|
2264
|
+
| `List.Root` | A `<ul>` with a `line` edge and rounded corners. Takes `size` |
|
|
2265
|
+
| `List.Item` | An `<li>`: one row, divided from the next by a `line` |
|
|
2266
|
+
| `List.ItemButton` | A `<button>` that fills its Item, for a row that acts as a whole. Takes `render` for a link |
|
|
2267
|
+
|
|
2268
|
+
## Props
|
|
2269
|
+
|
|
2270
|
+
```ts
|
|
2271
|
+
interface List.RootProps extends React.ComponentProps<"ul"> {
|
|
2272
|
+
size?: "sm" | "md"; // sm for dense lists; also the size of the controls inside
|
|
2273
|
+
}
|
|
2274
|
+
```
|
|
2275
|
+
|
|
2276
|
+
Every native attribute passes through.
|
|
2277
|
+
|
|
2278
|
+
## Rules
|
|
2279
|
+
|
|
2280
|
+
- 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.
|
|
2281
|
+
- 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).
|
|
2282
|
+
- Name the list: `aria-labelledby` pointing at the section title above it, or `aria-label`.
|
|
2283
|
+
- 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.
|
|
2284
|
+
- When the whole row opens something (a space, a record's page), put one `List.ItemButton` in the Item and the row's content inside it. For a link, `render={<a href="…" />}` or the router's link. Don't give a button negative margins to fill the row yourself.
|
|
2285
|
+
- A row with an ItemButton has no other controls: buttons don't nest. A row with its own actions (a Menu trigger, a Switch) keeps them in the Item and opens the record from a Link in its text.
|
|
2286
|
+
|
|
2287
|
+
```tsx
|
|
2288
|
+
<List.Root aria-label="Spaces">
|
|
2289
|
+
{spaces.map((space) => (
|
|
2290
|
+
<List.Item key={space.id}>
|
|
2291
|
+
<List.ItemButton render={<a href={`/s/${space.id}`} />}>
|
|
2292
|
+
<Avatar.Root size="sm">…</Avatar.Root>
|
|
2293
|
+
{space.name}
|
|
2294
|
+
</List.ItemButton>
|
|
2295
|
+
</List.Item>
|
|
2296
|
+
))}
|
|
2297
|
+
</List.Root>
|
|
2298
|
+
```
|
|
2299
|
+
- An empty list shows the [empty state](../patterns.md#empty-state) in place of the List.
|
|
2300
|
+
|
|
2301
|
+
## Style
|
|
2302
|
+
|
|
2303
|
+
- `surface` fill, a 1px `line` edge, `radius-lg`. No shadow: it is flat, like a field.
|
|
2304
|
+
- Rows pad `space-3` × `space-4` at md and `space-2` × `space-3` at sm, divided by a 1px `line`. No hover on a plain row. An ItemButton fills its row, padded as the row is, lays out its children in a row `space-3` apart, highlights with `hover`, and rounds with the box at the first and last row; keyboard focus shows the ink ring.
|
|
2305
|
+
- Text is `body` in `ink`.
|
|
2306
|
+
|
|
2013
2307
|
<!-- docs/components/prose.md -->
|
|
2014
2308
|
|
|
2015
2309
|
# Prose
|
|
@@ -2051,8 +2345,8 @@ Every native attribute passes through.
|
|
|
2051
2345
|
|
|
2052
2346
|
## Style
|
|
2053
2347
|
|
|
2054
|
-
- Text: 16px on a 24px line (`sm`: 14 on 22), weight 400, `ink`. Blocks are `space-3` apart.
|
|
2055
|
-
- Headings: `h1` 20/28px, `
|
|
2348
|
+
- Text: 16px on a 24px line (`sm`: 14 on 22), weight 400, `ink`. Paragraphs and list items use `text-wrap: pretty`, so a last line is rarely one word. Blocks are `space-3` apart.
|
|
2349
|
+
- Headings: `h1` 24/32px, `h2` 20/28px, `h3`–`h6` 16/24px (`sm`: `h1` 16/24px, `h2`–`h6` 14/22px); all weight 600, balanced over their lines (`text-wrap: balance`), with `space-5` above and `space-2` below. Bold (`strong`, `b`) is 600 too. These are the only 600s in the system: people wrote them as bold, and 500 does not read as bold in system fonts.
|
|
2056
2350
|
- Links: `accent`, underlined at rest, because color alone does not set them apart from the text.
|
|
2057
2351
|
- Code: `--mu-font-mono` at 0.875em on `well`. Inline code has `radius-xs`; blocks have `radius-md`, `space-3` by `space-4` padding, and scroll sideways.
|
|
2058
2352
|
- Quotes: a 2px `edge` bar, text in `ink-muted`. Rules: a 1px `line` with `space-5` around it.
|
|
@@ -2060,7 +2354,7 @@ Every native attribute passes through.
|
|
|
2060
2354
|
- Images and video: never wider than the column, `radius-lg`.
|
|
2061
2355
|
- Tables follow [Table](table.md): `line` between rows, 13px `ink-muted` heads (weight 500), tabular numbers; they scroll sideways when too wide.
|
|
2062
2356
|
- Tags: `ink-muted` on `well`, `radius-xs`. They read as words, not badges.
|
|
2063
|
-
- Sizes are custom properties set on `.mu-prose` in the foundation (`--prose-fs`, `--prose-lh`,
|
|
2357
|
+
- Sizes are custom properties set on `.mu-prose` in the foundation (`--prose-fs`, `--prose-lh`, `--prose-h1`, `--prose-h2`, `--prose-h3` and their line heights).
|
|
2064
2358
|
|
|
2065
2359
|
<!-- docs/usage.md -->
|
|
2066
2360
|
|
|
@@ -2088,6 +2382,7 @@ The most common mistake is reaching for the wrong overlay. Decide by what the fl
|
|
|
2088
2382
|
| [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
2383
|
| [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
2384
|
| [AlertDialog](components/dialog.md) | A question and two buttons | An action | Only its buttons | Yes | Confirming a destructive or irreversible action |
|
|
2385
|
+
| [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
2386
|
| [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
2387
|
|
|
2093
2388
|
### Menu or Popover
|
|
@@ -2165,7 +2460,8 @@ Answer in order; the first yes decides.
|
|
|
2165
2460
|
| Details or advanced settings people rarely need | [Collapsible](components/collapsible.md) | A Dialog |
|
|
2166
2461
|
| Several sections, one open at a time | Collapsibles, controlled together | A third-party accordion |
|
|
2167
2462
|
| 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
|
|
2463
|
+
| 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 |
|
|
2464
|
+
| Settings rows, or a few records with an action each | [List](components/list.md) | Separate boxes for each row |
|
|
2169
2465
|
| A list of content (notes, comments) | [Entries](patterns.md#entry) | A Table |
|
|
2170
2466
|
|
|
2171
2467
|
## Picking feedback
|
|
@@ -2282,17 +2578,21 @@ const [confirming, setConfirming] = useState(false);
|
|
|
2282
2578
|
### Settings that save at once
|
|
2283
2579
|
|
|
2284
2580
|
```tsx
|
|
2285
|
-
<
|
|
2286
|
-
|
|
2287
|
-
<
|
|
2288
|
-
<Field.
|
|
2289
|
-
<
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
</
|
|
2581
|
+
<h2 className="mu-label" id="notifications">Notifications</h2>
|
|
2582
|
+
<List.Root aria-labelledby="notifications">
|
|
2583
|
+
<List.Item>
|
|
2584
|
+
<Field.Root orientation="horizontal">
|
|
2585
|
+
<Field.Label>Email me when I'm mentioned</Field.Label>
|
|
2586
|
+
<Field.Description>Sent at most once an hour.</Field.Description>
|
|
2587
|
+
<Switch checked={mentions} onCheckedChange={saveMentions} />
|
|
2588
|
+
</Field.Root>
|
|
2589
|
+
</List.Item>
|
|
2590
|
+
<List.Item>…</List.Item>
|
|
2591
|
+
</List.Root>
|
|
2294
2592
|
```
|
|
2295
2593
|
|
|
2594
|
+
A horizontal Field puts the setting's name first, at weight 400, and the Switch or Select at the end of the row. The section title above it is the `label` (500). 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.
|
|
2595
|
+
|
|
2296
2596
|
No Save button and no toast: the Switch shows the result. If saving fails, move the Switch back and show a danger Toast.
|
|
2297
2597
|
|
|
2298
2598
|
### A toolbar
|
|
@@ -2319,6 +2619,97 @@ One `Tooltip.Provider` around the toolbar (or the app) so moving along it opens
|
|
|
2319
2619
|
- Show one or two frequent actions as xs or sm ghost Buttons beside it, each with a Tooltip.
|
|
2320
2620
|
- On an entry the trigger appears on hover; on touch screens it is always visible (see [Entry](patterns.md#entry)).
|
|
2321
2621
|
|
|
2622
|
+
### A sidebar navigation row
|
|
2623
|
+
|
|
2624
|
+
The package ships no navigation component: the sidebar's rows are the app's links. For 28px rows, put `mu-compact` on the sidebar: every md control and row inside it is `control-compact` (28px) tall instead of 32px, with md text, icons, padding and radius. The app's own rows read the row variables, so they match the package's controls beside them:
|
|
2625
|
+
|
|
2626
|
+
```tsx
|
|
2627
|
+
<aside className="app-sidebar mu-compact">
|
|
2628
|
+
<Select.Root value={space} onValueChange={setSpace}>
|
|
2629
|
+
<Select.Trigger variant="ghost">…</Select.Trigger>
|
|
2630
|
+
…
|
|
2631
|
+
</Select.Root>
|
|
2632
|
+
<Button variant="primary" icon={<Plus />}>New note</Button>
|
|
2633
|
+
<nav className="app-nav mu-s-md mu-base" aria-label="Main">
|
|
2634
|
+
<a className="app-nav-row mu-focus" href="/" aria-current={path === "/" ? "page" : undefined}>
|
|
2635
|
+
<House className="mu-icon" aria-hidden />
|
|
2636
|
+
Home
|
|
2637
|
+
</a>
|
|
2638
|
+
…
|
|
2639
|
+
</nav>
|
|
2640
|
+
</aside>
|
|
2641
|
+
```
|
|
2642
|
+
|
|
2643
|
+
```css
|
|
2644
|
+
.app-nav { display: grid; gap: var(--mu-inset); }
|
|
2645
|
+
.app-nav-row {
|
|
2646
|
+
display: flex;
|
|
2647
|
+
align-items: center;
|
|
2648
|
+
gap: var(--mu-row-gap);
|
|
2649
|
+
height: var(--mu-row-height); /* 28px inside mu-compact, 32px without */
|
|
2650
|
+
padding: 0 var(--mu-row-padding-icon);
|
|
2651
|
+
border-radius: var(--mu-row-radius);
|
|
2652
|
+
color: var(--mu-ink-muted);
|
|
2653
|
+
text-decoration: none;
|
|
2654
|
+
}
|
|
2655
|
+
.app-nav-row:hover, .app-nav-row[aria-current="page"] { color: var(--mu-ink); background-color: var(--mu-hover); }
|
|
2656
|
+
```
|
|
2657
|
+
|
|
2658
|
+
- `mu-compact` changes only md. sm (24px) and xs (20px) controls inside keep their heights, so an xs ghost Button for a row action still fits.
|
|
2659
|
+
- Popups opened from the sidebar (the Select's list, a Menu) mount outside it, at the normal 32px. To give a panel of rows the same 28px, put `mu-compact` on its popup: `<Popover.Popup variant="list" className="mu-compact">`.
|
|
2660
|
+
- `mu-s-md` gives the rows 14px text, 16px icons with a 1.5px stroke, and md padding and radius; `mu-base` sets the font and weight 500. Never set the `--mu-row-*` variables or the size tokens yourself; `mu-compact` is the only way to change a row's height.
|
|
2661
|
+
- Mark the current page with `aria-current="page"`, not with color alone; the fill shows it too.
|
|
2662
|
+
- Rows stack `inset` (2px) apart. Don't add `mu-hit` to stacked rows: their 44px touch areas would overlap. 28px is above the 24px minimum target size.
|
|
2663
|
+
- A row with a count or a menu at its end adds it after the label with `margin-inline-start: auto`.
|
|
2664
|
+
|
|
2665
|
+
### Buttons over a photo
|
|
2666
|
+
|
|
2667
|
+
Controls over an image or a video sit in a dark-theme scope, so they read on any picture in both app themes:
|
|
2668
|
+
|
|
2669
|
+
```tsx
|
|
2670
|
+
<figure className="app-viewer">
|
|
2671
|
+
<img src={src} alt={alt} />
|
|
2672
|
+
<div className="app-viewer-controls" data-theme="dark">
|
|
2673
|
+
<Button icon={<X />} aria-label="Close" onClick={close} />
|
|
2674
|
+
<Button icon={<ChevronLeft />} aria-label="Previous image" onClick={previous} />
|
|
2675
|
+
<Button icon={<ChevronRight />} aria-label="Next image" onClick={next} />
|
|
2676
|
+
</div>
|
|
2677
|
+
</figure>
|
|
2678
|
+
```
|
|
2679
|
+
|
|
2680
|
+
- `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.
|
|
2681
|
+
- When the scope holds only Buttons, leave its own background transparent: the keys carry their own fill and shadow.
|
|
2682
|
+
- A full-screen viewer is a Dialog with `variant="bare" fullScreen`; put the scope inside its Popup.
|
|
2683
|
+
|
|
2684
|
+
### Text over a photo
|
|
2685
|
+
|
|
2686
|
+
Plain text over a picture, such as a viewer's "3 / 12" counter or its zoom level, has no fill of its own, so it needs one. Put it in the same dark-theme scope and give the text a `surface` pill:
|
|
2687
|
+
|
|
2688
|
+
```tsx
|
|
2689
|
+
<div className="app-viewer-controls" data-theme="dark">
|
|
2690
|
+
<span className="app-viewer-label mu-label-sm" aria-live="polite">{index + 1} / {count}</span>
|
|
2691
|
+
<Button icon={<ZoomIn />} aria-label="Zoom in" onClick={zoomIn} />
|
|
2692
|
+
</div>
|
|
2693
|
+
```
|
|
2694
|
+
|
|
2695
|
+
```css
|
|
2696
|
+
.app-viewer-label {
|
|
2697
|
+
display: inline-flex;
|
|
2698
|
+
align-items: center;
|
|
2699
|
+
height: var(--mu-control-md);
|
|
2700
|
+
padding: 0 var(--mu-space-3);
|
|
2701
|
+
border-radius: var(--mu-radius-md);
|
|
2702
|
+
color: var(--mu-ink);
|
|
2703
|
+
background-color: var(--mu-surface);
|
|
2704
|
+
box-shadow: var(--mu-shadow-pop);
|
|
2705
|
+
font-variant-numeric: tabular-nums;
|
|
2706
|
+
}
|
|
2707
|
+
```
|
|
2708
|
+
|
|
2709
|
+
- The pill is opaque, as tall as the Buttons beside it, with their radius. Dark `surface` with `ink` text keeps 4.5:1 on any picture.
|
|
2710
|
+
- No blur and no translucent fill: glass behind text is not in the system (see [Depth](depth.md)), and it fails contrast on bright pictures.
|
|
2711
|
+
- Several labels and Buttons in one row can share one pill instead: put the `surface`, radius, padding and shadow on the scope, and the Buttons become `ghost` inside it, since the pill is their fill.
|
|
2712
|
+
|
|
2322
2713
|
### Inside a shadow root or a host page
|
|
2323
2714
|
|
|
2324
2715
|
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 +2722,7 @@ In an extension or an embed, pass the same `container` to every `Popup` and to `
|
|
|
2331
2722
|
## Size
|
|
2332
2723
|
|
|
2333
2724
|
- **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
|
|
2725
|
+
- **Pick by place, not by importance.** `md` everywhere by default, including dialogs, forms, onboarding, marketing pages and an app's sidebar. A sidebar with 28px rows puts `mu-compact` on itself, which makes md 28px inside it; see [A sidebar navigation row](#a-sidebar-navigation-row). `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
2726
|
- **Never make a control bigger to make it stronger.** Emphasis is the `primary` variant.
|
|
2336
2727
|
- **Don't mix sizes in one row.** A Button beside an Input has the Input's size.
|
|
2337
2728
|
- **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.
|
|
@@ -2343,7 +2734,7 @@ In an extension or an embed, pass the same `container` to every `Popup` and to `
|
|
|
2343
2734
|
- **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.
|
|
2344
2735
|
- **`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.
|
|
2345
2736
|
- **Dialog footers** are a ghost Cancel, then the action, named with its verb ("Delete note", "Create"), never "OK" or "Yes".
|
|
2346
|
-
- **Tone is status only.** Badge and Toast tones say success, warning or danger. Don't use them to highlight, decorate or color-code categories.
|
|
2737
|
+
- **Tone is status only.** Badge and Toast tones say success, warning or danger. Don't use them to highlight, decorate or color-code categories. The one exception is Badge `tone="primary"`: ink, for the single label in a view that should stand out, such as New.
|
|
2347
2738
|
- **Links are the only blue.** Don't color text, icons or backgrounds with `accent`.
|
|
2348
2739
|
|
|
2349
2740
|
## Accessibility checklist
|
|
@@ -2385,7 +2776,21 @@ Run through it for every screen.
|
|
|
2385
2776
|
| A Checkbox that saves at once | A Switch |
|
|
2386
2777
|
| A Select with 30 options | A Combobox |
|
|
2387
2778
|
| 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 |
|
|
2779
|
+
| 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); a small form among them in a `bordered` [Form](components/form.md) |
|
|
2780
|
+
| An icon after a Button's label inside `children` | `iconEnd` |
|
|
2781
|
+
| A `<button>` with its own classes as a row in a Popover | `menuClasses().item`, in a Popover with `variant="list"` |
|
|
2782
|
+
| A Dialog with `className` that removes its padding and X | `variant="bare"` |
|
|
2783
|
+
| A local box (a "settings panel") around a small form on a settings page | `Form.Root bordered` with `Form.Footer` |
|
|
2784
|
+
| A Dialog with `fixed inset-0 rounded-none` | `variant="bare" fullScreen` |
|
|
2785
|
+
| A lighter or clear backdrop for one Dialog, such as a search palette | The standard backdrop: every Dialog dims the page the same way |
|
|
2786
|
+
| A blue or custom-colored New badge | `tone="primary"` |
|
|
2787
|
+
| A wrapper with negative margins around two Buttons in a table cell | `Table.Actions` |
|
|
2788
|
+
| A button with negative margins to make a List row clickable | `List.ItemButton` |
|
|
2789
|
+
| `w-full` on a ToggleGroup and `flex-1` on its Toggles | `fullWidth` |
|
|
2790
|
+
| `h-dvh`, a `border-e` or your own Close on a bare Drawer's content | Nothing: bare does these; give your content `height: 100%` |
|
|
2791
|
+
| A 28px row class, or `--mu-row-height` set by hand | `mu-compact` on the sidebar or popup |
|
|
2792
|
+
| Text over a photo on a translucent, blurred fill | An opaque `surface` pill in a `data-theme="dark"` scope |
|
|
2793
|
+
| Tailwind `size-4` on a standalone icon | `mu-icon-sm` or `mu-icon-md`, which set the stroke too |
|
|
2389
2794
|
| A Spinner where a list will appear | Skeletons in the shape of the list |
|
|
2390
2795
|
| `size="md"` or `"lg"` on a Button to emphasize it | `variant="primary"` at the row's size |
|
|
2391
2796
|
| `size` on every part of a Menu or Select | `size` on its Root |
|
|
@@ -2405,14 +2810,19 @@ Names and parts from other libraries and where they go.
|
|
|
2405
2810
|
| --- | --- |
|
|
2406
2811
|
| Modal, Dialog | [Dialog](components/dialog.md); `sheet` covers phones |
|
|
2407
2812
|
| ConfirmDialog, AlertDialog, `window.confirm` | [AlertDialog](components/dialog.md) |
|
|
2408
|
-
| Drawer, Sheet |
|
|
2813
|
+
| Drawer, Sheet | [Drawer](components/drawer.md) from an edge; a Dialog already becomes a bottom sheet below 640px |
|
|
2814
|
+
| Command palette, Spotlight | Dialog with `variant="bare"` around an inline [Combobox](components/combobox.md) |
|
|
2815
|
+
| Lightbox, image viewer | Dialog with `variant="bare"`, its controls as neutral Buttons in a `data-theme="dark"` scope ([recipe](#buttons-over-a-photo)) |
|
|
2816
|
+
| List group, settings card, DataList | [List](components/list.md) |
|
|
2817
|
+
| Emoji or icon picker | Combobox with `inline` and `grid` |
|
|
2409
2818
|
| DropdownMenu, ActionMenu | [Menu](components/menu.md) |
|
|
2410
2819
|
| ContextMenu | Not built. Put the same actions in a Menu on a visible trigger |
|
|
2411
2820
|
| HoverCard | [Popover](components/popover.md) with `openOnHover` on the trigger |
|
|
2412
2821
|
| Tooltip with a `title` or `content` prop | [Tooltip](components/tooltip.md) parts: Root, Trigger, Popup |
|
|
2413
2822
|
| Snackbar, Toaster, sonner's `toast()` | [Toast](components/toast.md) and `Toast.useToastManager().add()` |
|
|
2414
2823
|
| Toast `variant="destructive"` | `type: "danger"` |
|
|
2415
|
-
| Card, Paper, Panel | No component: spacing, a section title and [Separator](components/separator.md) |
|
|
2824
|
+
| Card, Paper, Panel | No component: spacing, a section title and [Separator](components/separator.md). For a boxed group of rows, [List](components/list.md); for a boxed small form, a `bordered` [Form](components/form.md) |
|
|
2825
|
+
| Form (Base UI, React Hook Form wrappers) | [Form](components/form.md), with [Field](components/field.md)s inside |
|
|
2416
2826
|
| Divider | [Separator](components/separator.md) |
|
|
2417
2827
|
| Chip, Tag | [Badge](components/badge.md) to show, [Toggle](components/toggle.md) to filter |
|
|
2418
2828
|
| SegmentedControl, RadioButtons in a row | [ToggleGroup](components/toggle-group.md) without `multiple` |
|
|
@@ -2441,10 +2851,11 @@ How the components go together. Each product builds its screens from these patte
|
|
|
2441
2851
|
|
|
2442
2852
|
## Page
|
|
2443
2853
|
|
|
2444
|
-
- From `breakpoint-md` (1024px) up: a fixed sidebar of `sidebar-width` (240px). Below it, the sidebar is a
|
|
2854
|
+
- 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.
|
|
2855
|
+
- A sidebar may use 28px rows: put `mu-compact` on it and every md control and row inside is `control-compact` (28px) tall, the package's (a ghost Select switcher, the "New" Button) and the app's own navigation rows alike. See [A sidebar navigation row](usage.md#a-sidebar-navigation-row). Without it, sidebar rows are md (32px).
|
|
2445
2856
|
- Content sits in a column of at most `content-width` (720px), padded `space-4` on phones and `space-6` on desktop.
|
|
2446
2857
|
- 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
|
-
- 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
|
|
2858
|
+
- 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 are horizontal Fields: the name at weight 400 (`body`) and the description in `caption`, `ink-muted`. The section title above is the only `label` (500); the row names stay 400 so the title stands out from them.
|
|
2448
2859
|
|
|
2449
2860
|
## Form
|
|
2450
2861
|
|
|
@@ -2452,7 +2863,9 @@ How the components go together. Each product builds its screens from these patte
|
|
|
2452
2863
|
- Related checkboxes or switches sit in a `Fieldset`.
|
|
2453
2864
|
- Actions go at the end, right-aligned: a ghost Cancel, then the primary.
|
|
2454
2865
|
- A form that saves at once (settings) uses switches and has no Save button. A form with a Save button uses checkboxes.
|
|
2866
|
+
- 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
2867
|
- Errors appear under the field, in words that say how to fix it.
|
|
2868
|
+
- A small form on a settings page, among Lists ("Add a tag rule", a reactions picker), is a `bordered` [Form](components/form.md): a title, its fields, and the buttons in a footer bar.
|
|
2456
2869
|
|
|
2457
2870
|
## Entry
|
|
2458
2871
|
|
|
@@ -2493,7 +2906,7 @@ Under `breakpoint-sm` (640px):
|
|
|
2493
2906
|
|
|
2494
2907
|
- One column.
|
|
2495
2908
|
- Dialogs and menus become sheets.
|
|
2496
|
-
- Toasts
|
|
2909
|
+
- Toasts span the top of the screen.
|
|
2497
2910
|
- Every control keeps its 44px hit area.
|
|
2498
2911
|
- Controls stay `md`.
|
|
2499
2912
|
|
|
@@ -2559,7 +2972,7 @@ An app changes only the color, shadow and font tokens. Every other token and eve
|
|
|
2559
2972
|
- `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
2973
|
- 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
2974
|
- `mu-prose` styles rendered content. See [Prose](components/prose.md).
|
|
2562
|
-
- `mu-icon` applies icon size and stroke rules outside a component.
|
|
2975
|
+
- `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
2976
|
- `mu-visually-hidden` hides text visually but keeps it for screen readers.
|
|
2564
2977
|
- 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
2978
|
- 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 +3084,13 @@ A class function returns the classes that make any element look like a component
|
|
|
2671
3084
|
| `inputClasses({ size })` | A string | One-line field, such as a tag input around a native `<input>` |
|
|
2672
3085
|
| `textareaClasses({ size })` | A string | Multi-line field, such as a content-editable editor |
|
|
2673
3086
|
| `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 |
|
|
3087
|
+
| `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"` |
|
|
3088
|
+
| `popoverClasses({ variant })` | `popup`, `title`, `description` | Floating panel, such as an emoji picker |
|
|
2676
3089
|
|
|
2677
3090
|
State is set with attributes, never with extra classes:
|
|
2678
3091
|
|
|
2679
3092
|
- 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.
|
|
3093
|
+
- 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
3094
|
- A popup fades in with `data-starting-style` and out with `data-ending-style`, which Base UI's popups set.
|
|
2682
3095
|
|
|
2683
3096
|
```tsx
|
|
@@ -2709,6 +3122,7 @@ Apply these classes to an app's own elements. Don't override their rules: the pr
|
|
|
2709
3122
|
| `mu-s-xs` | The xs size row: 20px | Secondary actions beside 13px text |
|
|
2710
3123
|
| `mu-s-sm` | The sm size row: 24px | Dense toolbars, table rows, filters |
|
|
2711
3124
|
| `mu-s-md` | The md size row: 32px | Everything else |
|
|
3125
|
+
| `mu-compact` | md controls and rows inside are `control-compact` (28px) tall; text, icons, padding and radius stay md | An app's sidebar with 28px rows, or a list Popover beside it |
|
|
2712
3126
|
| `mu-base` | The system font, the row's text size and weight 500, `ink` text, and icon sizing for Lucide icons inside | The root of a control |
|
|
2713
3127
|
| `mu-recessed` | `well`, a 1px `edge` and `shadow-track` | A part that holds a value |
|
|
2714
3128
|
| `mu-key` | A `key` with the sheen and `shadow-key` | A key inside a well |
|
|
@@ -2718,7 +3132,10 @@ Apply these classes to an app's own elements. Don't override their rules: the pr
|
|
|
2718
3132
|
| `mu-positioner` | The popup layer | A popup's positioner |
|
|
2719
3133
|
| `mu-focus` | The ink focus ring on `:focus-visible`, 1px outside the edge | Anything focusable |
|
|
2720
3134
|
| `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
|
|
3135
|
+
| `mu-icon` | Icon size and stroke from the nearest size row, 16px outside one | A Lucide icon inside an app's own row |
|
|
3136
|
+
| `mu-icon-xs` | A 12px icon with a 1.25px stroke | A Lucide icon beside 12px or 13px text, outside a control |
|
|
3137
|
+
| `mu-icon-sm` | A 14px icon with a 1.25px stroke | A Lucide icon beside 13px or 14px text, outside a control |
|
|
3138
|
+
| `mu-icon-md` | A 16px icon with a 1.5px stroke | A Lucide icon beside 14px or 16px text, outside a control |
|
|
2722
3139
|
| `mu-visually-hidden` | Hidden on screen, read by screen readers | Text for screen readers only |
|
|
2723
3140
|
| `mu-label-xs` | 12 / 16px, 500 | Labels of 20px parts |
|
|
2724
3141
|
| `mu-caption` | 13 / 18px, 400 | Secondary text |
|
|
@@ -2740,13 +3157,15 @@ A size row class sets these variables on its element, and children inherit them.
|
|
|
2740
3157
|
| `--mu-row-padding-icon` | 4px | 6px | 10px |
|
|
2741
3158
|
| `--mu-row-font-size` | 12px | 13px | 14px |
|
|
2742
3159
|
| `--mu-row-line-height` | 16px | 18px | 20px |
|
|
3160
|
+
| `--mu-row-field-font-size` | 12px | 13px | 14px |
|
|
3161
|
+
| `--mu-row-field-line-height` | 16px | 18px | 20px |
|
|
2743
3162
|
| `--mu-row-radius` | 4px | 4px | 6px |
|
|
2744
3163
|
| `--mu-row-gap` | 4px | 4px | 6px |
|
|
2745
3164
|
| `--mu-row-icon` | 12px | 14px | 16px |
|
|
2746
3165
|
| `--mu-row-icon-stroke` | 1.25px | 1.25px | 1.5px |
|
|
2747
3166
|
| `--mu-row-check-box` | 14px | 16px | 18px |
|
|
2748
3167
|
|
|
2749
|
-
`--mu-row-padding-icon` is the padding on the side of a control that starts or ends with an icon.
|
|
3168
|
+
`--mu-row-padding-icon` is the padding on the side of a control that starts or ends with an icon. Inside `mu-compact`, the md `--mu-row-height` is 28px. `--mu-row-field-font-size` and `--mu-row-field-line-height` are for text people type; on touch screens they are 16px and 20px at every size, so iOS Safari does not zoom in when a field takes focus.
|
|
2750
3169
|
|
|
2751
3170
|
```css
|
|
2752
3171
|
.app-tag-input-chip {
|