@jonmilner/ui 0.0.0-stage → 0.1.1
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/THIRD_PARTY_NOTICES.md +51 -0
- package/dist/AlertDialog/index.d.mts +21 -0
- package/dist/AlertDialog/index.mjs +29 -0
- package/dist/Badge/Badge.css +2 -0
- package/dist/Badge/Badge.css.mjs +24 -0
- package/dist/Badge/index.d.mts +11 -0
- package/dist/Badge/index.mjs +54 -0
- package/dist/Box/Box.css +2 -0
- package/dist/Box/Box.css.mjs +8 -0
- package/dist/Box/index.d.mts +8 -0
- package/dist/Box/index.mjs +15 -0
- package/dist/Button/Button.css +2 -0
- package/dist/Button/Button.css.mjs +25 -0
- package/dist/Button/index.d.mts +18 -0
- package/dist/Button/index.mjs +97 -0
- package/dist/Checkbox/Checkbox.css +2 -0
- package/dist/Checkbox/Checkbox.css.mjs +8 -0
- package/dist/Checkbox/index.d.mts +6 -0
- package/dist/Checkbox/index.mjs +45 -0
- package/dist/Chip/Chip.css +2 -0
- package/dist/Chip/Chip.css.mjs +17 -0
- package/dist/Chip/index.d.mts +13 -0
- package/dist/Chip/index.mjs +73 -0
- package/dist/Clickable/Clickable.css +2 -0
- package/dist/Clickable/Clickable.css.mjs +8 -0
- package/dist/Clickable/index.d.mts +11 -0
- package/dist/Clickable/index.mjs +28 -0
- package/dist/Dialog/Dialog.css +2 -0
- package/dist/Dialog/Dialog.css.d.mts +3 -0
- package/dist/Dialog/Dialog.css.mjs +12 -0
- package/dist/Dialog/client.d.mts +12 -0
- package/dist/Dialog/client.mjs +31 -0
- package/dist/Dialog/index.d.mts +30 -0
- package/dist/Dialog/index.mjs +85 -0
- package/dist/Field/Field.css +2 -0
- package/dist/Field/Field.css.mjs +8 -0
- package/dist/Field/client.mjs +26 -0
- package/dist/Field/index.d.mts +20 -0
- package/dist/Field/index.mjs +88 -0
- package/dist/Heading/Heading.css +2 -0
- package/dist/Heading/Heading.css.mjs +8 -0
- package/dist/Heading/index.d.mts +9 -0
- package/dist/Heading/index.mjs +27 -0
- package/dist/Icon/Icon.css +2 -0
- package/dist/Icon/Icon.css.mjs +17 -0
- package/dist/Icon/index.d.mts +12 -0
- package/dist/Icon/index.mjs +24 -0
- package/dist/IconButton/IconButton.css +2 -0
- package/dist/IconButton/IconButton.css.mjs +24 -0
- package/dist/IconButton/index.d.mts +15 -0
- package/dist/IconButton/index.mjs +25 -0
- package/dist/Inline/Inline.css +2 -0
- package/dist/Inline/Inline.css.mjs +8 -0
- package/dist/Inline/index.d.mts +14 -0
- package/dist/Inline/index.mjs +17 -0
- package/dist/Menu/Menu.css +2 -0
- package/dist/Menu/Menu.css.mjs +8 -0
- package/dist/Menu/client.d.mts +9 -0
- package/dist/Menu/client.mjs +27 -0
- package/dist/Menu/index.d.mts +34 -0
- package/dist/Menu/index.mjs +107 -0
- package/dist/MultiSelect/MultiSelect.css +2 -0
- package/dist/MultiSelect/MultiSelect.css.mjs +8 -0
- package/dist/MultiSelect/client.d.mts +8 -0
- package/dist/MultiSelect/client.mjs +93 -0
- package/dist/MultiSelect/index.d.mts +23 -0
- package/dist/MultiSelect/index.mjs +75 -0
- package/dist/SearchField/SearchField.css +2 -0
- package/dist/SearchField/SearchField.css.mjs +27 -0
- package/dist/SearchField/client.d.mts +5 -0
- package/dist/SearchField/client.mjs +74 -0
- package/dist/SearchField/index.d.mts +25 -0
- package/dist/SearchField/index.mjs +36 -0
- package/dist/Stack/Stack.css +2 -0
- package/dist/Stack/Stack.css.mjs +8 -0
- package/dist/Stack/index.d.mts +14 -0
- package/dist/Stack/index.mjs +15 -0
- package/dist/Tabs/Tabs.css +2 -0
- package/dist/Tabs/Tabs.css.mjs +8 -0
- package/dist/Tabs/index.d.mts +20 -0
- package/dist/Tabs/index.mjs +88 -0
- package/dist/Tag/Tag.css +2 -0
- package/dist/Tag/Tag.css.mjs +17 -0
- package/dist/Tag/index.d.mts +13 -0
- package/dist/Tag/index.mjs +82 -0
- package/dist/Text/Text.css +2 -0
- package/dist/Text/Text.css.mjs +32 -0
- package/dist/Text/index.d.mts +16 -0
- package/dist/Text/index.mjs +25 -0
- package/dist/Toast/Toast.css +2 -0
- package/dist/Toast/Toast.css.mjs +18 -0
- package/dist/Toast/client.d.mts +46 -0
- package/dist/Toast/client.mjs +219 -0
- package/dist/Toast/index.d.mts +7 -0
- package/dist/Toast/index.mjs +5 -0
- package/dist/base.css +6 -0
- package/dist/brands/brand.d.mts +9 -0
- package/dist/brands/brand.mjs +4 -0
- package/dist/brands/check-fonts.mjs +15 -0
- package/dist/brands/default.d.mts +4 -0
- package/dist/brands/default.mjs +74 -0
- package/dist/brands/font.d.mts +17 -0
- package/dist/brands/font.mjs +35 -0
- package/dist/brands/forced-colors.mjs +18 -0
- package/dist/brands/index.d.mts +8 -0
- package/dist/brands/index.mjs +5 -0
- package/dist/brands/metrics.mjs +40 -0
- package/dist/contrast/color.d.mts +3 -0
- package/dist/contrast/color.mjs +222 -0
- package/dist/contrast/contrast.d.mts +18 -0
- package/dist/contrast/contrast.mjs +52 -0
- package/dist/contrast/mixes.mjs +33 -0
- package/dist/docs/AlertDialog.md +113 -0
- package/dist/docs/Badge.md +76 -0
- package/dist/docs/Box.md +87 -0
- package/dist/docs/Button.md +98 -0
- package/dist/docs/Checkbox.md +107 -0
- package/dist/docs/Chip.md +92 -0
- package/dist/docs/Clickable.md +82 -0
- package/dist/docs/Dialog.md +166 -0
- package/dist/docs/Field.md +143 -0
- package/dist/docs/Heading.md +69 -0
- package/dist/docs/Icon.md +116 -0
- package/dist/docs/IconButton.md +83 -0
- package/dist/docs/Inline.md +79 -0
- package/dist/docs/Menu.md +181 -0
- package/dist/docs/MultiSelect.md +120 -0
- package/dist/docs/SearchField.md +127 -0
- package/dist/docs/Stack.md +79 -0
- package/dist/docs/Tabs.md +130 -0
- package/dist/docs/Tag.md +97 -0
- package/dist/docs/Text.md +90 -0
- package/dist/docs/Toast.md +115 -0
- package/dist/docs/llms.txt +33 -0
- package/dist/docs/manifest.json +2649 -0
- package/dist/docs/manifest.schema.json +96 -0
- package/dist/docs/migrations/README.md +27 -0
- package/dist/docs/theming.md +59 -0
- package/dist/icons/ArrowLeftIcon.d.mts +4 -0
- package/dist/icons/ArrowLeftIcon.mjs +11 -0
- package/dist/icons/ArrowRightIcon.d.mts +4 -0
- package/dist/icons/ArrowRightIcon.mjs +11 -0
- package/dist/icons/CheckIcon.d.mts +4 -0
- package/dist/icons/CheckIcon.mjs +11 -0
- package/dist/icons/ChevronDownIcon.d.mts +4 -0
- package/dist/icons/ChevronDownIcon.mjs +11 -0
- package/dist/icons/ChevronRightIcon.d.mts +4 -0
- package/dist/icons/ChevronRightIcon.mjs +11 -0
- package/dist/icons/CircleAlertIcon.d.mts +4 -0
- package/dist/icons/CircleAlertIcon.mjs +15 -0
- package/dist/icons/CircleCheckIcon.d.mts +4 -0
- package/dist/icons/CircleCheckIcon.mjs +15 -0
- package/dist/icons/CircleDotIcon.d.mts +4 -0
- package/dist/icons/CircleDotIcon.mjs +19 -0
- package/dist/icons/CircleSlashIcon.d.mts +4 -0
- package/dist/icons/CircleSlashIcon.mjs +15 -0
- package/dist/icons/GitMergeIcon.d.mts +4 -0
- package/dist/icons/GitMergeIcon.mjs +23 -0
- package/dist/icons/GitPullRequestClosedIcon.d.mts +4 -0
- package/dist/icons/GitPullRequestClosedIcon.mjs +23 -0
- package/dist/icons/GitPullRequestDraftIcon.d.mts +4 -0
- package/dist/icons/GitPullRequestDraftIcon.mjs +23 -0
- package/dist/icons/GitPullRequestIcon.d.mts +4 -0
- package/dist/icons/GitPullRequestIcon.mjs +23 -0
- package/dist/icons/InboxIcon.d.mts +4 -0
- package/dist/icons/InboxIcon.mjs +11 -0
- package/dist/icons/InfoIcon.d.mts +4 -0
- package/dist/icons/InfoIcon.mjs +15 -0
- package/dist/icons/LoaderCircleIcon.d.mts +4 -0
- package/dist/icons/LoaderCircleIcon.mjs +11 -0
- package/dist/icons/RefreshCwIcon.d.mts +4 -0
- package/dist/icons/RefreshCwIcon.mjs +15 -0
- package/dist/icons/SearchIcon.d.mts +4 -0
- package/dist/icons/SearchIcon.mjs +15 -0
- package/dist/icons/SettingsIcon.d.mts +4 -0
- package/dist/icons/SettingsIcon.mjs +15 -0
- package/dist/icons/XIcon.d.mts +4 -0
- package/dist/icons/XIcon.mjs +11 -0
- package/dist/icons/index.d.mts +25 -0
- package/dist/icons/index.mjs +45 -0
- package/dist/index.d.mts +28 -0
- package/dist/index.mjs +25 -0
- package/dist/popup/client.d.mts +9 -0
- package/dist/popup/client.mjs +31 -0
- package/dist/styles/atoms.css +2 -0
- package/dist/styles/atoms.css.d.mts +219 -0
- package/dist/styles/atoms.css.mjs +2669 -0
- package/dist/styles/button-fills.mjs +17 -0
- package/dist/styles/highlight.mjs +8 -0
- package/dist/styles/hover-tint.mjs +4 -0
- package/dist/styles/join.mjs +4 -0
- package/dist/styles/layers.css +2 -0
- package/dist/styles/part-props.d.mts +8 -0
- package/dist/styles/split-style-props.mjs +9 -0
- package/dist/testing/index.d.mts +4 -0
- package/dist/testing/index.mjs +3 -0
- package/dist/testing/motion.d.mts +22 -0
- package/dist/testing/motion.mjs +49 -0
- package/dist/theme/FontCheck.mjs +11 -0
- package/dist/theme/ThemeProvider.d.mts +15 -0
- package/dist/theme/ThemeProvider.mjs +23 -0
- package/dist/theme/define-theme.d.mts +9 -0
- package/dist/theme/define-theme.mjs +84 -0
- package/dist/theme/use-theme-container.mjs +15 -0
- package/dist/tokens/contract.d.mts +110 -0
- package/dist/tokens/contract.mjs +17 -0
- package/dist/tokens/index.d.mts +2 -0
- package/dist/tokens/index.mjs +1 -0
- package/dist/tokens/roles.d.mts +6 -0
- package/dist/tokens/roles.mjs +24 -0
- package/dist/tokens/scales.d.mts +100 -0
- package/dist/tokens/scales.mjs +103 -0
- package/dist/types.d.mts +7 -0
- package/package.json +159 -3
- package/README.md +0 -3
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Dialog
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
A modal panel over the page for one short task: edit a name, fill a small form, read details that need a decision. Built on Base UI's Dialog, which owns the behavior: focus moves into the dialog and stays there, the page behind is inert and does not scroll, Esc and a click on the backdrop close it, and focus returns to the trigger. The parts add the look: a dimmed backdrop, a raised panel with a title, and a zoom-and-fade that is a plain fade under reduced motion.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- A task that needs the user's full attention before they go on, and fits on one screen.
|
|
10
|
+
- A form too small for its own page (rename, invite one person, change a setting).
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- Confirming something destructive or irreversible: use `AlertDialog`, which focuses Cancel and ignores outside clicks.
|
|
15
|
+
- A list of actions: use `Menu`.
|
|
16
|
+
- Content the user wants beside the page, or a hint: use a popover or inline text; a modal blocks the page.
|
|
17
|
+
- A long flow or a lot of content: give it a page.
|
|
18
|
+
- A status message or the result of an action: use `Toast`, which leaves focus where it is; a dialog takes it.
|
|
19
|
+
|
|
20
|
+
## Rules
|
|
21
|
+
|
|
22
|
+
- Compose the parts: `Dialog` > `Dialog.Trigger` + `Dialog.Popup` > `Dialog.Body` + `Dialog.Actions` > `Dialog.Close`. `Dialog` holds the open state and renders no element, so it takes no `className` or `style`; every other part does, plus style props.
|
|
23
|
+
- `Dialog.Popup` takes a required `title` and an optional `description`. They head the panel as an `h2` and muted text, and name and describe the dialog for screen readers. Keep the title short: a noun phrase ("Rename project") or the question asked.
|
|
24
|
+
- `size` (`sm`, `med`, `lg`; `med` by default) sets the width, 384, 512 or 640 px at the default font size; the panel never gets wider than the viewport less a 16 px edge.
|
|
25
|
+
- Put the content in `Dialog.Body`. When the dialog outgrows the viewport the whole popup scrolls; keep the content short so the actions stay close.
|
|
26
|
+
- `Dialog.Actions` end-aligns its buttons and wraps them on a narrow screen. Put `Dialog.Close` first, then one primary `Button`. A dialog with no buttons still closes on Esc and the backdrop, but give it a `Dialog.Close` so a pointer user sees the way out.
|
|
27
|
+
- `Dialog.Trigger` and `Dialog.Close` are bare `Clickable`s. Pass Button's `variant`, `size`, `iconStart` or `iconEnd` for a button look (`variant="secondary"` on Close); they render a `Button` with its fill, border and height, and keep the part's behavior and `aria-disabled`. Without them, style them with style props and give them an accessible name.
|
|
28
|
+
- `disabled` on the trigger keeps it focusable and announced, and stops it opening.
|
|
29
|
+
- Control it with `open` and `onOpenChange` to close it after a save or keep it open on an error. `onOpenChangeComplete` runs once the exit transition ends.
|
|
30
|
+
- The popup portals into the trigger's nearest `ThemeProvider`, so it keeps the brand and mode. That provider must not clip or stack its content: wrap the app root, and keep a nested provider off elements with `overflow: hidden`, a `transform` or their own `z-index`. It sits on `zIndex` `modal`. A dialog opened only through `open`, with no `Dialog.Trigger`, has no provider to find and portals to `<body>`.
|
|
31
|
+
- It is always modal. Never add key handlers, `tabIndex` or your own focus trap to the parts.
|
|
32
|
+
- The module has no hooks, so a server component can render the parts; `onOpenChange` and other functions must come from a client component.
|
|
33
|
+
|
|
34
|
+
## Do
|
|
35
|
+
|
|
36
|
+
Field has no input of its own yet (ADR 0026); Base UI's `Field.Control` stands in.
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<Dialog>
|
|
40
|
+
<Dialog.Trigger variant="secondary">Rename</Dialog.Trigger>
|
|
41
|
+
<Dialog.Popup
|
|
42
|
+
title="Rename project"
|
|
43
|
+
description="The new name shows everywhere the project does."
|
|
44
|
+
>
|
|
45
|
+
<Dialog.Body>
|
|
46
|
+
<Field name="name">
|
|
47
|
+
<Field.Label>Name</Field.Label>
|
|
48
|
+
<BaseField.Control defaultValue={name} />
|
|
49
|
+
</Field>
|
|
50
|
+
</Dialog.Body>
|
|
51
|
+
<Dialog.Actions>
|
|
52
|
+
<Dialog.Close variant="secondary">Cancel</Dialog.Close>
|
|
53
|
+
<Button onClick={save}>Save</Button>
|
|
54
|
+
</Dialog.Actions>
|
|
55
|
+
</Dialog.Popup>
|
|
56
|
+
</Dialog>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
// Closes once the save succeeds; stays open on an error.
|
|
61
|
+
const [open, setOpen] = useState(false);
|
|
62
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
63
|
+
<Dialog.Trigger variant="primary">Invite</Dialog.Trigger>
|
|
64
|
+
<Dialog.Popup title="Invite a teammate" size="sm">
|
|
65
|
+
…
|
|
66
|
+
</Dialog.Popup>
|
|
67
|
+
</Dialog>;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Don't
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
// Deleting forever needs AlertDialog: Cancel focused first, no dismissal by an outside click.
|
|
74
|
+
<Dialog.Popup title="Delete project?">
|
|
75
|
+
<Dialog.Actions>
|
|
76
|
+
<Button variant="danger" onClick={deleteForever}>
|
|
77
|
+
Delete
|
|
78
|
+
</Button>
|
|
79
|
+
</Dialog.Actions>
|
|
80
|
+
</Dialog.Popup>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// Two primary actions: the user can't tell which one finishes the task.
|
|
85
|
+
<Dialog.Actions>
|
|
86
|
+
<Button>Save</Button>
|
|
87
|
+
<Button>Save and close</Button>
|
|
88
|
+
</Dialog.Actions>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
// A heading in the body instead of `title`: the dialog has no name.
|
|
93
|
+
<Dialog.Popup title="">
|
|
94
|
+
<Heading>Rename project</Heading>
|
|
95
|
+
</Dialog.Popup>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Accessibility
|
|
99
|
+
|
|
100
|
+
- The trigger has `aria-haspopup="dialog"` and `aria-expanded`. The popup is `role="dialog"` with `aria-modal`, labelled by the title and described by the description.
|
|
101
|
+
- Opening moves focus into the popup; Tab and Shift+Tab cycle inside it, and the page behind is inert. Esc or a backdrop click closes it, and focus returns to the trigger.
|
|
102
|
+
- The panel takes the shared focus ring when focused from the keyboard. Under forced colors it keeps its border, so it stays apart from the page.
|
|
103
|
+
- The backdrop is 40% of the shadow color over the page; it only dims, and carries no text.
|
|
104
|
+
|
|
105
|
+
## Props
|
|
106
|
+
|
|
107
|
+
Generated from the TypeScript types.
|
|
108
|
+
|
|
109
|
+
### Dialog
|
|
110
|
+
|
|
111
|
+
| Prop | Type | Required | Description |
|
|
112
|
+
| --- | --- | --- | --- |
|
|
113
|
+
| `children` | `ReactNode` | no | |
|
|
114
|
+
| `defaultOpen` | `boolean` | no | Whether the dialog is initially open. To render a controlled dialog, use the `open` prop instead. |
|
|
115
|
+
| `onOpenChange` | `((open: boolean, eventDetails: DialogRootChangeEventDetails) => void)` | no | Event handler called when the dialog is opened or closed. |
|
|
116
|
+
| `onOpenChangeComplete` | `((open: boolean) => void)` | no | Event handler called after any animations complete when the dialog is opened or closed. |
|
|
117
|
+
| `open` | `boolean` | no | Whether the dialog is currently open. |
|
|
118
|
+
|
|
119
|
+
### Dialog.Actions
|
|
120
|
+
|
|
121
|
+
No props of its own.
|
|
122
|
+
|
|
123
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
|
124
|
+
|
|
125
|
+
### Dialog.Body
|
|
126
|
+
|
|
127
|
+
No props of its own.
|
|
128
|
+
|
|
129
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
|
130
|
+
|
|
131
|
+
### Dialog.Close
|
|
132
|
+
|
|
133
|
+
| Prop | Type | Required | Description |
|
|
134
|
+
| --- | --- | --- | --- |
|
|
135
|
+
| `iconEnd` | `ComponentType` | no | |
|
|
136
|
+
| `iconStart` | `ComponentType` | no | |
|
|
137
|
+
| `size` | `"lg" \| "med" \| "sm"` | no | |
|
|
138
|
+
| `variant` | `"primary" \| "secondary"` | no | |
|
|
139
|
+
|
|
140
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
|
141
|
+
|
|
142
|
+
### Dialog.Popup
|
|
143
|
+
|
|
144
|
+
| Prop | Type | Required | Description |
|
|
145
|
+
| --- | --- | --- | --- |
|
|
146
|
+
| `description` | `ReactNode` | no | |
|
|
147
|
+
| `finalFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => void \| boolean \| HTMLElement \| null)` | no | Determines the element to focus when the dialog is closed. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing. |
|
|
148
|
+
| `initialFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((openType: InteractionType) => void \| boolean \| HTMLElement \| null)` | no | Determines the element to focus when the dialog is opened. By default, focus moves to the first tabbable element inside the popup, except when the dialog is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (first tabbable element or popup). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing. |
|
|
149
|
+
| `size` | `"lg" \| "med" \| "sm"` | no | |
|
|
150
|
+
| `title` | `string \| number \| bigint \| Iterable<ReactNode> \| Promise<AwaitedReactNode> \| ReactElement<unknown, string \| JSXElementConstructor<any>> \| ReactPortal` | yes | |
|
|
151
|
+
|
|
152
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
|
153
|
+
|
|
154
|
+
### Dialog.Trigger
|
|
155
|
+
|
|
156
|
+
| Prop | Type | Required | Description |
|
|
157
|
+
| --- | --- | --- | --- |
|
|
158
|
+
| `handle` | `DialogHandle<unknown>` | no | A handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method. |
|
|
159
|
+
| `iconEnd` | `ComponentType` | no | |
|
|
160
|
+
| `iconStart` | `ComponentType` | no | |
|
|
161
|
+
| `id` | `string` | no | ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot `triggerId` prop). |
|
|
162
|
+
| `payload` | `unknown` | no | A payload to pass to the dialog when it is opened. |
|
|
163
|
+
| `size` | `"lg" \| "med" \| "sm"` | no | |
|
|
164
|
+
| `variant` | `"primary" \| "secondary"` | no | |
|
|
165
|
+
|
|
166
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Field
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Wires one form control to its label, description and error: ids, `htmlFor`, `aria-describedby` and `aria-invalid`. It is input-agnostic: the control inside is Base UI's `Field.Control` or a Base UI control (Checkbox, Combobox), and Base UI does the wiring. The parts are `Field` (the root, a flex column), `Field.Label`, `Field.Description` and `Field.Error`. Built on Base UI `Field` (ADR 0003, 0026, 0046); `@base-ui/react` is a peer dependency, so the app's Base UI controls and Field share one copy of its contexts.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Any form control that needs a visible label, help text or an error, including a control a component builds (Checkbox, MultiSelect build on Field rather than their own wiring).
|
|
10
|
+
- Showing a server validation error from a React Router action next to its control.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- An error about the whole form (a failed save, a network error): put it in a reserved slot at the form level, not on one field.
|
|
15
|
+
- The result of submitting the form ("Profile saved"): use `Toast`; the field's message is for that one input.
|
|
16
|
+
- A group of controls with one legend (a radio group): v0.2 adds `Fieldset`.
|
|
17
|
+
- A styled text input or select: v0.1 has none; they come in v0.2 on this Field.
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
|
|
21
|
+
- One control per Field. Put it between the label and the description; order in the DOM is the reading order.
|
|
22
|
+
- Mark the field with `invalid` whenever you pass an error message; the message alone does not set `aria-invalid`.
|
|
23
|
+
- `Field.Error` always renders a slot one line tall, so a one-line error appears without moving anything. A longer message wraps inside the field (even an unbroken URL) and pushes only what is below. Keep messages to one line where you can.
|
|
24
|
+
- `Field.Error` with children shows them; with `match` set to a validity key (`"valueMissing"`), only when that check fails. Without children, it shows the control's native validation message while the control is invalid.
|
|
25
|
+
- `Field.Label` renders a `label`. Keep it for `Field.Control` and for `Checkbox`, whose hidden input the label points at: clicking it toggles the checkbox, as on a native one. For a Base UI control with no input at all, set `nativeLabel={false}`; it renders a `span` that names the control (`aria-labelledby`); clicking it focuses the control.
|
|
26
|
+
- `disabled` on Field disables the control and mutes the label. A message in `Field.Error` still shows while disabled; clear it if the field is locked.
|
|
27
|
+
- `name`, `validate`, `validationMode`, `dirty` and `touched` pass through to Base UI's `Field.Root`. `validate` and event handlers are functions, so set them from a client component.
|
|
28
|
+
- Every part takes style props; the root's `display`, `flexDirection` and `gap` (`"8"`) are defaults you can replace. The label, description and error also take Text's `size`, `font`, `weight`, `leading` and `truncate` over their defaults (`sm`; the label in the `label` font at `medium`). `className` is a string and `style` an object (not Base UI's state functions), and the consumer's come last.
|
|
29
|
+
- The module has no hooks: a server component can render `Field` and its parts (the Base UI parts inside are client components). `render` is not accepted on the parts.
|
|
30
|
+
- On `Field.Error`, style props, `className` and `style` go to the slot; `id`, `ref` and handlers go to the message, which exists only while it shows.
|
|
31
|
+
|
|
32
|
+
## Do
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { Field as BaseField } from "@base-ui/react/field";
|
|
36
|
+
|
|
37
|
+
<Field name="email" invalid={Boolean(errors?.email)}>
|
|
38
|
+
<Field.Label>Email</Field.Label>
|
|
39
|
+
<BaseField.Control type="email" required defaultValue={values.email} />
|
|
40
|
+
<Field.Description>We only use it to send receipts.</Field.Description>
|
|
41
|
+
<Field.Error>{errors?.email}</Field.Error>
|
|
42
|
+
</Field>;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// Native constraints: no children, so Base UI shows the browser's message.
|
|
47
|
+
<Field name="age">
|
|
48
|
+
<Field.Label>Age</Field.Label>
|
|
49
|
+
<BaseField.Control type="number" min={18} required />
|
|
50
|
+
<Field.Error />
|
|
51
|
+
</Field>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Don't
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
// An error without `invalid`: it shows, but the control is not marked invalid.
|
|
58
|
+
<Field name="email">
|
|
59
|
+
<Field.Label>Email</Field.Label>
|
|
60
|
+
<BaseField.Control />
|
|
61
|
+
<Field.Error>Enter an email address.</Field.Error>
|
|
62
|
+
</Field>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// A bare input outside Base UI gets no ids: the label and messages are not linked.
|
|
67
|
+
<Field>
|
|
68
|
+
<Field.Label>Email</Field.Label>
|
|
69
|
+
<input />
|
|
70
|
+
</Field>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
// Placeholder as the label: it disappears on input and is not a label.
|
|
75
|
+
<Field>
|
|
76
|
+
<BaseField.Control placeholder="Email" />
|
|
77
|
+
</Field>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Accessibility
|
|
81
|
+
|
|
82
|
+
- The label is the control's accessible name; clicking it focuses the control.
|
|
83
|
+
- The description and the error join the control's `aria-describedby`, so they are read with it. Base UI adds them after hydration; server HTML has the label link and `aria-invalid`.
|
|
84
|
+
- The error slot is a `role="alert"` live region that stays in the page, so a message is announced when it appears, also after a client-side submit. Several fields failing at once each announce.
|
|
85
|
+
- The error is never color alone: a decorative alert icon (`aria-hidden`, at the text's size) leads the message, so it reads as an error under forced colors too, and the message says what is wrong and how to fix it.
|
|
86
|
+
|
|
87
|
+
## Props
|
|
88
|
+
|
|
89
|
+
Generated from the TypeScript types.
|
|
90
|
+
|
|
91
|
+
### Field
|
|
92
|
+
|
|
93
|
+
| Prop | Type | Required | Description |
|
|
94
|
+
| --- | --- | --- | --- |
|
|
95
|
+
| `actionsRef` | `RefObject<FieldRootActions \| null>` | no | A ref to imperative actions. - `validate`: Validates the field when called. |
|
|
96
|
+
| `dirty` | `boolean` | no | Whether the field's value has been changed from its initial value. Useful when the field state is controlled by an external library. |
|
|
97
|
+
| `disabled` | `boolean` | no | Whether the component should ignore user interaction. Takes precedence over the `disabled` prop on the `<Field.Control>` component. |
|
|
98
|
+
| `invalid` | `boolean` | no | Whether the field is invalid. Useful when the field state is controlled by an external library. |
|
|
99
|
+
| `name` | `string` | no | Identifies the field when a form is submitted. Takes precedence over the `name` prop on the `<Field.Control>` component. |
|
|
100
|
+
| `touched` | `boolean` | no | Whether the field has been touched. Useful when the field state is controlled by an external library. |
|
|
101
|
+
| `validate` | `((value: unknown, formValues: Record<string, any>) => void \| string \| string[] \| Promise<void \| string \| string[] \| null> \| null)` | no | A function for custom validation. Return a string or an array of strings with the error message(s) if the value is invalid. Returning nothing, `null`, an empty string, or an empty array means the value is valid. Asynchronous functions are supported, but they do not prevent form submission when using `validationMode="onSubmit"`. |
|
|
102
|
+
| `validationDebounceTime` | `number` | no | How long to wait between `validate` callbacks if `validationMode="onChange"` is used. Specified in milliseconds. |
|
|
103
|
+
| `validationMode` | `"onBlur" \| "onChange" \| "onSubmit"` | no | Determines when the field should be validated. This takes precedence over the `validationMode` prop on `<Form>`. - `onSubmit`: triggers validation when the form is submitted, and re-validates on change after submission. - `onBlur`: triggers validation when the control loses focus. - `onChange`: triggers validation on every change to the control value. |
|
|
104
|
+
|
|
105
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
|
106
|
+
|
|
107
|
+
### Field.Description
|
|
108
|
+
|
|
109
|
+
| Prop | Type | Required | Description |
|
|
110
|
+
| --- | --- | --- | --- |
|
|
111
|
+
| `font` | `"body" \| "display" \| "heading" \| "label" \| "mono"` | no | |
|
|
112
|
+
| `leading` | `"lg" \| "med" \| "sm"` | no | |
|
|
113
|
+
| `size` | `"2xl" \| "3xl" \| "4xl" \| "lg" \| "med" \| "sm" \| "xl" \| "xs"` | no | |
|
|
114
|
+
| `truncate` | `boolean` | no | |
|
|
115
|
+
| `weight` | `"bold" \| "medium" \| "regular" \| "semibold"` | no | |
|
|
116
|
+
|
|
117
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
|
118
|
+
|
|
119
|
+
### Field.Error
|
|
120
|
+
|
|
121
|
+
| Prop | Type | Required | Description |
|
|
122
|
+
| --- | --- | --- | --- |
|
|
123
|
+
| `font` | `"body" \| "display" \| "heading" \| "label" \| "mono"` | no | |
|
|
124
|
+
| `leading` | `"lg" \| "med" \| "sm"` | no | |
|
|
125
|
+
| `match` | `boolean \| keyof ValidityState` | no | Determines whether to show the error message according to the field's [ValidityState](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState). Specifying `true` will always show the error message, and lets external libraries control the visibility. |
|
|
126
|
+
| `size` | `"2xl" \| "3xl" \| "4xl" \| "lg" \| "med" \| "sm" \| "xl" \| "xs"` | no | |
|
|
127
|
+
| `truncate` | `boolean` | no | |
|
|
128
|
+
| `weight` | `"bold" \| "medium" \| "regular" \| "semibold"` | no | |
|
|
129
|
+
|
|
130
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
|
131
|
+
|
|
132
|
+
### Field.Label
|
|
133
|
+
|
|
134
|
+
| Prop | Type | Required | Description |
|
|
135
|
+
| --- | --- | --- | --- |
|
|
136
|
+
| `font` | `"body" \| "display" \| "heading" \| "label" \| "mono"` | no | |
|
|
137
|
+
| `leading` | `"lg" \| "med" \| "sm"` | no | |
|
|
138
|
+
| `nativeLabel` | `boolean` | no | Whether the component renders a native `<label>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a label (for example, `<div>`). This is useful to avoid inheriting label behaviors on `<button>` controls (such as `<Select.Trigger>` and `<Combobox.Trigger>`), including avoiding `:hover` on the button when hovering the label, and preventing clicks on the label from firing on the button. |
|
|
139
|
+
| `size` | `"2xl" \| "3xl" \| "4xl" \| "lg" \| "med" \| "sm" \| "xl" \| "xs"` | no | |
|
|
140
|
+
| `truncate` | `boolean` | no | |
|
|
141
|
+
| `weight` | `"bold" \| "medium" \| "regular" \| "semibold"` | no | |
|
|
142
|
+
|
|
143
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Heading
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Section and page headings. A `Text` with heading defaults: the `heading` font role, `semibold`, `sm` leading, and balanced line wrapping. The level (`as`) and the look (`size`) are separate, so the outline stays right whatever the design calls for.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Any heading in the document outline: page title, section, card, dialog title.
|
|
10
|
+
|
|
11
|
+
## When not to use
|
|
12
|
+
|
|
13
|
+
- Large text that is not a heading (a stat, a hero tagline): use `Text` with `size` and `font="display"`.
|
|
14
|
+
- Bold body text: use `Text` with `weight`.
|
|
15
|
+
|
|
16
|
+
## Rules
|
|
17
|
+
|
|
18
|
+
- `as` is the level, `h1` to `h6`; the default is `h2`. Pick it from the outline, not the look.
|
|
19
|
+
- `size` is the look: `sm`, `med`, `lg`, `xl` or `2xl`. Unset, it follows the level: `h1` `2xl`, `h2` `xl`, `h3` `lg`, `h4` `med`, `h5` and `h6` `sm`.
|
|
20
|
+
- Heading takes every other Text prop (`font`, `weight`, `leading`, `color`, `truncate`, style props) and overrides its defaults with them.
|
|
21
|
+
- One `h1` per page; do not skip levels going down.
|
|
22
|
+
|
|
23
|
+
## Do
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
<Heading as="h1">Settings</Heading>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
// A dialog title in a page's outline is an h2, but looks smaller.
|
|
31
|
+
<Heading as="h2" size="lg">
|
|
32
|
+
Delete project?
|
|
33
|
+
</Heading>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Don't
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
// A level picked for its look breaks the outline; set `size` instead.
|
|
40
|
+
<Heading as="h4">Section</Heading>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// Headings stop at 2xl; for display type use Text.
|
|
45
|
+
<Heading size="4xl" />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Accessibility
|
|
49
|
+
|
|
50
|
+
- The element is the level screen reader users navigate by; keep levels in order.
|
|
51
|
+
- A heading that only looks like one is not one: never fake a heading with `Text weight="bold"`.
|
|
52
|
+
- Truncated headings lose words for everyone; prefer wrapping.
|
|
53
|
+
|
|
54
|
+
## Props
|
|
55
|
+
|
|
56
|
+
Generated from the TypeScript types.
|
|
57
|
+
|
|
58
|
+
### Heading
|
|
59
|
+
|
|
60
|
+
| Prop | Type | Required | Description |
|
|
61
|
+
| --- | --- | --- | --- |
|
|
62
|
+
| `as` | `"h1" \| "h2" \| "h3" \| "h4" \| "h5" \| "h6"` | no | |
|
|
63
|
+
| `font` | `"body" \| "display" \| "heading" \| "label" \| "mono"` | no | |
|
|
64
|
+
| `leading` | `"lg" \| "med" \| "sm"` | no | |
|
|
65
|
+
| `size` | `"2xl" \| "lg" \| "med" \| "sm" \| "xl"` | no | |
|
|
66
|
+
| `truncate` | `boolean` | no | |
|
|
67
|
+
| `weight` | `"bold" \| "medium" \| "regular" \| "semibold"` | no | |
|
|
68
|
+
|
|
69
|
+
Also accepts every `<h2>` attribute (change the element with `as`) and the style props (see `manifest.json` `styleProps`).
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Icon
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The base for every icon: an `<svg>` on a 24-unit grid that draws its children with round 2-unit strokes in `currentColor`. Generated icons from `@jonmilner/ui/icons` (`ArrowRightIcon`, …) wrap it and take the same props, less `children` and `viewBox`. `size` takes a `fontSize` step, so an icon matches the `Text` beside it; `color` takes a color role.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- A one-off glyph the icon set lacks: pass its shapes as children.
|
|
10
|
+
- Building a component with an icon slot: render the consumer's icon, or an `Icon`, at the `size` of the component's text.
|
|
11
|
+
- Prefer a generated `<Name>Icon` from `@jonmilner/ui/icons` when one exists.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- Images, illustrations and logos with their own colors: use `img` or a dedicated component.
|
|
16
|
+
- An icon-only control: use `IconButton`, which takes the accessible name.
|
|
17
|
+
- An icon that is the only content of a link or button: name the control, not the icon.
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
|
|
21
|
+
- `size` is a `fontSize` step (`xs`–`4xl`), `med` by default; use the step of the text it sits beside. The middle step is `med`, never `md`.
|
|
22
|
+
- `color` is a color role (`fg`, `fgMuted`, `accent`, `danger`, …). Unset, the icon takes the surrounding text color.
|
|
23
|
+
- Shapes inherit `stroke="currentColor"` and `fill="none"`. A filled shape sets `fill="currentColor"`; a fill-only icon passes `stroke="none"`.
|
|
24
|
+
- `viewBox` defaults to `0 0 24 24`; pass another for shapes drawn on a different grid.
|
|
25
|
+
- `label` names the icon. Without a name the icon is decorative and `aria-hidden`; `aria-label` and `aria-labelledby` also name it, and `label` wins over both. Name it with these, not a `<title>` child, which stays hidden.
|
|
26
|
+
- `size` sets the box; there is no `width` or `height`. Use `style` or an unlayered class for a size off the scale.
|
|
27
|
+
- The consumer's `className` comes last, and `style` and other svg attributes pass through; `role`, `aria-hidden` and the name are always set from the rule above.
|
|
28
|
+
- It is a block box that doesn't shrink: place it beside text with `Inline`, not inside a `Text` run.
|
|
29
|
+
- `ref` is a plain prop for the `<svg>`. No hooks or context, so it renders in a server component.
|
|
30
|
+
|
|
31
|
+
## Do
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<Inline gap="8">
|
|
35
|
+
<ArrowRightIcon size="sm" color="fgMuted" />
|
|
36
|
+
<Text size="sm" color="fgMuted">
|
|
37
|
+
Continue
|
|
38
|
+
</Text>
|
|
39
|
+
</Inline>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<Icon label="Warning" color="danger">
|
|
44
|
+
<path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" />
|
|
45
|
+
</Icon>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Don't
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
// Raw colors and pixel sizes bypass the tokens; neither type-checks.
|
|
52
|
+
<Icon color="#c00" size="20px" />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// The icon is hidden, so this button has no name.
|
|
57
|
+
<button>
|
|
58
|
+
<XIcon />
|
|
59
|
+
</button>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// Naming the icon inside a labelled control announces the name twice.
|
|
64
|
+
<Button>
|
|
65
|
+
<SaveIcon label="Save" /> Save
|
|
66
|
+
</Button>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Icon set
|
|
70
|
+
|
|
71
|
+
Generated from `src/icons/svg/` and exported from `@jonmilner/ui/icons`; each takes `Icon`'s props except `children` and `viewBox`. Lucide icons keep their Lucide name.
|
|
72
|
+
|
|
73
|
+
<!-- icons:start -->
|
|
74
|
+
|
|
75
|
+
- `ArrowLeftIcon` (`arrow-left.svg`)
|
|
76
|
+
- `ArrowRightIcon` (`arrow-right.svg`)
|
|
77
|
+
- `CheckIcon` (`check.svg`)
|
|
78
|
+
- `ChevronDownIcon` (`chevron-down.svg`)
|
|
79
|
+
- `ChevronRightIcon` (`chevron-right.svg`)
|
|
80
|
+
- `CircleAlertIcon` (`circle-alert.svg`)
|
|
81
|
+
- `CircleCheckIcon` (`circle-check.svg`)
|
|
82
|
+
- `CircleDotIcon` (`circle-dot.svg`)
|
|
83
|
+
- `CircleSlashIcon` (`circle-slash.svg`)
|
|
84
|
+
- `GitMergeIcon` (`git-merge.svg`)
|
|
85
|
+
- `GitPullRequestClosedIcon` (`git-pull-request-closed.svg`)
|
|
86
|
+
- `GitPullRequestDraftIcon` (`git-pull-request-draft.svg`)
|
|
87
|
+
- `GitPullRequestIcon` (`git-pull-request.svg`)
|
|
88
|
+
- `InboxIcon` (`inbox.svg`)
|
|
89
|
+
- `InfoIcon` (`info.svg`)
|
|
90
|
+
- `LoaderCircleIcon` (`loader-circle.svg`)
|
|
91
|
+
- `RefreshCwIcon` (`refresh-cw.svg`)
|
|
92
|
+
- `SearchIcon` (`search.svg`)
|
|
93
|
+
- `SettingsIcon` (`settings.svg`)
|
|
94
|
+
- `XIcon` (`x.svg`)
|
|
95
|
+
|
|
96
|
+
<!-- icons:end -->
|
|
97
|
+
|
|
98
|
+
## Accessibility
|
|
99
|
+
|
|
100
|
+
- Decorative by default: `aria-hidden="true"` and no role, so text beside it carries the meaning.
|
|
101
|
+
- With `label`, it is `role="img"` with that `aria-label`. Use it when the icon alone conveys something, such as a status next to a number.
|
|
102
|
+
- Do not convey state with icon color alone; pair it with a shape change or text.
|
|
103
|
+
- Under forced colors `currentColor` follows the system text color, so the icon stays visible.
|
|
104
|
+
|
|
105
|
+
## Props
|
|
106
|
+
|
|
107
|
+
Generated from the TypeScript types.
|
|
108
|
+
|
|
109
|
+
### Icon
|
|
110
|
+
|
|
111
|
+
| Prop | Type | Required | Description |
|
|
112
|
+
| --- | --- | --- | --- |
|
|
113
|
+
| `color` | `"accent" \| "accentFg" \| "bg" \| "bgMuted" \| "bgRaised" \| "border" \| "borderStrong" \| "danger" \| "dangerFg" \| "fg" \| "fgMuted" \| "focus"` | no | |
|
|
114
|
+
| `label` | `string` | no | |
|
|
115
|
+
| `size` | `"2xl" \| "3xl" \| "4xl" \| "lg" \| "med" \| "sm" \| "xl" \| "xs"` | no | |
|
|
116
|
+
| `strokeWidth` | `number` | no | |
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# IconButton
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
A compact button whose only visible content is an icon, for an action the icon makes plain: close, edit, refresh. A required `label` names it for assistive technology (`aria-label`); the icon is decorative. It is a `<button type="button">` on `Clickable`, so it has the shared focus ring, the 24px minimum target and `aria-disabled` that blocks activation while the button stays focusable. Two variants from Button's fills (`primary`, `secondary`) and three square sizes at Button's heights (`sm`, `med`, `lg`: 32, 40 and 48 px).
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- A frequent action whose icon is widely understood: close a dialog or panel, edit a row, refresh a list.
|
|
10
|
+
- A dense toolbar or a table row where a labelled `Button` would not fit.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- Space allows a label, or the icon is not universally understood: use `Button`, with `iconStart` or `iconEnd` if the icon helps.
|
|
15
|
+
- A toggle that stays pressed (bold, mute) or a filter choice: use `Chip`, or a pressed icon toggle when it lands; IconButton is not a toggle.
|
|
16
|
+
- Opening a menu: use `Menu.Trigger`; an icon-only trigger is planned separately.
|
|
17
|
+
- A pressable area with its own layout, such as a whole card: use `Clickable`.
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
|
|
21
|
+
- `label` is required and is the accessible name: say what the button does ("Close dialog", "Refresh results"), not what the icon shows ("X", "Arrows"). Never pass an empty string: the button would have no name. `aria-label`, `aria-labelledby` and children are not props; a raw one is ignored.
|
|
22
|
+
- `icon` takes an icon component (a generated `XIcon`, or a component that renders `Icon`), not an element: IconButton renders it at the `fontSize` step of its `size`, `aria-hidden`.
|
|
23
|
+
- `variant`: `secondary` (default) for most actions; `primary` for the one action a view or dialog exists for. There is no `danger` in v0.1: a destructive action deserves a labelled `Button`.
|
|
24
|
+
- `size`: `sm`, `med` (default) or `lg`, never `md`. Each is a square of Button's height of the same name, so an IconButton lines up with a Button beside it.
|
|
25
|
+
- `disabled` renders `aria-disabled="true"` and a dashed border, never the native `disabled`. There is no `loading` and no tooltip in v0.1.
|
|
26
|
+
- With `aria-expanded="true"`, as a Dialog trigger sets it, it holds the hover tint and an inset ring while the popup is open, as Button does.
|
|
27
|
+
- Fill (`backgroundColor`), text color (`color`), width (`minInlineSize`), height (`minBlockSize`) and `borderRadius` are default style props: pass one to change it. Change width and height together to keep the square. Hover tints whatever the fill is; a custom fill needs an icon color that passes 3:1 on it (4.5:1 to match Button).
|
|
28
|
+
- `as="a"` with `href` for an icon-only link.
|
|
29
|
+
- Server-safe: IconButton has no hooks; `Clickable` inside it is the client boundary.
|
|
30
|
+
|
|
31
|
+
## Do
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<IconButton label="Close dialog" icon={XIcon} onClick={close} />
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<Inline gap="8" alignItems="center">
|
|
39
|
+
<Button>Export</Button>
|
|
40
|
+
<IconButton label="Refresh results" icon={RefreshCwIcon} />
|
|
41
|
+
</Inline>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Don't
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
// The label names the icon, not the action.
|
|
48
|
+
<IconButton label="X" icon={XIcon} />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// An element: IconButton cannot size it to its step.
|
|
53
|
+
<IconButton label="Close" icon={<XIcon />} />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
// An obscure icon with no visible text: use a labelled Button.
|
|
58
|
+
<IconButton label="Reconcile accounts" icon={GitMergeIcon} />
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Accessibility
|
|
62
|
+
|
|
63
|
+
- The `label` is announced as the button's name; the icon is hidden from assistive technology.
|
|
64
|
+
- The label is not shown on hover or focus. Sighted users who don't recognise the icon get no text, so keep IconButton to widely understood icons.
|
|
65
|
+
- A disabled IconButton stays in the tab order, and Enter and Space do nothing. State is never color alone: disabled adds a dashed border.
|
|
66
|
+
- Every size is at least 24 by 24 CSS px (WCAG 2.2 target size); the icon on each variant passes 4.5:1 at rest and on hover in light and dark modes. Under forced colors it keeps Button's system colors.
|
|
67
|
+
|
|
68
|
+
## Props
|
|
69
|
+
|
|
70
|
+
Generated from the TypeScript types.
|
|
71
|
+
|
|
72
|
+
### IconButton
|
|
73
|
+
|
|
74
|
+
| Prop | Type | Required | Description |
|
|
75
|
+
| --- | --- | --- | --- |
|
|
76
|
+
| `as` | `ElementType` | no | |
|
|
77
|
+
| `disabled` | `boolean` | no | |
|
|
78
|
+
| `icon` | `ComponentType` | yes | |
|
|
79
|
+
| `label` | `string` | yes | |
|
|
80
|
+
| `size` | `"lg" \| "med" \| "sm"` | no | |
|
|
81
|
+
| `variant` | `"primary" \| "secondary"` | no | |
|
|
82
|
+
|
|
83
|
+
Also accepts every `<button>` attribute (change the element with `as`) and the style props (see `manifest.json` `styleProps`).
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Inline
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
A horizontal row of children that wraps onto new lines, with even space between them on both axes. `Inline` is a `Box` with `display="flex"`, `flexDirection="row"`, `flexWrap="wrap"` and `alignItems="center"` as default style props, spaced by a required `gap`. Children render as they are: no wrapper per child.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- A group of buttons, tags, pills or badges: `<Inline gap="8">`.
|
|
10
|
+
- Metadata on one line that wraps when narrow: author, date, read time.
|
|
11
|
+
- An icon beside a label, centered on the cross axis.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- A vertical column: use `Stack`.
|
|
16
|
+
- A row that must never wrap and splits space between ends (a toolbar, a header bar): use `Box` with `display="flex"` and `justifyContent="space-between"`, or pass `flexWrap="nowrap"`.
|
|
17
|
+
- A two-dimensional layout: use `Box` with `display="grid"`.
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
|
|
21
|
+
- `gap` is required and takes spacing tokens only: px keys (`"8"`) or a responsive object (`{ base: "4", med: "8" }`). The same gap applies between items and between wrapped lines.
|
|
22
|
+
- Space comes from `gap`, never from margins on the children.
|
|
23
|
+
- It takes every `Box` prop, and the layout defaults are style props, so `flexWrap`, `alignItems` and `justifyContent` override them.
|
|
24
|
+
- Children are not wrapped, so each child is a flex item.
|
|
25
|
+
- The consumer's `className` comes last and `style` is passed through; `ref` is a plain prop typed for the `as` element.
|
|
26
|
+
|
|
27
|
+
## Do
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
<Inline gap="8">
|
|
31
|
+
<Button variant="primary">Save</Button>
|
|
32
|
+
<Button>Cancel</Button>
|
|
33
|
+
</Inline>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
<Inline as="ul" gap="4" aria-label="Tags">
|
|
38
|
+
<li>…</li>
|
|
39
|
+
</Inline>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Don't
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// Margins between items instead of gap; and `gap` is required.
|
|
46
|
+
<Inline>
|
|
47
|
+
<Pill>One</Pill>
|
|
48
|
+
<Box marginInlineStart="8">
|
|
49
|
+
<Pill>Two</Pill>
|
|
50
|
+
</Box>
|
|
51
|
+
</Inline>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// Reversing a row reorders it visually but not for keyboard or screen reader users.
|
|
56
|
+
<Inline gap="8" flexDirection="row-reverse">
|
|
57
|
+
<Button>Next</Button>
|
|
58
|
+
<Button>Back</Button>
|
|
59
|
+
</Inline>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Accessibility
|
|
63
|
+
|
|
64
|
+
- Inline adds no role or ARIA. Pick the element with `as`: `ul` with `li` children for a list of tags or links.
|
|
65
|
+
- Visual order is source order; do not reorder meaningful content with `row-reverse`, since reading and focus order follow the source.
|
|
66
|
+
- Wrapping keeps the row usable at 400% zoom and narrow widths; keep `flexWrap="wrap"` unless the content is guaranteed to fit.
|
|
67
|
+
|
|
68
|
+
## Props
|
|
69
|
+
|
|
70
|
+
Generated from the TypeScript types.
|
|
71
|
+
|
|
72
|
+
### Inline
|
|
73
|
+
|
|
74
|
+
| Prop | Type | Required | Description |
|
|
75
|
+
| --- | --- | --- | --- |
|
|
76
|
+
| `as` | `ElementType` | no | |
|
|
77
|
+
| `gap` | `"0" \| "1" \| "2" \| "4" \| "6" \| "8" \| "12" \| "16" \| "20" \| "24" \| "32" \| "40" \| "48" \| "64" \| "80" \| "96"` (responsive) | yes | |
|
|
78
|
+
|
|
79
|
+
Also accepts every `<div>` attribute (change the element with `as`) and the style props (see `manifest.json` `styleProps`).
|