@usememos/ui 26.11.2 → 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/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 30 components, what was left out, and one page per component |
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 |
@@ -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` | `system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` |
286
- | `--mu-font-display` | `system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` |
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 the same sizes: 16px text (14px at `sm`), 20px `h1`, 16px `h2`–`h6`, and 13px table heads. See [Prose](components/prose.md).
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, sheets and an app's sidebar navigation. Emphasis comes from the `primary` variant, not from a bigger control. On touch screens the hit area, not the visual size, grows.
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 |
@@ -556,12 +559,12 @@ When Lucide doesn't have an icon, draw it on Lucide's 24px grid with 2px round s
556
559
 
557
560
  # Components
558
561
 
559
- 30 components in five groups, chosen from what Base UI and other popular libraries provide and what our products need today. Every component is generic: it works the same in memos, the website, the web clipper and any future product.
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.
560
563
 
561
564
  | Group | Components |
562
565
  | --- | --- |
563
566
  | Actions | [Button](button.md), [Toggle](toggle.md), [ToggleGroup](toggle-group.md), [Menu](menu.md), [Link](link.md) |
564
- | Forms | [Field and Fieldset](field.md), [Input](input.md), [Textarea](textarea.md), [Select](select.md), [Combobox](combobox.md), [Checkbox](checkbox.md), [Switch](switch.md), [RadioGroup](radio-group.md) |
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) |
565
568
  | Navigation | [Tabs](tabs.md), [Collapsible](collapsible.md) |
566
569
  | Overlays | [Dialog](dialog.md) (and its sheet form), [Drawer](drawer.md), [Popover](popover.md), [Tooltip](tooltip.md), [Toast](toast.md) |
567
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) |
@@ -581,7 +584,7 @@ Components follow the [API conventions](../building.md#api-conventions): parts w
581
584
 
582
585
  ## Left out on purpose
583
586
 
584
- - **Card.** Content is grouped by spacing, headings and separators. The only boxes are a [List](list.md) of settings or records and a `bordered` [Table](table.md).
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.
585
588
  - **Slider, Stepper.** No setting in our products needs a continuous range. Use an Input with `type="number"` or a Select.
586
589
  - **Chip, SegmentedControl, Accordion.** A Toggle covers filter chips, a single-choice ToggleGroup covers segmented choices, and stacked Collapsibles cover an accordion.
587
590
  - **Later, when a screen needs them:** multi-select Combobox, ContextMenu, ScrollArea, Progress, Calendar.
@@ -610,6 +613,7 @@ The short version. [Using the components](../usage.md) has the full decision gui
610
613
  | Confirm something happened | Toast |
611
614
  | Settings rows, or a short list of records with actions | List |
612
615
  | Data with columns | Table |
616
+ | A small form (heading, a few fields, buttons) among Lists on a settings page | Form with `bordered` |
613
617
  | A status or tag label | Badge |
614
618
  | A clickable tag filter | Toggle |
615
619
  | Content still loading | Skeleton |
@@ -757,7 +761,8 @@ import { Bold, Code, Italic } from "lucide-react";
757
761
 
758
762
  ```ts
759
763
  interface ToggleGroupProps extends BaseToggleGroup.Props {
760
- size?: Size; // passed to every Toggle inside
764
+ size?: Size; // passed to every Toggle inside
765
+ fullWidth?: boolean; // fills the container; labeled Toggles share the width equally
761
766
  }
762
767
  ```
763
768
 
@@ -770,10 +775,11 @@ Base UI's props pass through: `value`, `defaultValue`, `onValueChange`, `multipl
770
775
  - Without `multiple`: filters and view switches that don't change the whole page. For sections of a page, use Tabs.
771
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.
772
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.
773
779
 
774
780
  ## Style
775
781
 
776
- - At most as wide as its container. When the labels don't fit, they shrink with an ellipsis; icon-only items keep their size.
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.
777
783
  - `multiple`: items 2px apart.
