@jonmilner/ui 0.0.0-stage → 0.1.0

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.
Files changed (214) hide show
  1. package/THIRD_PARTY_NOTICES.md +51 -0
  2. package/dist/AlertDialog/index.d.mts +21 -0
  3. package/dist/AlertDialog/index.mjs +29 -0
  4. package/dist/Badge/Badge.css +2 -0
  5. package/dist/Badge/Badge.css.mjs +24 -0
  6. package/dist/Badge/index.d.mts +11 -0
  7. package/dist/Badge/index.mjs +54 -0
  8. package/dist/Box/Box.css +2 -0
  9. package/dist/Box/Box.css.mjs +8 -0
  10. package/dist/Box/index.d.mts +10 -0
  11. package/dist/Box/index.mjs +15 -0
  12. package/dist/Button/Button.css +2 -0
  13. package/dist/Button/Button.css.mjs +25 -0
  14. package/dist/Button/index.d.mts +20 -0
  15. package/dist/Button/index.mjs +97 -0
  16. package/dist/Checkbox/Checkbox.css +2 -0
  17. package/dist/Checkbox/Checkbox.css.mjs +8 -0
  18. package/dist/Checkbox/index.d.mts +6 -0
  19. package/dist/Checkbox/index.mjs +45 -0
  20. package/dist/Chip/Chip.css +2 -0
  21. package/dist/Chip/Chip.css.mjs +17 -0
  22. package/dist/Chip/index.d.mts +13 -0
  23. package/dist/Chip/index.mjs +73 -0
  24. package/dist/Clickable/Clickable.css +2 -0
  25. package/dist/Clickable/Clickable.css.mjs +8 -0
  26. package/dist/Clickable/index.d.mts +13 -0
  27. package/dist/Clickable/index.mjs +28 -0
  28. package/dist/Dialog/Dialog.css +2 -0
  29. package/dist/Dialog/Dialog.css.d.mts +3 -0
  30. package/dist/Dialog/Dialog.css.mjs +12 -0
  31. package/dist/Dialog/client.d.mts +12 -0
  32. package/dist/Dialog/client.mjs +31 -0
  33. package/dist/Dialog/index.d.mts +30 -0
  34. package/dist/Dialog/index.mjs +85 -0
  35. package/dist/Field/Field.css +2 -0
  36. package/dist/Field/Field.css.mjs +8 -0
  37. package/dist/Field/client.mjs +26 -0
  38. package/dist/Field/index.d.mts +20 -0
  39. package/dist/Field/index.mjs +88 -0
  40. package/dist/Heading/Heading.css +2 -0
  41. package/dist/Heading/Heading.css.mjs +8 -0
  42. package/dist/Heading/index.d.mts +9 -0
  43. package/dist/Heading/index.mjs +27 -0
  44. package/dist/Icon/Icon.css +2 -0
  45. package/dist/Icon/Icon.css.mjs +17 -0
  46. package/dist/Icon/index.d.mts +12 -0
  47. package/dist/Icon/index.mjs +24 -0
  48. package/dist/IconButton/IconButton.css +2 -0
  49. package/dist/IconButton/IconButton.css.mjs +24 -0
  50. package/dist/IconButton/index.d.mts +17 -0
  51. package/dist/IconButton/index.mjs +25 -0
  52. package/dist/Inline/Inline.css +2 -0
  53. package/dist/Inline/Inline.css.mjs +8 -0
  54. package/dist/Inline/index.d.mts +15 -0
  55. package/dist/Inline/index.mjs +17 -0
  56. package/dist/Menu/Menu.css +2 -0
  57. package/dist/Menu/Menu.css.mjs +8 -0
  58. package/dist/Menu/client.d.mts +9 -0
  59. package/dist/Menu/client.mjs +27 -0
  60. package/dist/Menu/index.d.mts +34 -0
  61. package/dist/Menu/index.mjs +107 -0
  62. package/dist/MultiSelect/MultiSelect.css +2 -0
  63. package/dist/MultiSelect/MultiSelect.css.mjs +8 -0
  64. package/dist/MultiSelect/client.d.mts +8 -0
  65. package/dist/MultiSelect/client.mjs +93 -0
  66. package/dist/MultiSelect/index.d.mts +23 -0
  67. package/dist/MultiSelect/index.mjs +75 -0
  68. package/dist/SearchField/SearchField.css +2 -0
  69. package/dist/SearchField/SearchField.css.mjs +27 -0
  70. package/dist/SearchField/client.mjs +71 -0
  71. package/dist/SearchField/index.d.mts +22 -0
  72. package/dist/SearchField/index.mjs +35 -0
  73. package/dist/Stack/Stack.css +2 -0
  74. package/dist/Stack/Stack.css.mjs +8 -0
  75. package/dist/Stack/index.d.mts +15 -0
  76. package/dist/Stack/index.mjs +15 -0
  77. package/dist/Tabs/Tabs.css +2 -0
  78. package/dist/Tabs/Tabs.css.mjs +8 -0
  79. package/dist/Tabs/index.d.mts +20 -0
  80. package/dist/Tabs/index.mjs +88 -0
  81. package/dist/Tag/Tag.css +2 -0
  82. package/dist/Tag/Tag.css.mjs +17 -0
  83. package/dist/Tag/index.d.mts +13 -0
  84. package/dist/Tag/index.mjs +82 -0
  85. package/dist/Text/Text.css +2 -0
  86. package/dist/Text/Text.css.mjs +32 -0
  87. package/dist/Text/index.d.mts +18 -0
  88. package/dist/Text/index.mjs +25 -0
  89. package/dist/Toast/Toast.css +2 -0
  90. package/dist/Toast/Toast.css.mjs +18 -0
  91. package/dist/Toast/client.d.mts +46 -0
  92. package/dist/Toast/client.mjs +219 -0
  93. package/dist/Toast/index.d.mts +7 -0
  94. package/dist/Toast/index.mjs +5 -0
  95. package/dist/base.css +6 -0
  96. package/dist/brands/brand.d.mts +9 -0
  97. package/dist/brands/brand.mjs +4 -0
  98. package/dist/brands/check-fonts.mjs +15 -0
  99. package/dist/brands/default.d.mts +4 -0
  100. package/dist/brands/default.mjs +74 -0
  101. package/dist/brands/font.d.mts +17 -0
  102. package/dist/brands/font.mjs +35 -0
  103. package/dist/brands/forced-colors.mjs +18 -0
  104. package/dist/brands/index.d.mts +8 -0
  105. package/dist/brands/index.mjs +5 -0
  106. package/dist/brands/metrics.mjs +40 -0
  107. package/dist/contrast/color.d.mts +3 -0
  108. package/dist/contrast/color.mjs +222 -0
  109. package/dist/contrast/contrast.d.mts +18 -0
  110. package/dist/contrast/contrast.mjs +52 -0
  111. package/dist/contrast/mixes.mjs +33 -0
  112. package/dist/docs/AlertDialog.md +113 -0
  113. package/dist/docs/Badge.md +76 -0
  114. package/dist/docs/Box.md +74 -0
  115. package/dist/docs/Button.md +98 -0
  116. package/dist/docs/Checkbox.md +107 -0
  117. package/dist/docs/Chip.md +92 -0
  118. package/dist/docs/Clickable.md +82 -0
  119. package/dist/docs/Dialog.md +166 -0
  120. package/dist/docs/Field.md +143 -0
  121. package/dist/docs/Heading.md +69 -0
  122. package/dist/docs/Icon.md +116 -0
  123. package/dist/docs/IconButton.md +83 -0
  124. package/dist/docs/Inline.md +79 -0
  125. package/dist/docs/Menu.md +181 -0
  126. package/dist/docs/MultiSelect.md +120 -0
  127. package/dist/docs/SearchField.md +106 -0
  128. package/dist/docs/Stack.md +79 -0
  129. package/dist/docs/Tabs.md +130 -0
  130. package/dist/docs/Tag.md +97 -0
  131. package/dist/docs/Text.md +90 -0
  132. package/dist/docs/Toast.md +115 -0
  133. package/dist/docs/llms.txt +33 -0
  134. package/dist/docs/manifest.json +2632 -0
  135. package/dist/docs/manifest.schema.json +96 -0
  136. package/dist/docs/migrations/README.md +27 -0
  137. package/dist/docs/theming.md +59 -0
  138. package/dist/icons/ArrowLeftIcon.d.mts +4 -0
  139. package/dist/icons/ArrowLeftIcon.mjs +11 -0
  140. package/dist/icons/ArrowRightIcon.d.mts +4 -0
  141. package/dist/icons/ArrowRightIcon.mjs +11 -0
  142. package/dist/icons/CheckIcon.d.mts +4 -0
  143. package/dist/icons/CheckIcon.mjs +11 -0
  144. package/dist/icons/ChevronDownIcon.d.mts +4 -0
  145. package/dist/icons/ChevronDownIcon.mjs +11 -0
  146. package/dist/icons/ChevronRightIcon.d.mts +4 -0
  147. package/dist/icons/ChevronRightIcon.mjs +11 -0
  148. package/dist/icons/CircleAlertIcon.d.mts +4 -0
  149. package/dist/icons/CircleAlertIcon.mjs +15 -0
  150. package/dist/icons/CircleCheckIcon.d.mts +4 -0
  151. package/dist/icons/CircleCheckIcon.mjs +15 -0
  152. package/dist/icons/CircleDotIcon.d.mts +4 -0
  153. package/dist/icons/CircleDotIcon.mjs +19 -0
  154. package/dist/icons/CircleSlashIcon.d.mts +4 -0
  155. package/dist/icons/CircleSlashIcon.mjs +15 -0
  156. package/dist/icons/GitMergeIcon.d.mts +4 -0
  157. package/dist/icons/GitMergeIcon.mjs +23 -0
  158. package/dist/icons/GitPullRequestClosedIcon.d.mts +4 -0
  159. package/dist/icons/GitPullRequestClosedIcon.mjs +23 -0
  160. package/dist/icons/GitPullRequestDraftIcon.d.mts +4 -0
  161. package/dist/icons/GitPullRequestDraftIcon.mjs +23 -0
  162. package/dist/icons/GitPullRequestIcon.d.mts +4 -0
  163. package/dist/icons/GitPullRequestIcon.mjs +23 -0
  164. package/dist/icons/InboxIcon.d.mts +4 -0
  165. package/dist/icons/InboxIcon.mjs +11 -0
  166. package/dist/icons/InfoIcon.d.mts +4 -0
  167. package/dist/icons/InfoIcon.mjs +15 -0
  168. package/dist/icons/LoaderCircleIcon.d.mts +4 -0
  169. package/dist/icons/LoaderCircleIcon.mjs +11 -0
  170. package/dist/icons/RefreshCwIcon.d.mts +4 -0
  171. package/dist/icons/RefreshCwIcon.mjs +15 -0
  172. package/dist/icons/SearchIcon.d.mts +4 -0
  173. package/dist/icons/SearchIcon.mjs +15 -0
  174. package/dist/icons/SettingsIcon.d.mts +4 -0
  175. package/dist/icons/SettingsIcon.mjs +15 -0
  176. package/dist/icons/XIcon.d.mts +4 -0
  177. package/dist/icons/XIcon.mjs +11 -0
  178. package/dist/icons/index.d.mts +25 -0
  179. package/dist/icons/index.mjs +45 -0
  180. package/dist/index.d.mts +27 -0
  181. package/dist/index.mjs +25 -0
  182. package/dist/popup/client.d.mts +9 -0
  183. package/dist/popup/client.mjs +31 -0
  184. package/dist/styles/atoms.css +2 -0
  185. package/dist/styles/atoms.css.d.mts +219 -0
  186. package/dist/styles/atoms.css.mjs +2579 -0
  187. package/dist/styles/button-fills.mjs +17 -0
  188. package/dist/styles/highlight.mjs +8 -0
  189. package/dist/styles/hover-tint.mjs +4 -0
  190. package/dist/styles/join.mjs +4 -0
  191. package/dist/styles/layers.css +2 -0
  192. package/dist/styles/part-props.d.mts +8 -0
  193. package/dist/styles/split-style-props.mjs +9 -0
  194. package/dist/testing/index.d.mts +4 -0
  195. package/dist/testing/index.mjs +3 -0
  196. package/dist/testing/motion.d.mts +22 -0
  197. package/dist/testing/motion.mjs +49 -0
  198. package/dist/theme/FontCheck.mjs +11 -0
  199. package/dist/theme/ThemeProvider.d.mts +15 -0
  200. package/dist/theme/ThemeProvider.mjs +23 -0
  201. package/dist/theme/define-theme.d.mts +9 -0
  202. package/dist/theme/define-theme.mjs +84 -0
  203. package/dist/theme/use-theme-container.mjs +15 -0
  204. package/dist/tokens/contract.d.mts +105 -0
  205. package/dist/tokens/contract.mjs +17 -0
  206. package/dist/tokens/index.d.mts +2 -0
  207. package/dist/tokens/index.mjs +1 -0
  208. package/dist/tokens/roles.d.mts +6 -0
  209. package/dist/tokens/roles.mjs +24 -0
  210. package/dist/tokens/scales.d.mts +95 -0
  211. package/dist/tokens/scales.mjs +98 -0
  212. package/dist/types.d.mts +3 -0
  213. package/package.json +159 -3
  214. package/README.md +0 -3
