@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,76 @@
|
|
|
1
|
+
# Badge
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
A static label that states the status of, or a short fact about, the thing beside it: "Draft", "Failed", "Beta". A `span` with a fill from the color roles around a `Text` in the `label` font. It has no role and is never focusable.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- The status of a row, card or page title: "Draft", "Published", "Failed".
|
|
10
|
+
- Short metadata next to a name: "Beta", "Admin", "v2".
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- A category or topic label on content: use `Tag` (#101).
|
|
15
|
+
- A filter or choice the user toggles: use `Chip` (#102).
|
|
16
|
+
- Anything pressable: compose `Clickable`; Badge has no click, hover or focus state.
|
|
17
|
+
- A count or dot on another element (unread messages on an icon): not Badge's job.
|
|
18
|
+
- A sentence or a message: use `Text`, or a field's error on the field.
|
|
19
|
+
- The brief result of an action ("Saved"): use `Toast`; a badge stays put and says nothing new.
|
|
20
|
+
|
|
21
|
+
## Rules
|
|
22
|
+
|
|
23
|
+
- `variant`: `neutral` (default, `fgMuted` on `bgMuted`) for most statuses and facts; `accent` (`accentFg` on `accent`) to draw the eye; `danger` (`dangerFg` on `danger`) for a failure or a blocked state. `success` and `warning` arrive with their color roles.
|
|
24
|
+
- `size`: `sm` or `med` (default), the label's `fontSize` step.
|
|
25
|
+
- The label states the state. Color is never the only cue (WCAG 1.4.1): under forced colors every variant is the same outlined box, and some readers cannot tell the fills apart. Write "Failed", not a red badge reading "3".
|
|
26
|
+
- Keep it to one or two words; it never wraps. With no children it renders nothing.
|
|
27
|
+
- Fill, text color, padding, radius and `display` (`inline-block`) are default style props on the outer `span`; a consumer's win. `className`, `style` and `ref` go to it, `className` last. The box holds inside a `Text` or `Heading` too.
|
|
28
|
+
- The module has no hooks, so a server component can render it.
|
|
29
|
+
- Migrating from the inbox's `Pill`: `Pill tone=…` is `Badge variant=…`; `pillLink` has no Badge equivalent (compose `Clickable`).
|
|
30
|
+
|
|
31
|
+
## Do
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<Inline gap="8" alignItems="center">
|
|
35
|
+
<Heading as="h2">Quarterly report</Heading>
|
|
36
|
+
<Badge>Draft</Badge>
|
|
37
|
+
</Inline>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<Badge variant="danger" size="sm">
|
|
42
|
+
Failed
|
|
43
|
+
</Badge>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Don't
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
// Color alone carries the state; under forced colors this is an outlined "Build".
|
|
50
|
+
<Badge variant="danger">Build</Badge>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
// A badge is not a button; compose Clickable for anything pressable.
|
|
55
|
+
<Badge onClick={retry}>Retry</Badge>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Accessibility
|
|
59
|
+
|
|
60
|
+
- It is plain text in a `span`: screen readers read the label in line with what is around it, so the label must make sense there ("Quarterly report Draft").
|
|
61
|
+
- No role, no `aria-live`: a badge that changes is not announced. If a status change matters right away, announce it elsewhere (a toast).
|
|
62
|
+
- Each variant's text and fill pass AA (4.5:1) in light and dark mode; the pairs are in `contrastPairs` (ADR 0033).
|
|
63
|
+
- Under forced colors every badge is `CanvasText` on `Canvas` with a `CanvasText` outline, so the box survives and the variants look alike; the label carries the meaning. The role fills are not kept: `accent` would take the link color, on something that is not a link, and the browser's backplate would hide the filled variants' text.
|
|
64
|
+
|
|
65
|
+
## Props
|
|
66
|
+
|
|
67
|
+
Generated from the TypeScript types.
|
|
68
|
+
|
|
69
|
+
### Badge
|
|
70
|
+
|
|
71
|
+
| Prop | Type | Required | Description |
|
|
72
|
+
| --- | --- | --- | --- |
|
|
73
|
+
| `size` | `"med" \| "sm"` | no | |
|
|
74
|
+
| `variant` | `"accent" \| "danger" \| "neutral"` | no | |
|
|
75
|
+
|
|
76
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
package/dist/docs/Box.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Box
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The base element every component builds on: one element, chosen with `as`, styled with style props. Style props are the token-backed classes from `@layer atoms`: layout (`display`, flex, `gap`, logical padding, margin and sizing), color roles, `borderRadius`, `boxShadow`, `position`, `zIndex`, `overflow` and `textAlign`.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Layout and surfaces that need tokens but no behavior: a padded panel, a flex row, a sticky bar.
|
|
10
|
+
- A semantic element (`as="section"`, `as="nav"`, `as="ul"`) that needs style props.
|
|
11
|
+
- Building a new component: render a `Box` and pass its tunable values as default style props.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- Text: use `Text` or `Heading`; all visible text goes through them.
|
|
16
|
+
- Stacks and rows of children with a gap: use `Stack` or `Inline`.
|
|
17
|
+
- Anything interactive: use `Button` or a link component, which bring focus and states.
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
|
|
21
|
+
- Style props take token keys only. Spacing keys are px values (`"16"`); the middle step is `med`, never `md`.
|
|
22
|
+
- Properties are logical: `paddingInline`, `marginBlockStart`, `inlineSize`. There is no `paddingLeft` or `width`.
|
|
23
|
+
- Sizing keys `xs`, `sm`, `med`, `lg` and `xl` are control heights, 24 to 56px: `maxInlineSize="lg"` is 48px wide. For a page or panel width use a container width, on `inlineSize`, `minInlineSize` and `maxInlineSize` only: `prose` (40rem, a reading column), `page` (64rem) or `wide` (80rem). `full`, `fit`, `min`, `max` and `auto` work on every sizing prop.
|
|
24
|
+
- Only layout props are responsive: `{ base: "none", med: "flex" }` with breakpoints `sm`, `med`, `lg`, `xl`. Color and the rest take one value.
|
|
25
|
+
- There are no hover, focus or active props; states belong in component CSS.
|
|
26
|
+
- `null` and `false` leave a prop unset, so `paddingInline={dense && "8"}` works.
|
|
27
|
+
- The consumer's `className` comes last and `style` is passed through, so consumer values win.
|
|
28
|
+
- `ref` is a plain prop and is typed for the `as` element.
|
|
29
|
+
|
|
30
|
+
## Do
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
<Box as="section" display={{ base: "block", med: "flex" }} gap="16" paddingInline="24">
|
|
34
|
+
…
|
|
35
|
+
</Box>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<Box backgroundColor="bgRaised" borderRadius="med" boxShadow="sm" padding="16" />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// A centered page: full width on small screens, at most 64rem on wide ones.
|
|
44
|
+
<Box as="main" maxInlineSize="page" marginInline="auto" padding={{ base: "16", med: "32" }}>
|
|
45
|
+
…
|
|
46
|
+
</Box>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Don't
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// Physical property, `md`: neither type-checks.
|
|
53
|
+
<Box paddingLeft="16" gap="md" />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
// `lg` is a control height: this page is 48px wide.
|
|
58
|
+
<Box as="main" maxInlineSize="lg" marginInline="auto" />
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// `style` is not checked: raw values here bypass the tokens.
|
|
63
|
+
<Box style={{ color: "#333" }} />
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// A clickable Box has no focus ring, role or keyboard support.
|
|
68
|
+
<Box onClick={open}>Open</Box>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Accessibility
|
|
72
|
+
|
|
73
|
+
- Box adds no role or ARIA. Pick the element with `as` so the structure is right: `nav`, `main`, `ul`, `section` with a label.
|
|
74
|
+
- `display="none"` hides content from assistive tech as well as from sight.
|
|
75
|
+
- Do not make a Box interactive; use the component built for it.
|
|
76
|
+
|
|
77
|
+
## Props
|
|
78
|
+
|
|
79
|
+
Generated from the TypeScript types.
|
|
80
|
+
|
|
81
|
+
### Box
|
|
82
|
+
|
|
83
|
+
| Prop | Type | Required | Description |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| `as` | `ElementType` | no | |
|
|
86
|
+
|
|
87
|
+
Also accepts every `<div>` attribute (change the element with `as`) and the style props (see `manifest.json` `styleProps`).
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Button
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The labelled action: a `<button type="button">` with the house look, or a link styled as one with `as="a"` (or a router's link). Three variants (`primary`, `secondary`, `danger`), three sizes (`sm`, `med`, `lg`, 32, 40 and 48 px high), a `loading` state that keeps its width, and icon slots before and after the label. It is built on `Clickable`, so it has the shared focus ring, the 24px minimum target and `aria-disabled` / `aria-busy` states that block activation while the button stays focusable.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Any action a user takes with a labelled control: save, cancel, open a dialog, submit a form (`type="submit"`).
|
|
10
|
+
- A link that should look like an action, such as "Create report" leading to a new page: `as="a"` with `href`.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- An icon with no visible label: use `IconButton`.
|
|
15
|
+
- A link inside running text: use a plain `<a>` (or the link component).
|
|
16
|
+
- A toggle, a choice between options, or a menu trigger: use the component for that widget (Checkbox, Tabs, Menu), which handles its keyboard and state.
|
|
17
|
+
- A one-off pressable area, such as a whole card: use `Clickable`.
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
|
|
21
|
+
- `primary` marks the one action a view or dialog exists for; everything else is `secondary`, the default. One primary per group.
|
|
22
|
+
- `danger` (`dangerFg` on `danger`, ADR 0060) replaces `primary` when that action destroys data or cannot be undone, such as a confirm dialog's "Delete project". It takes `primary`'s place, so a group has one `primary` or one `danger`, not both. The label names the consequence ("Delete project", not "OK"): under forced colors `danger` looks like `primary`, so the word carries the meaning, not the color. Confirm before an irreversible action runs; `danger` does not.
|
|
23
|
+
- Sizes are `sm`, `med` (default) and `lg`, never `md`. The label and the icons take the `fontSize` step of the same name.
|
|
24
|
+
- `loading` renders `aria-busy="true"`, blocks clicks, Enter and Space, and swaps the label for three pulsing dots in the same space: the width never changes, and the label stays the accessible name. A label narrower than the dots (one character, such as a page number) widens by a few pixels; give such buttons a fixed `minInlineSize`. Announce the outcome elsewhere (a toast or a status message); `aria-busy` alone is not announced.
|
|
25
|
+
- `disabled` renders `aria-disabled="true"` and a dashed border, never the native `disabled`: the button stays focusable and announced. Say why it is disabled nearby when it is not obvious.
|
|
26
|
+
- `iconStart` and `iconEnd` take an icon component (a generated `ArrowRightIcon`, or a component that renders `Icon`), not an element: Button renders it at its text size. The icons are decorative; the label names the button.
|
|
27
|
+
- Children are the label, rendered inside `Text`; pass a string, not your own `Text` or `Icon`.
|
|
28
|
+
- Fill (`backgroundColor`), text color (`color`), height (`minBlockSize`), `paddingBlock` (which keeps a wrapped label off the border), `paddingInline`, `borderRadius` and `gap` (between icons and label) are default style props: pass one to change it. Hover tints whatever the fill is (not under forced colors, where hover keeps only the pointer cue); the disabled look is component CSS keyed off `aria-disabled`, so it wins over a custom fill. A custom fill needs a text color that passes 4.5:1 on it.
|
|
29
|
+
- With `aria-expanded="true"` (a Menu or Dialog trigger while its popup is open, or your own disclosure button), a live button holds the hover tint and draws a 1px inset ring in its text color: a shape cue, kept under forced colors, that takes no layout space (ADR 0063). While open, the ring replaces a custom `boxShadow`.
|
|
30
|
+
- `type="submit"` for a form's submit button; otherwise it is `type="button"`.
|
|
31
|
+
- Server-safe: Button has no hooks; `Clickable` inside it is the client boundary.
|
|
32
|
+
|
|
33
|
+
## Do
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<Inline gap="8">
|
|
37
|
+
<Button variant="primary" loading={saving} onClick={save}>
|
|
38
|
+
Save
|
|
39
|
+
</Button>
|
|
40
|
+
<Button onClick={cancel}>Cancel</Button>
|
|
41
|
+
</Inline>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<Button as={RouterLink} to="/reports/new" iconEnd={ArrowRightIcon}>
|
|
46
|
+
Create report
|
|
47
|
+
</Button>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Don't
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// Changing the label while loading moves the layout and the accessible name; `loading` keeps both.
|
|
54
|
+
<Button disabled={saving}>{saving ? "Saving…" : "Save"}</Button>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// An element in the slot: Button cannot size it to the text.
|
|
59
|
+
<Button iconStart={<ArrowLeftIcon />}>Back</Button>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// Red for emphasis on a safe action: `danger` means the action destroys something.
|
|
64
|
+
<Button variant="danger">Publish</Button>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// Two primaries compete; one action leads.
|
|
69
|
+
<Button variant="primary">Save</Button>
|
|
70
|
+
<Button variant="primary">Publish</Button>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Accessibility
|
|
74
|
+
|
|
75
|
+
- A button that goes somewhere is a link (`as="a"` with `href`); one that does something is a button.
|
|
76
|
+
- Disabled and loading buttons stay in the tab order, so keyboard and screen reader users find them; Enter and Space do nothing while either is set.
|
|
77
|
+
- State is never color alone: disabled adds a dashed border, loading shows the dots. Under forced colors the button keeps its system colors (`LinkText` fill for `primary` and `danger`, a `CanvasText` border for `secondary`) and a disabled one is `GrayText`. Hover draws no tint there, so the system pairs keep their contrast.
|
|
78
|
+
- Under reduced motion the dots hold still.
|
|
79
|
+
- Text on every variant passes 4.5:1 at rest and on hover in light and dark modes.
|
|
80
|
+
|
|
81
|
+
## Props
|
|
82
|
+
|
|
83
|
+
Generated from the TypeScript types.
|
|
84
|
+
|
|
85
|
+
### Button
|
|
86
|
+
|
|
87
|
+
| Prop | Type | Required | Description |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| `aria-busy` | `"false" \| "true" \| boolean` | no | |
|
|
90
|
+
| `as` | `ElementType` | no | |
|
|
91
|
+
| `disabled` | `boolean` | no | |
|
|
92
|
+
| `iconEnd` | `ComponentType` | no | |
|
|
93
|
+
| `iconStart` | `ComponentType` | no | |
|
|
94
|
+
| `loading` | `boolean` | no | |
|
|
95
|
+
| `size` | `"lg" \| "med" \| "sm"` | no | |
|
|
96
|
+
| `variant` | `"danger" \| "primary" \| "secondary"` | no | |
|
|
97
|
+
|
|
98
|
+
Also accepts every `<button>` attribute (change the element with `as`) and the style props (see `manifest.json` `styleProps`).
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Checkbox
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
A two-state choice, with a third mixed state for a "select all" over a partly selected list. Built on Base UI `Checkbox` (ADR 0003, 0046): a `span` with `role="checkbox"` and `aria-checked`, plus a hidden `<input type="checkbox">` that submits with the form. The span is a 24 by 24 CSS px target (WCAG 2.2) with the shared focus ring; the 16px box inside fills and shows a tick when checked, a dash when mixed. It composes `Field` for its label, description and error (ADR 0026).
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- An on/off choice that takes effect when the form is submitted ("Accept the terms", "Email me receipts").
|
|
10
|
+
- Selecting rows or items in a list, with an indeterminate "select all".
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- A setting that applies the moment it flips: v0.2 adds `Switch`.
|
|
15
|
+
- One choice out of several: v0.2 adds `Radio`.
|
|
16
|
+
- Picking several options from a long list in a popup: use `MultiSelect`.
|
|
17
|
+
|
|
18
|
+
## Rules
|
|
19
|
+
|
|
20
|
+
- Give it a name: a `Field.Label` in a `Field` (preferred), or `aria-label` when the context shows the meaning (a table row's checkbox).
|
|
21
|
+
- In a `Field`, keep the default native `Field.Label`. It points at the hidden input, so clicking the label toggles the checkbox, as on a native one. `nativeLabel={false}` would only focus it.
|
|
22
|
+
- Put `name` on the `Field` (or on the Checkbox outside one). A checked box submits `value` (default `"on"`); an unchecked one submits nothing unless you set `uncheckedValue`.
|
|
23
|
+
- `checked` with `onCheckedChange` controls it; `defaultChecked` leaves it uncontrolled.
|
|
24
|
+
- `indeterminate` shows the dash and sets `aria-checked="mixed"`, whatever `checked` is. Clear it yourself when the user clicks: Base UI toggles `checked` but leaves `indeterminate` as you pass it.
|
|
25
|
+
- `disabled` takes it out of the tab order and dashes the border; `readOnly` keeps it focusable and announced (`aria-readonly`) but unchangeable; it looks like an editable one apart from the cursor and no hover, so say nearby why it cannot change. Set `disabled` on the `Field` to disable the checkbox and mute the label together.
|
|
26
|
+
- Space toggles it; Enter submits the form it is in, as on a native checkbox.
|
|
27
|
+
- For an error, set `invalid` on the `Field` with the message in `Field.Error`; the box border thickens and turns `danger`, and the message says what is wrong.
|
|
28
|
+
- Layout and the target are default style props (`display="inline-flex"`, centered, `flexShrink={0}`, `minInlineSize` and `minBlockSize` `"xs"`, 24px). Raise them freely; never set them below `"xs"`. The box is 16px; there are no sizes yet.
|
|
29
|
+
- `className` is a string and `style` an object (not Base UI's state functions); they go to the span, after the defaults. `render` and `nativeButton` are not accepted.
|
|
30
|
+
- The module has no hooks, so a server component can render it. `onCheckedChange` is a function: set it from a client component.
|
|
31
|
+
- Style states off the ARIA attributes (`[aria-checked='true']`, `'mixed'`, `[aria-disabled='true']`, `[aria-invalid='true']`), not Base UI's `data-*` attributes.
|
|
32
|
+
|
|
33
|
+
## Do
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<Field name="terms" invalid={Boolean(errors?.terms)}>
|
|
37
|
+
<Inline gap="8" alignItems="center">
|
|
38
|
+
<Checkbox required />
|
|
39
|
+
<Field.Label>Accept the terms</Field.Label>
|
|
40
|
+
</Inline>
|
|
41
|
+
<Field.Description>You can withdraw any time.</Field.Description>
|
|
42
|
+
<Field.Error>{errors?.terms}</Field.Error>
|
|
43
|
+
</Field>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
// "Select all": mixed while some rows are selected.
|
|
48
|
+
<Checkbox
|
|
49
|
+
aria-label="Select all issues"
|
|
50
|
+
checked={rows.length > 0 && selected.length === rows.length}
|
|
51
|
+
indeterminate={selected.length > 0 && selected.length < rows.length}
|
|
52
|
+
onCheckedChange={(checked) => setSelected(checked ? rows.map((row) => row.id) : [])}
|
|
53
|
+
/>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Don't
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// No name: a screen reader hears only "checkbox, not checked".
|
|
60
|
+
<Checkbox />
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// A bare text node beside it is not its label: clicking it does nothing, and it is not the name.
|
|
65
|
+
<Inline gap="8">
|
|
66
|
+
<Checkbox />
|
|
67
|
+
<Text>Accept the terms</Text>
|
|
68
|
+
</Inline>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// Shrinks the target below 24px.
|
|
73
|
+
<Checkbox minInlineSize="auto" minBlockSize="auto" />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Accessibility
|
|
77
|
+
|
|
78
|
+
- Role `checkbox` with `aria-checked` `"true"`, `"false"` or `"mixed"`; `aria-disabled`, `aria-readonly` and `aria-required` follow the props, and `aria-invalid` the `Field`.
|
|
79
|
+
- The label, description and error reach it through `aria-labelledby` and `aria-describedby`, which Base UI adds after hydration; server HTML has the label's `for`.
|
|
80
|
+
- Checked and mixed are a filled box with a tick or a dash, invalid a thicker border, disabled a dashed one, so no state is color alone. The box border is `borderStrong` (3:1 on every surface).
|
|
81
|
+
- Under forced colors the box keeps its role colors' system mappings: the fill is `LinkText` and the mark `Canvas` when checked, the border `CanvasText`, the focus ring `Highlight`.
|
|
82
|
+
- The target is 24 by 24 CSS px; the focus ring wraps the target, not the box.
|
|
83
|
+
|
|
84
|
+
## Props
|
|
85
|
+
|
|
86
|
+
Generated from the TypeScript types.
|
|
87
|
+
|
|
88
|
+
### Checkbox
|
|
89
|
+
|
|
90
|
+
| Prop | Type | Required | Description |
|
|
91
|
+
| --- | --- | --- | --- |
|
|
92
|
+
| `checked` | `boolean` | no | Whether the checkbox is currently ticked. To render an uncontrolled checkbox, use the `defaultChecked` prop instead. |
|
|
93
|
+
| `defaultChecked` | `boolean` | no | Whether the checkbox is initially ticked. To render a controlled checkbox, use the `checked` prop instead. |
|
|
94
|
+
| `disabled` | `boolean` | no | Whether the component should ignore user interaction. |
|
|
95
|
+
| `form` | `string` | no | Identifies the form that owns the hidden input. Useful when the checkbox is rendered outside the form. |
|
|
96
|
+
| `id` | `string` | no | The id of the input element. |
|
|
97
|
+
| `indeterminate` | `boolean` | no | Whether the checkbox is in a mixed state: neither ticked, nor unticked. |
|
|
98
|
+
| `inputRef` | `Ref<HTMLInputElement>` | no | A ref to access the hidden `<input>` element. |
|
|
99
|
+
| `name` | `string` | no | Identifies the field when a form is submitted. |
|
|
100
|
+
| `onCheckedChange` | `((checked: boolean, eventDetails: { reason: "none"; event: Event; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element \| undefined; }) => void)` | no | Event handler called when the checkbox is ticked or unticked. |
|
|
101
|
+
| `parent` | `boolean` | no | Whether the checkbox controls a group of child checkboxes. Must be used in a [Checkbox Group](https://base-ui.com/react/components/checkbox-group). |
|
|
102
|
+
| `readOnly` | `boolean` | no | Whether the user should be unable to tick or untick the checkbox. |
|
|
103
|
+
| `required` | `boolean` | no | Whether the user must tick the checkbox before submitting a form. |
|
|
104
|
+
| `uncheckedValue` | `string` | no | The value submitted with the form when the checkbox is unchecked. By default, unchecked checkboxes do not submit any value, matching native checkbox behavior. |
|
|
105
|
+
| `value` | `string` | no | The checkbox's value. Identifies it within a [Checkbox Group](https://base-ui.com/react/components/checkbox-group), falling back to `name` when omitted. When submitting a form, a checked box submits `value`; with no `value`, it submits the native "on". |
|
|
106
|
+
|
|
107
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Chip
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
A toggle that filters content or picks an option: "Unread", "Starred", "Medium". A `<button>` with `aria-pressed` on `Clickable`, built on Base UI `Toggle`; `ChipGroup` is a Base UI `ToggleGroup` of chips that share one state. If it changes state when clicked, it's a chip.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Filters over a list: "Unread", "Starred", "Has attachments" (`ChipGroup multiple`).
|
|
10
|
+
- A compact choice of one option among a few: a size, a time range (`ChipGroup`).
|
|
11
|
+
- One on/off option beside a list: a lone `Chip`.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- A status or fact: use `Badge`.
|
|
16
|
+
- A category or topic label on content: use `Tag` (#101).
|
|
17
|
+
- A one-off action: use `Button`.
|
|
18
|
+
- Switching between views of the same content: use `Tabs`.
|
|
19
|
+
- A form field with a required single answer: a radio group (not yet in the library); a `ChipGroup` can be cleared.
|
|
20
|
+
- Picking several values from a list too long to show: use `MultiSelect`.
|
|
21
|
+
|
|
22
|
+
## Rules
|
|
23
|
+
|
|
24
|
+
- `pressed` or `defaultPressed`, with `onPressedChange`, for a lone chip. In a `ChipGroup`, give every chip a `value`; the group's `value` or `defaultValue` is the array of pressed values, and `onValueChange` reports it.
|
|
25
|
+
- `ChipGroup` is single selection by default: pressing a chip releases the rest, and pressing the pressed chip releases it, so the group can be empty. For a choice that is never empty, control `value` and ignore an empty array. `multiple` lets any number be pressed.
|
|
26
|
+
- Name every `ChipGroup` with `aria-label` or `aria-labelledby`.
|
|
27
|
+
- `size`: `sm` (24px) or `med` (32px, default). The label and the check are `sm` at both; 24px is the minimum target (WCAG 2.2).
|
|
28
|
+
- Pressed is `accentFg` on `accent` with a check ahead of the label; pressing widens the chip by the check. Released is `fg` on `bg` with a `borderStrong` border.
|
|
29
|
+
- `disabled` renders `aria-disabled` and blocks toggling. A lone disabled chip stays in the tab order; in a group the arrow keys skip it. `ChipGroup` has no `disabled`: disable the chips one by one.
|
|
30
|
+
- Keep the label to a word or two. Chips wrap onto new lines in a group; a label wider than the group wraps inside its chip.
|
|
31
|
+
- Released fill, text color, radius (`full`), padding (the block padding keeps a wrapped label off the border), gap and height are default style props on the button; a consumer's win. The pressed and disabled looks and the border are the chip's own and beat style props. `className`, `style` and `ref` go to it, `className` last. `ChipGroup` is a wrapping flex row `Box` (`gap="8"`), a column when `orientation="vertical"`.
|
|
32
|
+
- The module has no hooks, so a server component can render it; Base UI's parts are the client components.
|
|
33
|
+
|
|
34
|
+
## Do
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
<ChipGroup aria-label="Filters" multiple value={filters} onValueChange={setFilters}>
|
|
38
|
+
<Chip value="unread">Unread</Chip>
|
|
39
|
+
<Chip value="starred">Starred</Chip>
|
|
40
|
+
<Chip value="attachments">Attachments</Chip>
|
|
41
|
+
</ChipGroup>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<ChipGroup aria-label="Range" defaultValue={["week"]}>
|
|
46
|
+
<Chip value="day">Day</Chip>
|
|
47
|
+
<Chip value="week">Week</Chip>
|
|
48
|
+
<Chip value="month">Month</Chip>
|
|
49
|
+
</ChipGroup>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Don't
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// A chip is a toggle, not an action: this never changes state. Use Button.
|
|
56
|
+
<Chip onClick={archive}>Archive</Chip>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
// An unnamed group: a screen reader announces "group" with nothing to say what it filters.
|
|
61
|
+
<ChipGroup multiple>
|
|
62
|
+
<Chip value="unread">Unread</Chip>
|
|
63
|
+
</ChipGroup>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Accessibility
|
|
67
|
+
|
|
68
|
+
- Each chip is a toggle button: screen readers announce its label with "pressed" or "not pressed". The group is a `group` named by its label.
|
|
69
|
+
- Keyboard: a lone chip is one tab stop; Enter or Space toggles. A `ChipGroup` is one tab stop, landing on the first chip (not the pressed one), then on the last focused chip; Left and Right (Up and Down when vertical) move focus, looping (`loopFocus={false}` stops at the ends); Home and End jump; Enter or Space toggles. Moving focus never changes the selection.
|
|
70
|
+
- The focus ring is Clickable's shared ring on `:focus-visible`.
|
|
71
|
+
- Pressed is never color alone: the check shows. Released text (`fg` on `bg`) and pressed text (`accentFg` on `accent`) pass AA (4.5:1), and the released border (`borderStrong`) passes 3:1, in light and dark mode; the pairs are in `contrastPairs` (ADR 0033).
|
|
72
|
+
- Under forced colors, pressed is `HighlightText` on `Highlight` with the check, released is the page's colors with a `CanvasText` border, and disabled is `GrayText` with a dashed border.
|
|
73
|
+
- Disabled is a dashed border and muted colors, not opacity alone. A pressed chip that is disabled keeps its check but takes the disabled colors.
|
|
74
|
+
|
|
75
|
+
## Props
|
|
76
|
+
|
|
77
|
+
Generated from the TypeScript types.
|
|
78
|
+
|
|
79
|
+
### Chip
|
|
80
|
+
|
|
81
|
+
| Prop | Type | Required | Description |
|
|
82
|
+
| --- | --- | --- | --- |
|
|
83
|
+
| `defaultPressed` | `boolean` | no | Whether the toggle button is currently pressed. This is the uncontrolled counterpart of `pressed`. |
|
|
84
|
+
| `disabled` | `boolean` | no | |
|
|
85
|
+
| `onPressedChange` | `((pressed: boolean, eventDetails: { reason: "none"; event: Event; cancel: () => void; allowPropagation: () => void; isCanceled: boolean; isPropagationAllowed: boolean; trigger: Element \| undefined; }) => void)` | no | Callback fired when the pressed state is changed. |
|
|
86
|
+
| `pressed` | `boolean` | no | Whether the toggle button is currently pressed. This is the controlled counterpart of `defaultPressed`. |
|
|
87
|
+
| `size` | `"med" \| "sm"` | no | |
|
|
88
|
+
| `value` | `string` | no | A unique string that identifies the toggle when used inside a toggle group. |
|
|
89
|
+
|
|
90
|
+
Also accepts the style props (see `manifest.json` `styleProps`).
|
|
91
|
+
|
|
92
|
+
Also exports `ChipGroup`.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Clickable
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The low-level base for everything a user presses: a `<button type="button">` by default, or a link with `as="a"` (or a router's link component). It brings the shared focus ring, a 24 by 24 CSS px minimum target (WCAG 2.2), `disabled` and `busy` states as ARIA attributes, and no look of its own: the button and link chrome is stripped. `Button`, `IconButton` and link components are built on it.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Building a new pressable component: render `Clickable` and pass its look as default style props and its own CSS.
|
|
10
|
+
- A one-off pressable area that no component covers, such as a whole card that opens a detail view.
|
|
11
|
+
|
|
12
|
+
## When not to use
|
|
13
|
+
|
|
14
|
+
- An ordinary action: use `Button`, or `IconButton` for an icon alone. They add variants, sizes and loading.
|
|
15
|
+
- A link inside running text: use a plain `<a>` (or the link component); the 24px target and stripped underline are wrong in a sentence, and WCAG exempts inline links.
|
|
16
|
+
- Menus, tabs, checkboxes and other widgets: use the component built on Base UI, which handles the keyboard.
|
|
17
|
+
|
|
18
|
+
## Rules
|
|
19
|
+
|
|
20
|
+
- `disabled` renders `aria-disabled="true"`, never the native `disabled`: the control stays focusable and announced, and clicks, Enter and Space are blocked (`preventDefault`, so a link does not navigate, a middle-click does not open it in a new tab, and a submit button does not submit). The context menu can still open a disabled link; keep a destination that must be unreachable off the `href`.
|
|
21
|
+
- `busy` renders `aria-busy="true"` and blocks activation the same way. Pair it with a visible change (a spinner or new label).
|
|
22
|
+
- Set state with `disabled` and `busy`, not `aria-disabled` and `aria-busy`. A raw attribute still blocks the click, since JSX cannot reject hyphenated attributes, but the props are the API.
|
|
23
|
+
- `as="button"` gets `type="button"`; pass `type="submit"` for a form's submit button. A component passed as `as` gets no `type`; it sets its own.
|
|
24
|
+
- Only `onClick` is blocked. Do not put actions in `onPointerDown`, `onMouseDown` or `onKeyDown`.
|
|
25
|
+
- Layout and the target size are default style props (`display="inline-flex"`, centered, `minInlineSize` and `minBlockSize` `"xs"`, 24px). Raise them freely; never set them below `"xs"`.
|
|
26
|
+
- Style props win over the stripped chrome, so `padding`, `color` and `backgroundColor` apply as given.
|
|
27
|
+
- The focus ring is the library's one ring (`outline` in the `focus` color, `Highlight` under forced colors). Do not restyle `:focus-visible` or `outline`.
|
|
28
|
+
- A client component (`'use client'`): it wraps `onClick`. Server components can render it with serializable props.
|
|
29
|
+
|
|
30
|
+
## Do
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
<Clickable onClick={open} paddingInline="12" borderRadius="med">
|
|
34
|
+
<Text as="span">Open</Text>
|
|
35
|
+
</Clickable>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<Clickable as={RouterLink} to="/reports" disabled={!canView}>
|
|
40
|
+
<Text as="span">Reports</Text>
|
|
41
|
+
</Clickable>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Don't
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
// Use `disabled`, the API for this state.
|
|
48
|
+
<Clickable aria-disabled onClick={save} />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// A clickable Box has no focus ring, role or keyboard support.
|
|
53
|
+
<Box onClick={open}>Open</Box>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
// Shrinks the target below 24px.
|
|
58
|
+
<Clickable minBlockSize="auto" minInlineSize="auto">
|
|
59
|
+
×
|
|
60
|
+
</Clickable>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Accessibility
|
|
64
|
+
|
|
65
|
+
- A pressable thing that goes somewhere is a link (`as="a"` with `href`); one that does something is a button. Do not give a button an `href` or a link an action with no destination.
|
|
66
|
+
- An icon-only Clickable needs an accessible name (`aria-label`).
|
|
67
|
+
- `aria-disabled` keeps the control discoverable; say why it is disabled nearby when it is not obvious.
|
|
68
|
+
- The ring passes 3:1 against `bg`, `bgMuted` and `bgRaised` in both modes, and is `Highlight` under forced colors.
|
|
69
|
+
|
|
70
|
+
## Props
|
|
71
|
+
|
|
72
|
+
Generated from the TypeScript types.
|
|
73
|
+
|
|
74
|
+
### Clickable
|
|
75
|
+
|
|
76
|
+
| Prop | Type | Required | Description |
|
|
77
|
+
| --- | --- | --- | --- |
|
|
78
|
+
| `as` | `ElementType` | no | |
|
|
79
|
+
| `busy` | `boolean` | no | |
|
|
80
|
+
| `disabled` | `boolean` | no | |
|
|
81
|
+
|
|
82
|
+
Also accepts every `<button>` attribute (change the element with `as`) and the style props (see `manifest.json` `styleProps`).
|