778
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.
779
785
 
@@ -907,6 +913,72 @@ Provide `href` and `children`.
907
913
 
908
914
  - `accent` text that underlines on hover: the one place the hue appears at rest. `muted` uses `ink-muted`.
909
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
+
910
982
  <!-- docs/components/field.md -->
911
983
 
912
984
  # Field and Fieldset
@@ -983,7 +1055,7 @@ Base UI's props pass through: `name`, `invalid`, `disabled`, `validate`, `valida
983
1055
  ## Style
984
1056
 
985
1057
  - Label: `label` style (14px, 500), `space-1` above the control; weight 400 when it wraps a Checkbox, Switch or Radio.
986
- - Horizontal: a two-column grid. The label (weight 400) and description fill the first column; the control sits at the end, centered on them, `space-4` from the text. The error spans the row under them.
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.
987
1059
  - Description and error: `caption` (13px), below the control. The error hides the description and turns the control's edge `danger`.
988
1060
 
989
1061
  <!-- docs/components/input.md -->
@@ -1027,7 +1099,7 @@ Wraps Base UI's [Input](https://base-ui.com/react/components/input), so inside a
1027
1099
  ## Style
1028
1100
 
1029
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.
1030
- - 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.
1031
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.
1032
1104
  - The side with a prefix or suffix has 2px less padding.
1033
1105
 
@@ -1070,6 +1142,7 @@ Base UI has no textarea part, so this renders Base UI's `Field.Control` as a `<t
1070
1142
  ## Style
1071
1143
 
1072
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.
1073
1146
 
1074
1147
  <!-- docs/components/select.md -->
1075
1148
 
@@ -1139,7 +1212,7 @@ Base UI's props pass through: `items`, `value`, `defaultValue`, `onValueChange`,
1139
1212
  - Label it with a [Field](field.md), or `aria-label` on the trigger.
1140
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.
1141
1214
  - For actions, use [Menu](menu.md).
1142
- - `variant="ghost"` draws the trigger as a row instead of a key, for a value people switch in place, such as the current space in a sidebar. Put an icon or Avatar before `Select.Value`. In a form, keep `neutral`.
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`.
1143
1216
 
1144
1217
  ## Style
1145
1218
 
@@ -1552,10 +1625,11 @@ Dialog and AlertDialog have the same parts.
1552
1625
  interface Dialog.PopupProps extends BaseDialog.Popup.Props {
1553
1626
  sheet?: boolean; // below breakpoint-sm, a sheet from the bottom; default true
1554
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)
1555
1629
  closeLabel?: string; // accessible name of the X; default "Close"
1556
1630
  container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null; // portal target
1557
1631
  }
1558
- // AlertDialog.Popup takes sheet and container; it has no X and no variant.
1632
+ // AlertDialog.Popup takes sheet and container; it has no X, no variant and no fullScreen.
1559
1633
  ```
1560
1634
 
1561
1635
  Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `disablePointerDismissal` on the root; `initialFocus`, `finalFocus` on the popup.
@@ -1568,6 +1642,8 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `d
1568
1642
  - Escape and a backdrop click close a Dialog. While a form has unsaved input, keep it open from `onOpenChange`.
1569
1643
  - Focus moves into the dialog and returns to the trigger on close.
1570
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.
1571
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.
1572
1648
 
1573
1649
  ## Style
@@ -1577,6 +1653,7 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal`, `d
1577
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.
1578
1654
  - Long content scrolls the viewport around the panel.
1579
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.
1580
1657
 
1581
1658
  <!-- docs/components/drawer.md -->
1582
1659
 
@@ -1618,7 +1695,7 @@ interface Drawer.RootProps extends BaseDrawer.Root.Props {
1618
1695
  }
1619
1696
  interface Drawer.PopupProps extends BaseDrawer.Popup.Props {
1620
1697
  variant?: "panel" | "bare"; // default "panel"; bare: no padding, gap or X
1621
- closeLabel?: string; // accessible name of the X; default "Close"
1698
+ closeLabel?: string; // accessible name of the X, or of bare's screen-reader Close; default "Close"
1622
1699
  container?: HTMLElement | ShadowRoot | React.RefObject<HTMLElement | ShadowRoot | null> | null; // portal target
1623
1700
  }
1624
1701
  ```