@@ -0,0 +1,97 @@
1
+ # Tag
2
+
3
+ ## Purpose
4
+
5
+ A label that puts content in a category: a topic, a label, a user-made category. A `span` with a `bgMuted` fill and a full radius around a `Text` in the `label` font. It can be read-only, a link, or removable.
6
+
7
+ ## When to use
8
+
9
+ - Topics or labels on content: "Design", "Release", "Needs review".
10
+ - Categories a user adds and removes, such as the labels on an issue.
11
+ - A category that links to a page listing everything in it (`href`).
12
+
13
+ ## When not to use
14
+
15
+ - The status of, or a short fact about, a thing: use `Badge`.
16
+ - A filter or choice the user toggles on and off: use `Chip`.
17
+ - A whole-tag action other than a link or removal: compose `Clickable`.
18
+ - A sentence or a message: use `Text`.
19
+
20
+ ## Rules
21
+
22
+ - `size`: `sm` or `med` (default), the label's `fontSize` step. There are no color variants yet: a Tag is neutral (`fg` on `bgMuted`, a `contrastPairs` pair).
23
+ - Read-only is the default. `href` makes the label a link (a `Clickable` `<a>` around the text, underlined). The tag itself stays a `span`, so a remove button never nests in the link.
24
+ - `onRemove` adds a native `<button>` after the label with an `x` icon. Its name is "Remove <label>" from a string or number label; set `removeLabel` for any other label. Enter and Space activate it; the click calls `onRemove`. Tag handles no keys, so Backspace and Delete do nothing (ADR 0003, 0055).
25
+ - Wrapping: an uncapped label wraps to the width it has, so a tag never overflows a narrow container or a 320px viewport at large text sizes.
26
+ - Truncation: set `maxInlineSize` on the Tag, as the style prop or in `style` (the style prop's steps stop at 56px, so most caps are `style={{ maxInlineSize: "min(10rem, 100%)" }}`; the `100%` keeps a rem cap inside a narrow container at large text sizes). `style.maxWidth` counts too, and `style` wins over the prop, as inline styles do. A responsive prop truncates only with a `base` step and a size step (or `full`) at every breakpoint: `{ med: "xl" }` and `{ base: "xl", med: "fit" }` wrap. The prop's `fit`, `min`, `max` and `auto`, and `style` values `none`, `auto` and `*-content`, follow the content and wrap. A cap set only through `className` wraps instead. The label becomes one line with an ellipsis. While the pointer is over the tag, or focus is inside it, the label shows its whole text, wrapped within the same width. There is no tooltip and no `title`; the DOM always holds the full text, so screen readers read all of it and so does the remove button's name. The tag grows in height when it reveals, so leave room for it in a tight row. A read-only tag takes no focus, so a keyboard user cannot reveal it: cap read-only tags only where the full text is also elsewhere, or make them links.
27
+ - With no children it renders nothing. Display, fill, text color, padding, radius and `minBlockSize` (the remove button's 24px target) are default style props; a consumer's win. `className`, `style` and `ref` go to the root, `className` last.
28
+ - The module has no hooks, so a server component can render it.
29
+
30
+ ## Focus after removal
31
+
32
+ Tag never moves focus: removing a tag unmounts the button that has it, and the browser drops focus to `body`. The owner of the list puts it back. In order:
33
+
34
+ 1. The next tag's remove button, which now sits at the removed tag's index.
35
+ 2. Else the previous tag's remove button.
36
+ 3. Else (the list is empty) the list's container or heading, made focusable with `tabIndex={-1}`, or the input that adds tags.
37
+
38
+ Tell screen reader users the tag is gone, with the owner's live region or by naming the list's new count in the element that takes focus.
39
+
40
+ ## Do
41
+
42
+ ```tsx
43
+ <Inline gap="4" alignItems="center">
44
+ <Tag href="/topics/design">Design</Tag>
45
+ <Tag>Release</Tag>
46
+ </Inline>
47
+ ```
48
+
49
+ ```tsx
50
+ // The owner removes the tag and moves focus (see the Removable story).
51
+ <Tag onRemove={() => remove(label)}>{label}</Tag>
52
+ ```
53
+
54
+ ```tsx
55
+ <Tag style={{ maxInlineSize: "min(10rem, 100%)" }}>{longCategoryName}</Tag>
56
+ ```
57
+
58
+ ## Don't
59
+
60
+ ```tsx
61
+ // Tag handles no keys; do not add Backspace or Delete to it.
62
+ <Tag onKeyDown={removeOnDelete}>Design</Tag>
63
+ ```
64
+
65
+ ```tsx
66
+ // A tag is not a status. Use Badge.
67
+ <Tag>Failed</Tag>
68
+ ```
69
+
70
+ ```tsx
71
+ // A non-text label needs removeLabel, or the button is just "Remove".
72
+ <Tag onRemove={remove}>
73
+ <b>Design</b>
74
+ </Tag>
75
+ ```
76
+
77
+ ## Accessibility
78
+
79
+ - A read-only tag is plain text in a `span`, no role. A link tag is a link in the tab order. A removable tag adds one button; each button's name says which tag it removes.
80
+ - Nothing essential is only in a tooltip: truncated text is fully in the DOM and shows on hover and focus.
81
+ - The `x` icon is `aria-hidden`; the button's `aria-label` names it. The link and the button each get the shared focus ring and a 24px target.
82
+ - Under forced colors the tag is `CanvasText` on `Canvas` with a `CanvasText` outline, as Badge is.
83
+
84
+ ## Props
85
+
86
+ Generated from the TypeScript types.
87
+
88
+ ### Tag
89
+
90
+ | Prop | Type | Required | Description |
91
+ | --- | --- | --- | --- |
92
+ | `href` | `string` | no | |
93
+ | `onRemove` | `(() => void)` | no | |
94
+ | `removeLabel` | `string` | no | |
95
+ | `size` | `"med" \| "sm"` | no | |
96
+
97
+ Also accepts the style props (see `manifest.json` `styleProps`).
@@ -0,0 +1,90 @@
1
+ # Text
2
+
3
+ ## Purpose
4
+
5
+ The base of all visible text, and the primitive every text component builds on. Capsize trims the box to the cap height and the baseline, so the gap around text is the visible space. It takes every Box style prop (`color`, `textAlign`, margins), plus `size`, `font`, `weight`, `leading` and `truncate`.
6
+
7
+ ## When to use
8
+
9
+ - Body copy, labels, captions, code: any string a user sees.
10
+ - Building a text component (a heading, a label, a badge's text): render `Text` with defaults, as `Heading` does.
11
+ - A run inside a line (`strong`, `em`, `code`): a `Text` rendered as a phrasing element directly inside another `Text` is inline and untrimmed, and inherits the outer font until its own props say otherwise.
12
+
13
+ ## When not to use
14
+
15
+ - Section and page headings: use `Heading`, which picks the element by level.
16
+ - Layout around text: put Text in a `Box`, `Stack` or `Inline` and set the gap there.
17
+
18
+ ## Rules
19
+
20
+ - `as` defaults to `p`, so a run inside a paragraph must set an inline element: `as="span"`, `"strong"`, `"em"`, `"code"`, `"time"`. A `p` inside a `p` is invalid HTML and breaks hydration.
21
+ - A run inherits the outer weight, so `<Text as="strong">` is not bold until it sets `weight="bold"` (or `semibold`).
22
+ - A run is inline whatever its `display` prop, and `truncate` gives it no ellipsis; truncate a block Text instead.
23
+ - `display="flex"` or `"grid"` makes the trim pseudo-elements flex or grid items, so `gap` adds space around them; wrap the text in a Box for layout instead. A block element (`p`, `div`, `h2`) or a Text inside a `Box` inside a Text keeps its own trimmed box.
24
+ - `size` takes a `fontSize` step: `xs`, `sm`, `med`, `lg`, `xl`, `2xl`, `3xl`, `4xl`. The top-level default is `med`.
25
+ - `font` takes a font role: `body` (default), `heading`, `display`, `label`, `mono`. The brand maps roles to fonts.
26
+ - `weight` takes `regular` (default), `medium`, `semibold` or `bold`.
27
+ - `leading` takes a unitless line height: `sm`, `med` (default) or `lg`. The trim follows it.
28
+ - `color` takes a color role (`fg`, `fgMuted`, `accent`, …), never a raw color. Unset, it inherits.
29
+ - `truncate` keeps one line and cuts it with an ellipsis, without clipping ascenders or descenders. It sets the block margins, so do not combine it with margin props.
30
+ - The trim is two pseudo-elements; do not style `::before` or `::after` on a Text.
31
+ - No native `text-box`; capsize only (ADR 0016).
32
+
33
+ ## Do
34
+
35
+ ```tsx
36
+ <Text size="sm" color="fgMuted">
37
+ Updated{" "}
38
+ <Text as="time" dateTime="2026-10-23">
39
+ today
40
+ </Text>
41
+ </Text>
42
+ ```
43
+
44
+ ```tsx
45
+ <Text as="span" font="label" weight="medium" truncate>
46
+ {fileName}
47
+ </Text>
48
+ ```
49
+
50
+ ## Don't
51
+
52
+ ```tsx
53
+ // Raw colors and `md` do not type-check.
54
+ <Text color="#666" size="md" />
55
+ ```
56
+
57
+ ```tsx
58
+ // Text outside Text: untrimmed, in no font role.
59
+ <Box>Saved</Box>
60
+ ```
61
+
62
+ ```tsx
63
+ // A heading built from Text loses the level; use Heading.
64
+ <Text as="h2" size="xl" weight="semibold">
65
+ Settings
66
+ </Text>
67
+ ```
68
+
69
+ ## Accessibility
70
+
71
+ - Text adds no role or ARIA. Pick the element with `as` so the meaning is right: `p`, `label`, `time`, `code`, `strong`.
72
+ - Color alone carries no meaning: `fgMuted` text needs words that say the same.
73
+ - Truncated text is cut for everyone; put the full string somewhere reachable (the page it links to, an accessible name), not only in a tooltip.
74
+
75
+ ## Props
76
+
77
+ Generated from the TypeScript types.
78
+
79
+ ### Text
80
+
81
+ | Prop | Type | Required | Description |
82
+ | --- | --- | --- | --- |
83
+ | `as` | `ElementType` | no | |
84
+ | `font` | `"body" \| "display" \| "heading" \| "label" \| "mono"` | no | |
85
+ | `leading` | `"lg" \| "med" \| "sm"` | no | |
86
+ | `size` | `"2xl" \| "3xl" \| "4xl" \| "lg" \| "med" \| "sm" \| "xl" \| "xs"` | no | |
87
+ | `truncate` | `boolean` | no | |
88
+ | `weight` | `"bold" \| "medium" \| "regular" \| "semibold"` | no | |
89
+
90
+ Also accepts every `<p>` attribute (change the element with `as`) and the style props (see `manifest.json` `styleProps`).
@@ -0,0 +1,115 @@
1
+ # Toast
2
+
3
+ ## Purpose
4
+
5
+ Tells the user briefly about the result of an action without interrupting them: "Changes saved", "Message sent", "Couldn't upload the file". Material calls it a snackbar and Atlassian a flag. Built on Base UI's Toast, which owns the queue, the timers and swipe-to-dismiss. The parts add the look: a raised card in a stack at the bottom end corner, an icon per variant, a close button, and a slide-up-and-fade that is a plain fade under reduced motion. A toast never takes focus and moves nothing on the page.
6
+
7
+ ## When to use
8
+
9
+ - The outcome of something the user just did, when they can carry on without reading it.
10
+ - An action that ran at once and can be undone: offer Undo as the toast's action.
11
+ - A background task finishing or failing (an upload, an export).
12
+
13
+ ## When not to use
14
+
15
+ - A decision the user must make before the action runs: use `AlertDialog`.
16
+ - A focused task, or anything with fields: use `Dialog`. A toast holds no form fields.
17
+ - An error on one input: use the `Field`'s error message, on the field.
18
+ - A status that stays (draft, synced, 3 unread): use `Badge`.
19
+ - A message that must stay on the page until its cause is fixed (offline, trial ending): an inline alert or banner, which the library does not have yet.
20
+ - Anything the user must read or act on to continue: a toast may close before they reach it.
21
+
22
+ ## Rules
23
+
24
+ - Wrap the app once in `Toast.Provider`, inside its `ThemeProvider`. Show a toast from any client component under it with `useToast()`: `add(options)` returns the toast's id, `close(id)` dismisses it.
25
+ - `title` is one line on what happened. `description` is one optional sentence more. Both are plain strings.
26
+ - `variant` is `info` (the default), `success` or `error`. Each has its own icon, so the variant never rests on color alone. `success` uses the `accent` icon on `bgRaised` until the library has a success role (#100).
27
+ - `action` takes a `label` and an `onClick`; there is at most one. Choosing it also closes the toast. Nothing essential goes there: Undo is fine when the user could also undo another way; "Retry" on an error is fine because an error stays until it is dismissed.
28
+ - `duration` is how long, in milliseconds, before the toast closes itself; `0` keeps it until the user dismisses it. It defaults to 5000, and to 0 for `error`, so the user always gets time to read an error.
29
+ - Every toast has a close button. `closeLabel` on the provider names it for every toast ("Dismiss" by default) and `regionLabel` names the stack's landmark ("Notifications" by default); translate both there.
30
+ - `limit` on the provider (3 by default) caps how many show at once: the newest. Older ones are hidden until a newer one leaves, so an error that stays until dismissed can be hidden by later toasts. The newest sits at the top, nearest the content.
31
+ - The stack portals into the provider's nearest `ThemeProvider`, so it keeps the brand and mode, and sits on `zIndex` `toast`, above modals. That provider must not clip or stack its content: wrap the app root. `className` and `style` on the provider reach the stack.
32
+ - The index module has no hooks, so a server component can render `Toast.Provider`; `useToast` runs in a client component.
33
+
34
+ ## Do
35
+
36
+ ```tsx
37
+ <ThemeProvider brand={brand}>
38
+ <Toast.Provider>
39
+ <App />
40
+ </Toast.Provider>
41
+ </ThemeProvider>
42
+ ```
43
+
44
+ ```tsx
45
+ const toast = useToast();
46
+
47
+ async function save() {
48
+ try {
49
+ await saveProfile();
50
+ toast.add({ title: "Profile saved", variant: "success" });
51
+ } catch {
52
+ toast.add({
53
+ title: "Couldn't save your profile",
54
+ description: "Check your connection and try again.",
55
+ variant: "error",
56
+ action: { label: "Retry", onClick: save },
57
+ });
58
+ }
59
+ }
60
+ ```
61
+
62
+ ```tsx
63
+ // The archive ran at once; Undo is a shortcut, the archive view is the other way back.
64
+ toast.add({ title: "Conversation archived", action: { label: "Undo", onClick: unarchive } });
65
+ ```
66
+
67
+ ## Don't
68
+
69
+ ```tsx
70
+ // A decision dressed as a toast: it may close before the user decides. Use AlertDialog.
71
+ toast.add({ title: "Delete this project?", action: { label: "Delete", onClick: remove } });
72
+ ```
73
+
74
+ ```tsx
75
+ // The only way to finish the task, on a timer.
76
+ toast.add({ title: "Verify your email", action: { label: "Send link", onClick: send } });
77
+ ```
78
+
79
+ ```tsx
80
+ // A paragraph: a toast is one line and one sentence. Give it a page or a Dialog.
81
+ toast.add({ title: "Update", description: releaseNotes });
82
+ ```
83
+
84
+ ## Accessibility
85
+
86
+ - The stack is a `region` landmark named by `regionLabel` ("Notifications" by default); F6 moves focus to it, and Shift+Tab from there returns focus where it was. Each toast is a `group` named by its title. Neither the stack (`aria-live="off"`) nor the toasts are live regions; the provider renders two visually hidden ones that exist before any toast, and adds each toast's title and description to one as it arrives: `info` and `success` to a polite `status`, `error` to an assertive `alert`. So each toast is announced once, and an error at once (ADR 0074). Adding a toast never moves focus.
87
+ - A toast hidden by `limit` was announced when it arrived. When a newer one leaves and it shows again, it is not announced a second time; a user who needs it finds it in the stack with F6.
88
+ - An error raised from inside a modal `Dialog` is still announced: the dialog hides the rest of the page from assistive tech, but not the announcer's regions.
89
+ - Inside a modal `Dialog`: F6 still reaches the stack past the dialog's focus trap, so an error's action and close button stay in keyboard reach; Tab past the last toast returns focus to the dialog. Escape in the stack currently closes the dialog too, not only the toast (#145).
90
+ - Reflow (WCAG 1.4.10): a stack taller than the window, as at 320px wide with 200% text, scrolls inside itself with the newest toast on top, so the older ones stay in reach.
91
+ - Resize text (WCAG 1.4.4): when the stack is narrower than 16rem, as at 320px wide with 200% text, the content moves to its own line below the icon and close button, so the button stays inside the card and words wrap whole (ADR 0067).
92
+ - Timing (WCAG 2.2.1, Timing Adjustable): a toast closes on its own only when it is not essential. The timer pauses while the pointer is over the stack, while keyboard focus is in it, and while the window is in the background, so a user who reaches it gets the time they need. An error stays until dismissed by default, and `duration: 0` keeps any toast. The close button dismisses a toast at any time.
93
+ - Dismissing a toast from the keyboard (Enter or Space on its close button, or a screen reader's activation) moves focus to the close button of the toast that takes its place: the next one, else the previous one. Dismissing the last returns focus to where it was before Tab or F6 brought it into the stack, so focus never falls to the page (ADR 0085). Focus after a click or swipe is #149.
94
+ - The close button and the action are native buttons with the shared focus ring and a 24px target. The close button's name is `closeLabel`.
95
+ - Swiping a toast away, down or toward the stack's inline end (right, or left under `dir="rtl"`), is a shortcut; the close button does the same.
96
+ - Under reduced motion a toast only fades. Under forced colors it keeps its border.
97
+
98
+ ## Props
99
+
100
+ Generated from the TypeScript types.
101
+
102
+ `Toast` is not a component: render its parts.
103
+
104
+ ### Toast.Provider
105
+
106
+ | Prop | Type | Required | Description |
107
+ | --- | --- | --- | --- |
108
+ | `children` | `ReactNode` | no | |
109
+ | `className` | `string` | no | |
110
+ | `closeLabel` | `string` | no | |
111
+ | `limit` | `number` | no | |
112
+ | `regionLabel` | `string` | no | |
113
+ | `style` | `CSSProperties` | no | |
114
+
115
+ Also exports `useToast`.
@@ -0,0 +1,33 @@
1
+ # @jonmilner/ui
2
+
3
+ > React design system. Each component's docs say when to use it, its rules and props.
4
+
5
+ ## Components
6
+
7
+ - [AlertDialog](AlertDialog.md): Asks the user to confirm a consequential action before it runs: delete a project, discard unsaved changes, sign out every device. Built on Base UI's AlertDialog, which owns the behavior: `role="alertdialog"`, focus moves into the dialog and stays there, the page behind is inert, Esc closes it, and a click outside does not. It wears Dialog's panel at the `sm` width with a title, a description, Cancel and one confirm button, and Cancel takes focus on open.
8
+ - [Badge](Badge.md): 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.
9
+ - [Box](Box.md): 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`.
10
+ - [Button](Button.md): 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.
11
+ - [Checkbox](Checkbox.md): 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).
12
+ - [Chip](Chip.md): 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.
13
+ - [Clickable](Clickable.md): 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.
14
+ - [Dialog](Dialog.md): 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.
15
+ - [Field](Field.md): 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.
16
+ - [Heading](Heading.md): 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.
17
+ - [Icon](Icon.md): 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.
18
+ - [IconButton](IconButton.md): 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).
19
+ - [Inline](Inline.md): 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.
20
+ - [Menu](Menu.md): A list of actions that opens from a button: a row's "Edit, Duplicate, Delete", a toolbar's overflow. Built on Base UI's Menu, which owns the keyboard and focus: arrow keys, Home and End move through the items, typing jumps to a matching item, Esc and an outside click close it, and focus returns to the trigger. The parts add the look: a raised surface with a border and shadow, a highlighted item, a zoom-and-fade that is a plain fade under reduced motion.
21
+ - [MultiSelect](MultiSelect.md): A form field for picking any number of values from a list too long to show at once. A trigger styled as Button's secondary look opens a listbox of options; each selected option shows a check mark, and the trigger summarises the selection as the joined labels, or a count ("3 selected") once the labels would not fit. Built on Base UI's `Select` in multiple mode (ADR 0003), which owns the keyboard and focus: arrow keys, Home and End move through the options, typing jumps to a match, Esc and an outside click close it, and focus returns to the trigger. It composes `Field` for its label, description and error (ADR 0026).
22
+ - [SearchField](SearchField.md): A form field for a search query: an `<input type="search">` (role `searchbox`) with a search icon at its inline start and, while it holds a value, a clear button at its inline end. Escape empties a field that has a value, and the clear button empties it and puts focus back in the input, so a keyboard user never loses their place (ADR 0087). Built on Base UI's `Input` (ADR 0003); it composes `Field` for its label, description and error (ADR 0026). Three sizes at Button's heights (`sm`, `med`, `lg`: 32, 40 and 48 px).
23
+ - [Stack](Stack.md): A vertical column of children with even space between them. `Stack` is a `Box` with `display="flex"` and `flexDirection="column"` as default style props, spaced by a required `gap`. Children render as they are: no wrapper per child.
24
+ - [Tabs](Tabs.md): Switches between views that share one place on the page. The parts are `Tabs` (the root, a flex column; a row when vertical), `Tabs.List` (the `tablist`), `Tabs.Tab` (a `tab` button on `Clickable`) and `Tabs.Panel` (the `tabpanel`). Built on Base UI `Tabs` (ADR 0003, 0046): it owns the selection, one tab stop with arrow-key roving, `aria-selected`, and the ids that tie each tab to its panel. The selected tab shows an accent bar that fades and grows in (ADR 0049).
25
+ - [Tag](Tag.md): A label that puts content in a category: a topic, a label, a user-made category. A `span` with a `bgMuted` fill and a full radius around a `Text` in the `label` font. It can be read-only, a link, or removable.
26
+ - [Text](Text.md): The base of all visible text, and the primitive every text component builds on. Capsize trims the box to the cap height and the baseline, so the gap around text is the visible space. It takes every Box style prop (`color`, `textAlign`, margins), plus `size`, `font`, `weight`, `leading` and `truncate`.
27
+ - [Toast](Toast.md): Tells the user briefly about the result of an action without interrupting them: "Changes saved", "Message sent", "Couldn't upload the file". Material calls it a snackbar and Atlassian a flag. Built on Base UI's Toast, which owns the queue, the timers and swipe-to-dismiss. The parts add the look: a raised card in a stack at the bottom end corner, an icon per variant, a close button, and a slide-up-and-fade that is a plain fade under reduced motion. A toast never takes focus and moves nothing on the page.
28
+
29
+ ## Reference
30
+
31
+ - [manifest.json](manifest.json): every component, prop, variant, icon and token, as JSON
32
+ - [theming.md](theming.md): themes, brands and tokens
33
+ - [migrations/](migrations/README.md): upgrade notes for breaking releases