@@ -1629,15 +1706,16 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `onOpenChan
1629
1706
 
1630
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.
1631
1708
  - Name it: a `Drawer.Title`, hidden with `mu-visually-hidden` when the content names itself, or `aria-label` on the popup.
1632
- - `variant="bare"` is for content that lays itself out, such as an app's sidebar. Set its width with `className` when the content has its own, such as `sidebar-width`.
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.
1633
1710
  - Close it when people pick a destination: wrap each link in `Drawer.Close render={<a … />}`, or close it from the router.
1634
1711
  - Escape, a backdrop click and a swipe toward its edge close it. Focus moves in and returns to the trigger.
1635
1712
  - The panel is always md, as a Dialog is.
1636
1713
 
1637
1714
  ## Style
1638
1715
 
1639
- - `surface` with `shadow-pop`, full height and `drawer-width` (320px) wide from the start or end edge, at most the screen width minus `space-12`. Square corners; from the bottom it is full width with `radius-2xl` top corners and at most the screen height minus `space-6`.
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`.
1640
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.
1641
1719
  - The backdrop is `overlay`; it fades as the drawer is swiped away.
1642
1720
  - Slides in from its edge over `duration-settle` with `ease-settle` and follows the finger while swiped. Under reduced motion it fades instead.
1643
1721
 
@@ -1691,7 +1769,7 @@ Base UI's props pass through: `open`, `defaultOpen`, `onOpenChange`, `modal` on
1691
1769
 
1692
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).
1693
1771
  - Give it a `Popover.Title`, or `aria-label` on the popup.
1694
- - `variant="list"` is for a panel made of rows, such as an account panel (a name, then links) or a scope picker with a header. Draw the rows with `menuClasses().item` and the lines between them with `menuClasses().separator`. When the rows are only actions, use a [Menu](menu.md) instead.
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"`.
1695
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.
1696
1774
  - It closes on Escape and on a click outside; focus returns to the trigger.
1697
1775
  - An app's own floating panel, such as an emoji picker, takes `popoverClasses()`. See [Class functions](../custom-components.md#class-functions).
@@ -1879,31 +1957,32 @@ import { Hash, Lock } from "lucide-react";
1879
1957
  <Badge icon={<Hash />}>reading</Badge>
1880
1958
  <Badge tone="outline" icon={<Lock />}>Private</Badge>
1881
1959
  <Badge tone="success">Synced</Badge>
1960
+ <Badge tone="primary">New</Badge>
1882
1961
  ```
1883
1962
 
1884
1963
  ## Props
1885
1964
 
1886
1965
  ```ts
1887
1966
  interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
1888
- tone?: Tone | "outline"; // default "neutral"
1967
+ tone?: Tone | "outline" | "primary"; // default "neutral"
1889
1968
  icon?: IconElement;
1890
1969
  children: React.ReactNode; // one or two words
1891
1970
  render?: React.ReactElement;
1892
1971
  }
1893
1972
 
1894
- function badgeClasses(options?: { tone?: Tone | "outline" }): string;
1973
+ function badgeClasses(options?: { tone?: Tone | "outline" | "primary" }): string;
1895
1974
  ```
1896
1975
 
1897
1976
  ## Rules
1898
1977
 
1899
- - 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.
1900
1979
  - A badge is not a button. A clickable tag filter is a [Toggle](toggle.md).
1901
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).
1902
1981
 
1903
1982
  ## Style
1904
1983
 
1905
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.
1906
- - `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.
1907
1986
 
1908
1987
  <!-- docs/components/kbd.md -->
1909
1988
 
@@ -2100,6 +2179,7 @@ No Base UI part: these are styled native table elements.
2100
2179
  | `Table.Row` | `<tr>` |
2101
2180
  | `Table.Head` | `<th scope="col">`. Takes `num` |
2102
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 |
2103
2183
 
2104
2184
  ## Props
2105
2185
 
@@ -2124,6 +2204,16 @@ Every native attribute passes through.
2124
2204
  - Use it for data with columns. Lists of content (notes, comments) are [entries](../patterns.md#entry), not rows.
2125
2205
  - Set `num` on a numeric column's Head and on its Cells.
2126
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
+ ```
2127
2217
  - Name the table with a Caption, or `aria-label` on the root.
2128
2218
  - Sort, select and paginate in the app. The table has no data props.
2129
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.
@@ -2131,9 +2221,9 @@ Every native attribute passes through.
2131
2221
  ## Style
2132
2222
 
2133
2223
  - No stripes: a `line` under the head row and between body rows. Body rows highlight with `hover`.
2134
- - `bordered`: `surface` fill, a 1px `line` edge and `radius-lg` on the scrolling wrapper; the Caption sits inside the edge, under a `line`. The outer columns pad `space-1` more, so the text lines up with a [List](list.md) row's.
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.
2135
2225
  - Heads: `label-sm` (13px, 500) in `ink-muted`, a control height tall.
2136
- - Cells: `caption` at sm and `body` at md, with tabular numbers. Rows are 34 or 40px; a Button in a cell overlaps the padding, so rows keep that height.
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.
2137
2227
  - Cells pad `px` at the sides. Caption: `caption` in `ink-muted`, `space-2` above.
2138
2228
 
2139
2229
  <!-- docs/components/list.md -->
@@ -2173,6 +2263,7 @@ No Base UI part: these are styled native list elements.
2173
2263
  | --- | --- |
2174
2264
  | `List.Root` | A `<ul>` with a `line` edge and rounded corners. Takes `size` |
2175
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 |
2176
2267
 
2177
2268
  ## Props
2178
2269
 
@@ -2190,13 +2281,27 @@ Every native attribute passes through.
2190
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).
2191
2282
  - Name the list: `aria-labelledby` pointing at the section title above it, or `aria-label`.
2192
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.
2193
- - Rows are not links or buttons. To open a record, put a Link or a Button in the row.
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
+ ```
2194
2299
  - An empty list shows the [empty state](../patterns.md#empty-state) in place of the List.
2195
2300
 
2196
2301
  ## Style
2197
2302
 
2198
2303
  - `surface` fill, a 1px `line` edge, `radius-lg`. No shadow: it is flat, like a field.
2199
- - Rows pad `space-3` × `space-4` at md and `space-2` × `space-3` at sm, divided by a 1px `line`. No hover: rows don't act on click.
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.
2200
2305
  - Text is `body` in `ink`.
2201
2306
 
2202
2307
  <!-- docs/components/prose.md -->
@@ -2240,8 +2345,8 @@ Every native attribute passes through.
2240
2345
 
2241
2346
  ## Style
2242
2347
 
2243
- - Text: 16px on a 24px line (`sm`: 14 on 22), weight 400, `ink`. Blocks are `space-3` apart.
2244
- - Headings: `h1` 20/28px, `h2`–`h6` 16/24px; all weight 600, 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.
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.
2245
2350
  - Links: `accent`, underlined at rest, because color alone does not set them apart from the text.
2246
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.
2247
2352
  - Quotes: a 2px `edge` bar, text in `ink-muted`. Rules: a 1px `line` with `space-5` around it.
@@ -2249,7 +2354,7 @@ Every native attribute passes through.
2249
2354
  - Images and video: never wider than the column, `radius-lg`.
2250
2355
  - Tables follow [Table](table.md): `line` between rows, 13px `ink-muted` heads (weight 500), tabular numbers; they scroll sideways when too wide.
2251
2356
  - Tags: `ink-muted` on `well`, `radius-xs`. They read as words, not badges.
2252
- - Sizes are custom properties set on `.mu-prose` in the foundation (`--prose-fs`, `--prose-lh`, heading sizes).
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).
2253
2358
 
2254
2359
  <!-- docs/usage.md -->
2255
2360
 
@@ -2486,7 +2591,7 @@ const [confirming, setConfirming] = useState(false);
2486
2591
  </List.Root>
2487
2592
  ```
2488
2593
 
2489
- A horizontal Field puts the setting's name first and the Switch or Select at the end of the row. A [List](components/list.md) boxes the rows of one section and divides them with lines. Inside a Dialog, leave out the List and stack the Fields. For a short list of options under one heading (notification types), a Switch or Checkbox inside its `Field.Label` also works.
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.
2490
2595
 
2491
2596
  No Save button and no toast: the Switch shows the result. If saving fails, move the Switch back and show a danger Toast.
2492
2597
 
@@ -2514,6 +2619,49 @@ One `Tooltip.Provider` around the toolbar (or the app) so moving along it opens
2514
2619
  - Show one or two frequent actions as xs or sm ghost Buttons beside it, each with a Tooltip.
2515
2620
  - On an entry the trigger appears on hover; on touch screens it is always visible (see [Entry](patterns.md#entry)).
2516
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
+
2517
2665
  ### Buttons over a photo
2518
2666
 
2519
2667
  Controls over an image or a video sit in a dark-theme scope, so they read on any picture in both app themes:
@@ -2530,8 +2678,37 @@ Controls over an image or a video sit in a dark-theme scope, so they read on any
2530
2678
  ```
2531
2679
 
2532
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.
2533
- - Leave the scope's own background transparent; the keys carry their own fill and shadow.
2534
- - A full-screen viewer is a Dialog with `variant="bare"`; put the scope inside its Popup.
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.
2535
2712
 
2536
2713
  ### Inside a shadow root or a host page
2537
2714
 
@@ -2545,7 +2722,7 @@ In an extension or an embed, pass the same `container` to every `Popup` and to `
2545
2722
  ## Size
2546
2723
 
2547
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.
2548
- - **Pick by place, not by importance.** `md` everywhere by default, including dialogs, forms, onboarding, marketing pages and the app's sidebar (its navigation rows and its main button). `sm` for dense toolbars, table rows and filters inside panels. `xs` for secondary actions beside 13px text (entry footers, row actions). See [Control sizes](layout.md#control-sizes).
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).
2549
2726
  - **Never make a control bigger to make it stronger.** Emphasis is the `primary` variant.
2550
2727
  - **Don't mix sizes in one row.** A Button beside an Input has the Input's size.
2551
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.
@@ -2557,7 +2734,7 @@ In an extension or an embed, pass the same `container` to every `Popup` and to `
2557
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.
2558
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.
2559
2736
  - **Dialog footers** are a ghost Cancel, then the action, named with its verb ("Delete note", "Create"), never "OK" or "Yes".
2560
- - **Tone is status only.** Badge and Toast tones say success, warning or danger. Don't use them to highlight, decorate or color-code categories.
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.
2561
2738
  - **Links are the only blue.** Don't color text, icons or backgrounds with `accent`.
2562
2739
 
2563
2740
  ## Accessibility checklist
@@ -2599,10 +2776,20 @@ Run through it for every screen.
2599
2776
  | A Checkbox that saves at once | A Switch |
2600
2777
  | A Select with 30 options | A Combobox |
2601
2778
  | A Select with two options and room to spare | A RadioGroup |
2602
- | A boxed card around each item | Whitespace, a section title, or a Separator. Rows of settings or records go in one [List](components/list.md) |
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) |
2603
2780
  | An icon after a Button's label inside `children` | `iconEnd` |
2604
2781
  | A `<button>` with its own classes as a row in a Popover | `menuClasses().item`, in a Popover with `variant="list"` |
2605
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 |
2606
2793
  | Tailwind `size-4` on a standalone icon | `mu-icon-sm` or `mu-icon-md`, which set the stroke too |
2607
2794
  | A Spinner where a list will appear | Skeletons in the shape of the list |
2608
2795
  | `size="md"` or `"lg"` on a Button to emphasize it | `variant="primary"` at the row's size |
@@ -2634,7 +2821,8 @@ Names and parts from other libraries and where they go.
2634
2821
  | Tooltip with a `title` or `content` prop | [Tooltip](components/tooltip.md) parts: Root, Trigger, Popup |
2635
2822
  | Snackbar, Toaster, sonner's `toast()` | [Toast](components/toast.md) and `Toast.useToastManager().add()` |
2636
2823
  | Toast `variant="destructive"` | `type: "danger"` |
2637
- | Card, Paper, Panel | No component: spacing, a section title and [Separator](components/separator.md). For a boxed group of rows, [List](components/list.md) |
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 |
2638
2826
  | Divider | [Separator](components/separator.md) |
2639
2827
  | Chip, Tag | [Badge](components/badge.md) to show, [Toggle](components/toggle.md) to filter |
2640
2828
  | SegmentedControl, RadioButtons in a row | [ToggleGroup](components/toggle-group.md) without `multiple` |
@@ -2664,10 +2852,10 @@ How the components go together. Each product builds its screens from these patte
2664
2852
  ## Page
2665
2853
 
2666
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.
2667
- - Sidebar rows (navigation links, a space switcher, the main "New" button) are md: 32px tall, `label` text, md icons. There is no 28px row; don't build one with custom padding.
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).
2668
2856
  - Content sits in a column of at most `content-width` (720px), padded `space-4` on phones and `space-6` on desktop.
2669
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.
2670
- - Sections inside a page have a `label` title (14px, 500) with `space-6` above it and `space-2` below. Section titles are never larger than the text around them; space and grouping set them apart. A section's rows use `label` for the name and `caption` in `ink-muted` for the description.
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.
2671
2859
 
2672
2860
  ## Form
2673
2861
 
@@ -2677,6 +2865,7 @@ How the components go together. Each product builds its screens from these patte
2677
2865
  - A form that saves at once (settings) uses switches and has no Save button. A form with a Save button uses checkboxes.
2678
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.
2679
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.
2680
2869
 
2681
2870
  ## Entry
2682
2871
 
@@ -2933,6 +3122,7 @@ Apply these classes to an app's own elements. Don't override their rules: the pr
2933
3122
  | `mu-s-xs` | The xs size row: 20px | Secondary actions beside 13px text |
2934
3123
  | `mu-s-sm` | The sm size row: 24px | Dense toolbars, table rows, filters |
2935
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 |
2936
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 |
2937
3127
  | `mu-recessed` | `well`, a 1px `edge` and `shadow-track` | A part that holds a value |
2938
3128
  | `mu-key` | A `key` with the sheen and `shadow-key` | A key inside a well |
@@ -2967,13 +3157,15 @@ A size row class sets these variables on its element, and children inherit them.
2967
3157
  | `--mu-row-padding-icon` | 4px | 6px | 10px |
2968
3158
  | `--mu-row-font-size` | 12px | 13px | 14px |
2969
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 |
2970
3162
  | `--mu-row-radius` | 4px | 4px | 6px |
2971
3163
  | `--mu-row-gap` | 4px | 4px | 6px |
2972
3164
  | `--mu-row-icon` | 12px | 14px | 16px |
2973
3165
  | `--mu-row-icon-stroke` | 1.25px | 1.25px | 1.5px |
2974
3166
  | `--mu-row-check-box` | 14px | 16px | 18px |
2975
3167
 
2976
- `--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.
2977
3169
 
2978
3170
  ```css
2979
3171
  .app-tag-input-chip {