@handamade/psi-react 0.19.0 → 0.21.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 (121) hide show
  1. package/README.md +1 -1
  2. package/dist/AppShell/AppShell.d.ts +51 -0
  3. package/dist/AppShell/AppShell.d.ts.map +1 -0
  4. package/dist/Banner/Banner.d.ts +35 -0
  5. package/dist/Banner/Banner.d.ts.map +1 -0
  6. package/dist/Button/Button.d.ts +4 -0
  7. package/dist/Button/Button.d.ts.map +1 -1
  8. package/dist/CopyButton/CopyButton.d.ts +23 -0
  9. package/dist/CopyButton/CopyButton.d.ts.map +1 -0
  10. package/dist/CursorPagination/CursorPagination.d.ts +29 -0
  11. package/dist/CursorPagination/CursorPagination.d.ts.map +1 -0
  12. package/dist/Field/Field.d.ts +18 -3
  13. package/dist/Field/Field.d.ts.map +1 -1
  14. package/dist/Field/useFieldControl.d.ts +40 -0
  15. package/dist/Field/useFieldControl.d.ts.map +1 -0
  16. package/dist/InlineAlert/InlineAlert.d.ts +32 -0
  17. package/dist/InlineAlert/InlineAlert.d.ts.map +1 -0
  18. package/dist/NavBar/NavBar.d.ts +10 -2
  19. package/dist/NavBar/NavBar.d.ts.map +1 -1
  20. package/dist/NavTree/NavGroup.d.ts +22 -0
  21. package/dist/NavTree/NavGroup.d.ts.map +1 -0
  22. package/dist/NavTree/NavItem.d.ts +25 -0
  23. package/dist/NavTree/NavItem.d.ts.map +1 -0
  24. package/dist/NavTree/NavTree.d.ts +23 -0
  25. package/dist/NavTree/NavTree.d.ts.map +1 -0
  26. package/dist/Skeleton/Skeleton.d.ts +22 -0
  27. package/dist/Skeleton/Skeleton.d.ts.map +1 -0
  28. package/dist/SkipLink/SkipLink.d.ts +25 -0
  29. package/dist/SkipLink/SkipLink.d.ts.map +1 -0
  30. package/dist/Table/Table.d.ts +6 -3
  31. package/dist/Table/Table.d.ts.map +1 -1
  32. package/dist/Table/TableBody.d.ts +7 -4
  33. package/dist/Table/TableBody.d.ts.map +1 -1
  34. package/dist/Table/TableCaption.d.ts +21 -0
  35. package/dist/Table/TableCaption.d.ts.map +1 -0
  36. package/dist/Table/TableCell.d.ts +15 -5
  37. package/dist/Table/TableCell.d.ts.map +1 -1
  38. package/dist/Table/TableHead.d.ts +5 -3
  39. package/dist/Table/TableHead.d.ts.map +1 -1
  40. package/dist/Table/TableHeaderCell.d.ts +5 -3
  41. package/dist/Table/TableHeaderCell.d.ts.map +1 -1
  42. package/dist/Table/TableRow.d.ts +5 -3
  43. package/dist/Table/TableRow.d.ts.map +1 -1
  44. package/dist/Toast/Announcement.d.ts +30 -0
  45. package/dist/Toast/Announcement.d.ts.map +1 -0
  46. package/dist/Toast/Toast.d.ts +16 -2
  47. package/dist/Toast/Toast.d.ts.map +1 -1
  48. package/dist/Toast/ToastProvider.d.ts.map +1 -1
  49. package/dist/Toast/ToastRegion.d.ts +16 -7
  50. package/dist/Toast/ToastRegion.d.ts.map +1 -1
  51. package/dist/Toast/status.d.ts +15 -0
  52. package/dist/Toast/status.d.ts.map +1 -0
  53. package/dist/Toast/useToast.d.ts +6 -1
  54. package/dist/Toast/useToast.d.ts.map +1 -1
  55. package/dist/Toolbar/Toolbar.d.ts +16 -4
  56. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  57. package/dist/a11y-meta.d.ts.map +1 -1
  58. package/dist/icons/IconChevronLeft.d.ts +7 -0
  59. package/dist/icons/IconChevronLeft.d.ts.map +1 -0
  60. package/dist/icons/IconLogOut.d.ts +7 -0
  61. package/dist/icons/IconLogOut.d.ts.map +1 -0
  62. package/dist/icons/IconMenu.d.ts +7 -0
  63. package/dist/icons/IconMenu.d.ts.map +1 -0
  64. package/dist/icons/IconSimCard.d.ts +7 -0
  65. package/dist/icons/IconSimCard.d.ts.map +1 -0
  66. package/dist/icons/index.d.ts +4 -0
  67. package/dist/icons/index.d.ts.map +1 -1
  68. package/dist/index.d.ts +30 -4
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +1373 -1032
  71. package/dist/manifest.json +1044 -240
  72. package/dist/patterns.json +671 -3
  73. package/dist/styles.css +1 -1
  74. package/docs/Announcement.md +37 -0
  75. package/docs/AppShell.md +46 -0
  76. package/docs/AspectRatio.md +1 -1
  77. package/docs/Banner.md +54 -0
  78. package/docs/Button.md +2 -1
  79. package/docs/Card.md +1 -1
  80. package/docs/Checkbox.md +1 -1
  81. package/docs/CopyButton.md +51 -0
  82. package/docs/CursorPagination.md +45 -0
  83. package/docs/DescriptionItem.md +1 -1
  84. package/docs/DescriptionList.md +1 -1
  85. package/docs/Dialog.md +1 -1
  86. package/docs/Field.md +5 -3
  87. package/docs/IconButton.md +1 -1
  88. package/docs/InlineAlert.md +53 -0
  89. package/docs/Input.md +1 -1
  90. package/docs/Menu.md +1 -1
  91. package/docs/MenuItem.md +1 -1
  92. package/docs/MenuSeparator.md +1 -1
  93. package/docs/NavBar.md +4 -2
  94. package/docs/NavGroup.md +40 -0
  95. package/docs/NavItem.md +40 -0
  96. package/docs/NavTree.md +39 -0
  97. package/docs/Pagination.md +1 -1
  98. package/docs/Panel.md +1 -1
  99. package/docs/Select.md +1 -1
  100. package/docs/Skeleton.md +50 -0
  101. package/docs/SkipLink.md +40 -0
  102. package/docs/Switch.md +1 -1
  103. package/docs/Tab.md +1 -1
  104. package/docs/TabList.md +1 -1
  105. package/docs/TabPanel.md +1 -1
  106. package/docs/Table.md +1 -1
  107. package/docs/TableBody.md +3 -2
  108. package/docs/TableCaption.md +39 -0
  109. package/docs/TableCell.md +5 -2
  110. package/docs/TableHead.md +2 -1
  111. package/docs/TableHeaderCell.md +2 -1
  112. package/docs/TableRow.md +2 -1
  113. package/docs/Tabs.md +1 -1
  114. package/docs/Tag.md +1 -1
  115. package/docs/Toast.md +5 -3
  116. package/docs/ToastProvider.md +1 -1
  117. package/docs/ToastRegion.md +4 -4
  118. package/docs/Toolbar.md +5 -3
  119. package/docs/Tooltip.md +1 -1
  120. package/llms.txt +39 -9
  121. package/package.json +2 -2
package/docs/MenuItem.md CHANGED
@@ -45,5 +45,5 @@ This component has no `--psi-menu-item-*` tokens — its styling binds scale tok
45
45
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
46
46
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
47
47
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
48
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
48
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
49
49
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
@@ -32,5 +32,5 @@ This component has no `--psi-menu-separator-*` tokens — its styling binds scal
32
32
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
33
33
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
34
34
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
35
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
35
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
36
36
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/NavBar.md CHANGED
@@ -9,7 +9,9 @@ Top navigation bar with brand, nav-link, and trailing-action slots.
9
9
  |---|---|---|---|---|
10
10
  | `brand` | `ReactNode` | — | no | Brand slot (wordmark / logo link), leading edge. |
11
11
  | `actions` | `ReactNode` | — | no | Trailing actions slot (theme switch, CTA). |
12
- | `children` | `ReactNode` | — | no | Nav links. |
12
+ | `children` | `ReactNode` | — | no | Nav links. The `<nav>` renders only when there are children, so a bar with a brand and actions and no links has no empty landmark (D88). |
13
+ | `navLabel` | `string` | — | no | Accessible name of the links' `<nav>` (its `aria-label`). Rest props land on the `<header>`, so this is the only way to label the `<nav>` (D88). |
14
+ | `fluid` | `boolean` | false | no | Lay the row out at full width: it drops `psi-container` (the centred max-width column) and keeps the `--psi-gutter` side padding. For an application header over a full-width frame (D88). |
13
15
  | `ref` | `Ref<HTMLElement>` | — | no | Forwarded ref to the header element. |
14
16
  | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
15
17
 
@@ -28,5 +30,5 @@ Override `--psi-navbar-*` custom properties at any scope; interactive states der
28
30
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
29
31
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
30
32
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
31
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
33
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
32
34
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
@@ -0,0 +1,40 @@
1
+ <!-- Generated by emit-docs.ts — do not edit. -->
2
+ # NavGroup
3
+
4
+ A collapsible group of `NavItem`s in a `NavTree` (D88). Controlled-only, like `Tabs` (D67): `open` and `onOpenChange` are required and nothing is remembered. Renders a native `<button aria-expanded aria-controls>` over a `<ul>` that is `hidden` when closed, so the items leave the layout and the accessibility tree while `aria-controls` keeps naming an element. The chevron turns with the state and is `aria-hidden`: the label and `aria-expanded` carry the meaning.
5
+
6
+ ## Props
7
+
8
+ | Prop | Type | Default | Required | Description |
9
+ |---|---|---|---|---|
10
+ | `label` | `ReactNode` | — | yes | The group's visible label, such as "Reports". |
11
+ | `open` | `boolean` | — | yes | Whether the group's items are shown. Controlled. |
12
+ | `onOpenChange` | `(open: boolean) => void` | — | yes | Called with the next state when the button is pressed. The group never changes `open` itself. |
13
+ | `children` | `ReactNode` | — | yes | `NavItem`s of the group. |
14
+ | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
15
+
16
+ ## Keyboard & assistive tech
17
+
18
+ | Keys | Behavior |
19
+ |---|---|
20
+ | Enter / Space | Activates the group's native button, which asks the parent to open or close the group through onOpenChange. |
21
+
22
+ Controlled-only: open and onOpenChange are required and the group never changes its own state. Renders a <button type="button"> with aria-expanded and aria-controls naming its <ul>, which carries `hidden` when closed, so the links leave the layout and the accessibility tree while aria-controls still resolves. The chevron is aria-hidden and turns on a duration token; the label and aria-expanded carry the meaning.
23
+
24
+ ## Theming
25
+
26
+ Override `--psi-nav-tree-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).
27
+
28
+
29
+
30
+ ## Rules
31
+
32
+ - One accent per visual group; everything else neutral or ghost.
33
+ - danger only for actions with real consequences.
34
+ - Sizes are px numbers (24|32|40|48), never S/M/L.
35
+ - Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.
36
+ - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
37
+ - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
38
+ - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
39
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
40
+ - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
@@ -0,0 +1,40 @@
1
+ <!-- Generated by emit-docs.ts — do not edit. -->
2
+ # NavItem
3
+
4
+ One entry of a `NavTree` (D88): an `<li>` that styles the single anchor inside it. Router apps pass their own link as the child, so it keeps its `href`, `aria-current` and handlers; `href` is for plain links and for presets, where the compose tree can hold only manifest components and a raw `<a>` is not one. The current page is never marked by colour alone: medium weight, a raised surface and a bar at the inline start.
5
+
6
+ ## Props
7
+
8
+ | Prop | Type | Default | Required | Description |
9
+ |---|---|---|---|---|
10
+ | `children` | `ReactNode` | — | yes | Without `href`: one anchor, your router's link — put `aria-current="page"` on the current one. With `href`: the link's text. |
11
+ | `href` | `string` | — | no | Render the anchor yourself (D88), for a plain link or a preset: the `<li>` holds `<a href>` with `children` as its text. Router apps leave it out and pass their own link as the child, so the link keeps its own handlers. |
12
+ | `current` | `boolean` | — | no | Mark the link as the current page (`aria-current="page"`). Only with `href`; a link you pass as a child carries its own `aria-current`. |
13
+ | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
14
+ | `ref` | `Ref<HTMLLIElement>` | — | no | Forwarded ref to the `<li>`. |
15
+
16
+ ## Keyboard & assistive tech
17
+
18
+ | Keys | Behavior |
19
+ |---|---|
20
+ | Enter | Follows the link inside it (native anchor behavior); the link is the consumer's router link. |
21
+
22
+ An <li> that styles the one anchor inside it. Router apps pass their own link as the child and set aria-current="page" on the current one; with href the NavItem renders the anchor itself (for plain links and presets) and current sets aria-current="page" on it. The current page is marked by weight, a raised surface and an inline-start bar, never by colour alone, and the focus ring is drawn inset on the anchor.
23
+
24
+ ## Theming
25
+
26
+ Override `--psi-nav-tree-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).
27
+
28
+
29
+
30
+ ## Rules
31
+
32
+ - One accent per visual group; everything else neutral or ghost.
33
+ - danger only for actions with real consequences.
34
+ - Sizes are px numbers (24|32|40|48), never S/M/L.
35
+ - Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.
36
+ - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
37
+ - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
38
+ - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
39
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
40
+ - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
@@ -0,0 +1,39 @@
1
+ <!-- Generated by emit-docs.ts — do not edit. -->
2
+ # NavTree
3
+
4
+ A navigation tree (D88): a `<nav aria-label>` around a `<ul>` of `NavItem`s and `NavGroup`s. Native elements only — *Tab* moves through the group buttons and the links of open groups; there is no roving focus and no `role="menu"` or `role="tree"`, because site navigation is a list of links, not a widget. Router-agnostic: pass your router's link inside each `NavItem`, with `aria-current="page"` on the current one.
5
+
6
+ ## Props
7
+
8
+ | Prop | Type | Default | Required | Description |
9
+ |---|---|---|---|---|
10
+ | `aria-label` | `string` | — | yes | Accessible name of the navigation landmark, such as "Main". Required: an app has more than one `<nav>` (a header's, a sidebar's), and an unnamed one is indistinguishable from the rest. Declared here rather than inherited so docgen keeps it in the manifest (D60). |
11
+ | `children` | `ReactNode` | — | yes | `NavItem`s and `NavGroup`s — each renders an `<li>`. |
12
+ | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
13
+ | `ref` | `Ref<HTMLElement>` | — | no | Forwarded ref to the `<nav>`. |
14
+
15
+ ## Keyboard & assistive tech
16
+
17
+ | Keys | Behavior |
18
+ |---|---|
19
+ | Tab / Shift+Tab | Moves through the group buttons and the links of open groups in document order. There is no roving focus and no arrow-key model: it is a list of links, not a menu or a tree widget. |
20
+
21
+ A native <nav> around a <ul>, named by the required aria-label (an app has more than one navigation landmark). No role="menu" or role="tree" is used (D88). Router-agnostic: the consumer passes its router's link inside each NavItem.
22
+
23
+ ## Theming
24
+
25
+ Override `--psi-nav-tree-*` custom properties at any scope; interactive states derive automatically (L - 0.04 hover, L - 0.08 active).
26
+
27
+
28
+
29
+ ## Rules
30
+
31
+ - One accent per visual group; everything else neutral or ghost.
32
+ - danger only for actions with real consequences.
33
+ - Sizes are px numbers (24|32|40|48), never S/M/L.
34
+ - Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.
35
+ - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
36
+ - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
37
+ - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
38
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
39
+ - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
@@ -29,5 +29,5 @@ This component has no `--psi-pagination-*` tokens — its styling binds scale to
29
29
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
30
30
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
31
31
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
32
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
32
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
33
33
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/Panel.md CHANGED
@@ -35,5 +35,5 @@ Override `--psi-panel-*` custom properties at any scope.
35
35
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
36
36
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
37
37
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
38
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
38
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
39
39
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/Select.md CHANGED
@@ -37,5 +37,5 @@ Override `--psi-select-*` custom properties at any scope; interactive states der
37
37
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
38
38
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
39
39
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
40
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
40
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
41
41
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
@@ -0,0 +1,50 @@
1
+ <!-- Generated by emit-docs.ts — do not edit. -->
2
+ # Skeleton
3
+
4
+ A loading placeholder. It is not a live region: it renders no `role` and no `aria-live`, and it is always `aria-hidden="true"`, so assistive tech skips it entirely. Put `aria-busy` on the region the skeleton stands for — that is what tells a screen reader the content is coming. Its width fills the container; size the container, not the skeleton. The pulse is an opacity animation that stops under `prefers-reduced-motion: reduce` (D86).
5
+
6
+ ## Props
7
+
8
+ | Prop | Type | Default | Required | Description |
9
+ |---|---|---|---|---|
10
+ | `variant` | `"text" \| "block"` | text | no | `text` renders `lines` stacked bars; `block` one box. |
11
+ | `lines` | `number` | 1 | no | Number of text lines; the last of several is shorter. Text only. |
12
+ | `size` | `24 \| 32 \| 40 \| 48` | 40 | no | Height of a block, from the size scale. Block only. |
13
+ | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
14
+ | `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the underlying `<div>`. |
15
+
16
+ ## Keyboard & assistive tech
17
+
18
+ | Keys | Behavior |
19
+ |---|---|
20
+
21
+
22
+ Not a live region: it renders no role and no aria-live, and it is always aria-hidden="true", so assistive tech skips it entirely. Put aria-busy="true" on the region the skeleton stands for (a table body, a card, a list) and remove it when the content arrives; that, not the skeleton, is what tells a screen reader the content is loading. Not focusable. The pulse is an opacity animation that stops under prefers-reduced-motion: reduce, because zeroing the duration of an infinite alternating animation flickers instead of stopping (D86).
23
+
24
+ ## Theming
25
+
26
+ Override `--psi-skeleton-*` custom properties at any scope.
27
+
28
+ ## Variant guidance
29
+
30
+ - **accent** — Primary action, draws attention. Submit, CTA, main action in a group.
31
+ - **accent-subtle** — Accent tone, lower visual weight. Selected state, active filter, soft CTA.
32
+ - **neutral** — Default, structurally present. Secondary actions, toolbar buttons.
33
+ - **neutral-subtle** — Minimal chrome. Inline actions, table row actions.
34
+ - **ghost** — No visible container until hover. Icon-only triggers, compact toolbars.
35
+ - **outline** — Bordered ghost — visible structure, no fill until hover. Marketing CTA, download button; hover fills accent.
36
+ - **danger** — Destructive action. Delete, remove, disconnect.
37
+ - **danger-subtle** — Destructive context, low urgency. Warning badges, soft destructive hints.
38
+ - **success | warning** — Status communication (Tag only). Status badges, labels.
39
+
40
+ ## Rules
41
+
42
+ - One accent per visual group; everything else neutral or ghost.
43
+ - danger only for actions with real consequences.
44
+ - Sizes are px numbers (24|32|40|48), never S/M/L.
45
+ - Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.
46
+ - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
47
+ - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
48
+ - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
49
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
50
+ - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
@@ -0,0 +1,40 @@
1
+ <!-- Generated by emit-docs.ts — do not edit. -->
2
+ # SkipLink
3
+
4
+ A link to the page's main content, invisible until it takes keyboard focus and then fixed at the inline-start top corner on a surface, drawing the shared focus ring (D88). Render it first in the document. No script: native fragment navigation moves focus to a target that is focusable, so the next *Tab* continues from inside it. `AppShell`'s `main` is one (`tabIndex={-1}`); a bare `<main>` is not, so give it one. A modal `Dialog` makes the rest of the document inert, so the link never needs to beat one. A `ToastRegion` is on the native top layer, which paints above everything whatever its `z-index`: do not place one at `top-start` (its default is `bottom-end`) in an app with a skip link, or it covers the focused link.
5
+
6
+ ## Props
7
+
8
+ | Prop | Type | Default | Required | Description |
9
+ |---|---|---|---|---|
10
+ | `href` | `string` | — | yes | Fragment of the element to skip to, such as `"#main"`. The target must be focusable (`tabIndex={-1}` is enough), or focus does not move. |
11
+ | `children` | `ReactNode` | — | yes | Visible text, such as "Skip to content". |
12
+ | `ref` | `Ref<HTMLAnchorElement>` | — | no | Forwarded ref to the anchor. |
13
+ | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
14
+
15
+ ## Keyboard & assistive tech
16
+
17
+ | Keys | Behavior |
18
+ |---|---|
19
+ | Tab | Takes focus and becomes visible at the inline-start top corner, drawing the shared focus ring. Put it first in the document so it is the first tab stop. |
20
+ | Enter | Follows the fragment href: focus moves to the target, and the next Tab continues from inside it. |
21
+
22
+ A native anchor, visually hidden until it is focused (:focus-visible) and with no script (D88). Fragment navigation moves focus only to a focusable target, so the element named by href must be focusable: tabIndex={-1} is enough, and AppShell's main has it. A modal Dialog makes the link inert, so it never needs to beat one. A ToastRegion is on the native top layer and paints over the focused link whatever its z-index: do not place one at top-start in an app with a skip link.
23
+
24
+ ## Theming
25
+
26
+ Override `--psi-skip-link-*` custom properties at any scope.
27
+
28
+
29
+
30
+ ## Rules
31
+
32
+ - One accent per visual group; everything else neutral or ghost.
33
+ - danger only for actions with real consequences.
34
+ - Sizes are px numbers (24|32|40|48), never S/M/L.
35
+ - Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.
36
+ - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
37
+ - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
38
+ - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
39
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
40
+ - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/Switch.md CHANGED
@@ -33,5 +33,5 @@ Override `--psi-switch-*` custom properties at any scope.
33
33
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
34
34
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
35
35
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
36
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
36
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
37
37
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/Tab.md CHANGED
@@ -36,5 +36,5 @@ This component has no `--psi-tab-*` tokens — its styling binds scale tokens on
36
36
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
37
37
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
38
38
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
39
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
39
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
40
40
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/TabList.md CHANGED
@@ -37,5 +37,5 @@ This component has no `--psi-tab-list-*` tokens — its styling binds scale toke
37
37
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
38
38
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
39
39
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
40
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
40
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
41
41
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/TabPanel.md CHANGED
@@ -35,5 +35,5 @@ This component has no `--psi-tab-panel-*` tokens — its styling binds scale tok
35
35
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
36
36
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
37
37
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
38
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
38
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
39
39
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/Table.md CHANGED
@@ -34,5 +34,5 @@ Override `--psi-table-*` custom properties at any scope; interactive states deri
34
34
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
35
35
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
36
36
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
37
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
37
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
38
38
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/TableBody.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <!-- Generated by emit-docs.ts — do not edit. -->
2
2
  # TableBody
3
3
 
4
- `<tbody>` wrapper.
4
+ `<tbody>` wrapper. Remaining attributes land on it (D85) — `aria-busy` while a page of rows loads, say.
5
5
 
6
6
  ## Props
7
7
 
@@ -9,6 +9,7 @@
9
9
  |---|---|---|---|---|
10
10
  | `children` | `ReactNode` | — | yes | The body TableRows. |
11
11
  | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
12
+ | `ref` | `Ref<HTMLTableSectionElement>` | — | no | Forwarded ref to the `<tbody>`. |
12
13
 
13
14
  ## Theming
14
15
 
@@ -25,5 +26,5 @@ This component has no `--psi-table-body-*` tokens — its styling binds scale to
25
26
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
26
27
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
27
28
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
28
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
29
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
29
30
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
@@ -0,0 +1,39 @@
1
+ <!-- Generated by emit-docs.ts — do not edit. -->
2
+ # TableCaption
3
+
4
+ `<caption>` — the table's name, above it, and an optional detail line (D85). It names the table for assistive tech, which an `aria-label` on the `Table` would also do, but a caption is visible. A token-paged list has no page numbers to land on, so after a page change the consumer moves focus here: give it `tabIndex={-1}`, put the range in `detail`, and call `focus()` on the ref. The ring is the shared one (D82), drawn inside the caption's box. Remaining attributes land on the element.
5
+
6
+ ## Props
7
+
8
+ | Prop | Type | Default | Required | Description |
9
+ |---|---|---|---|---|
10
+ | `children` | `ReactNode` | — | yes | The name line — what the table lists. Also the table's accessible name. |
11
+ | `detail` | `ReactNode` | — | no | An optional second line: the range shown, say — "Rows 51–100 of more than 100,000". |
12
+ | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
13
+ | `ref` | `Ref<HTMLTableCaptionElement>` | — | no | Forwarded ref to the `<caption>`, so focus can be moved to it. |
14
+
15
+ ## Keyboard & assistive tech
16
+
17
+ | Keys | Behavior |
18
+ |---|---|
19
+ | Tab | Not a tab stop. With tabIndex={-1} the consumer moves focus to it after a page change, and the shared ring is drawn inside the caption. |
20
+
21
+ Renders <caption>, which names the table for assistive tech and is visible, unlike an aria-label on Table. Put the range in `detail` ("Rows 51–100 of more than 100,000") so that moving focus to the caption after a token-paged page change reads the new range (D85). Remaining attributes land on the element.
22
+
23
+ ## Theming
24
+
25
+ This component has no `--psi-table-caption-*` tokens — its styling binds scale tokens only, so there is nothing component-scoped to override. Theme changes reach it through the semantic tokens of its children and surroundings.
26
+
27
+
28
+
29
+ ## Rules
30
+
31
+ - One accent per visual group; everything else neutral or ghost.
32
+ - danger only for actions with real consequences.
33
+ - Sizes are px numbers (24|32|40|48), never S/M/L.
34
+ - Typography tokens are --psi-text-{size}-{lineHeight}-{weight}.
35
+ - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
36
+ - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
37
+ - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
38
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
39
+ - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/TableCell.md CHANGED
@@ -1,15 +1,18 @@
1
1
  <!-- Generated by emit-docs.ts — do not edit. -->
2
2
  # TableCell
3
3
 
4
- `<td>`. `numeric` means right-aligned *and* tabular — a column that aligns but whose digits jitter between rows defeats the purpose.
4
+ `<td>`, or `<th scope="row">` with `rowHeader`. `numeric` means right-aligned *and* tabular — a column that aligns but whose digits jitter between rows defeats the purpose. Remaining attributes land on the cell (D85).
5
5
 
6
6
  ## Props
7
7
 
8
8
  | Prop | Type | Default | Required | Description |
9
9
  |---|---|---|---|---|
10
10
  | `numeric` | `boolean` | — | no | Right-aligns and renders tabular figures (D62). |
11
+ | `colSpan` | `number` | — | no | Columns the cell spans — an empty-state row spanning the table (D85). |
12
+ | `rowHeader` | `boolean` | — | no | Renders `<th scope="row">` instead of `<td>`: the cell that names its row, an identifier say, so a screen reader reads it with every cell in the row (D85). Styled as a cell, at medium weight. |
11
13
  | `children` | `ReactNode` | — | no | Cell content. |
12
14
  | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
15
+ | `ref` | `Ref<HTMLTableCellElement>` | — | no | Forwarded ref to the cell element. |
13
16
 
14
17
  ## Theming
15
18
 
@@ -26,5 +29,5 @@ This component has no `--psi-table-cell-*` tokens — its styling binds scale to
26
29
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
27
30
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
28
31
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
29
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
32
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
30
33
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/TableHead.md CHANGED
@@ -9,6 +9,7 @@
9
9
  |---|---|---|---|---|
10
10
  | `children` | `ReactNode` | — | yes | The header TableRow. |
11
11
  | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
12
+ | `ref` | `Ref<HTMLTableSectionElement>` | — | no | Forwarded ref to the `<thead>`. |
12
13
 
13
14
  ## Theming
14
15
 
@@ -25,5 +26,5 @@ This component has no `--psi-table-head-*` tokens — its styling binds scale to
25
26
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
26
27
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
27
28
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
28
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
29
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
29
30
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
@@ -11,6 +11,7 @@
11
11
  | `numeric` | `boolean` | — | no | Right-aligns and renders tabular figures (D62). |
12
12
  | `children` | `ReactNode` | — | no | Header label. |
13
13
  | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
14
+ | `ref` | `Ref<HTMLTableCellElement>` | — | no | Forwarded ref to the `<th>`. |
14
15
 
15
16
  ## Theming
16
17
 
@@ -27,5 +28,5 @@ This component has no `--psi-table-header-cell-*` tokens — its styling binds s
27
28
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
28
29
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
29
30
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
30
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
31
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
31
32
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/TableRow.md CHANGED
@@ -11,6 +11,7 @@
11
11
  | `selectLabel` | `string` | — | no | Accessible name for this row's selection checkbox. |
12
12
  | `children` | `ReactNode` | — | yes | The row's cells. |
13
13
  | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
14
+ | `ref` | `Ref<HTMLTableRowElement>` | — | no | Forwarded ref to the `<tr>`. |
14
15
 
15
16
  ## Theming
16
17
 
@@ -27,5 +28,5 @@ This component has no `--psi-table-row-*` tokens — its styling binds scale tok
27
28
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
28
29
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
29
30
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
30
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
31
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
31
32
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/Tabs.md CHANGED
@@ -37,5 +37,5 @@ Override `--psi-tabs-*` custom properties at any scope; interactive states deriv
37
37
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
38
38
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
39
39
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
40
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
40
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
41
41
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/Tag.md CHANGED
@@ -47,5 +47,5 @@ Override `--psi-tag-*` custom properties at any scope.
47
47
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
48
48
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
49
49
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
50
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
50
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
51
51
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
package/docs/Toast.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <!-- Generated by emit-docs.ts — do not edit. -->
2
2
  # Toast
3
3
 
4
- A single transient notification card (D64). Controlled and presentational: it holds no state, runs no timer, and never removes itself — `onDismiss` reports and the owner disposes, exactly as Dialog (D50) and Menu (D53) do. Toast does not carry its own `role`/`aria-live`. Politeness belongs to the two persistent wrappers inside ToastRegion, because a live region must pre-exist the content it announces; a role on the toast itself would reintroduce the mount-with-content problem one level down. Composed by hand with ToastRegion for full control, or driven by ToastProvider's queue.
4
+ A single transient notification card (D64). Controlled and presentational: it holds no state, runs no timer, and never removes itself — `onDismiss` reports and the owner disposes, exactly as Dialog (D50) and Menu (D53) do. Toast does not carry its own `role`/`aria-live`. Politeness belongs to the two persistent wrappers inside ToastRegion, because a live region must pre-exist the content it announces; a role on the toast itself would reintroduce the mount-with-content problem one level down. `politeness` (D83) only tells the region which wrapper to use. Composed by hand with ToastRegion for full control, or driven by ToastProvider's queue.
5
5
 
6
6
  ## Props
7
7
 
@@ -11,6 +11,8 @@ A single transient notification card (D64). Controlled and presentational: it ho
11
11
  | `children` | `ReactNode` | — | yes | The message. |
12
12
  | `action` | `ReactNode` | — | no | Trailing affordance — a ghost Button (Undo, Retry, View). |
13
13
  | `onDismiss` | `(() => void)` | — | no | When provided, renders the dismiss button and calls this. Toast does not remove itself; the owner disposes. |
14
+ | `politeness` | `"polite" \| "assertive"` | — | no | Which live wrapper ToastRegion routes this toast into (D83). Unset, the variant decides: neutral and success are polite, warning and danger assertive. Set it when the kind of event, not its tone, decides — the outcome of the user's own action may interrupt even when it succeeded. |
15
+ | `statusLabel` | `string \| null` | — | no | The visually hidden status word (D83). A string replaces the default ("Success:", "Warning:", "Error:") — pass the translated word; `null` drops it. Unset keeps the default, and `neutral` has none. |
14
16
  | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
15
17
  | `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the underlying `<div>`. |
16
18
 
@@ -21,7 +23,7 @@ A single transient notification card (D64). Controlled and presentational: it ho
21
23
  | Tab | Reaches the action and the dismiss button in DOM order. Esc is not a dismissal — a toast is not modal and traps nothing. |
22
24
  | Enter / Space | Activates the focused action or dismiss button. |
23
25
 
24
- Presentational and controlled (D64): onDismiss reports and the owner disposes; Toast never removes itself. It carries no role/aria-live of its own — politeness belongs to ToastRegion's two persistent wrappers. The variant's meaning is announced by a visually hidden status word ("Success:", "Warning:", "Error:"), never by colour and icon shape alone; the icon is aria-hidden. neutral has no status and gets no prefix. The dismiss button requires no props — it is labelled "Dismiss notification".
26
+ Presentational and controlled (D64): onDismiss reports and the owner disposes; Toast never removes itself. It carries no role/aria-live of its own — politeness belongs to ToastRegion's two persistent wrappers. The variant's meaning is announced by a visually hidden status word ("Success:", "Warning:", "Error:"), never by colour and icon shape alone; the icon is aria-hidden. neutral has no status and gets no prefix. statusLabel replaces that word with a translated one, or drops it with null (D83). politeness tells ToastRegion which wrapper speaks the toast; unset, the variant decides. The dismiss button requires no props — it is labelled "Dismiss notification".
25
27
 
26
28
  ## Theming
27
29
 
@@ -48,5 +50,5 @@ Override `--psi-toast-*` custom properties at any scope.
48
50
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
49
51
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
50
52
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
51
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
53
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
52
54
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
@@ -36,5 +36,5 @@ This component has no `--psi-toast-provider-*` tokens — its styling binds scal
36
36
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
37
37
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
38
38
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
39
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
39
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
40
40
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.
@@ -1,7 +1,7 @@
1
1
  <!-- Generated by emit-docs.ts — do not edit. -->
2
2
  # ToastRegion
3
3
 
4
- The positioned live region that holds the toast stack (D64). Two things here are load-bearing and both look like details: 1. **The two live wrappers are always rendered**, empty queue included. A live region announces mutations to a subtree that already existed; a wrapper that mounts together with its first toast reads as a new subtree, and that first toast is never announced. 2. **`popover="manual"`, not `"auto"`.** Manual puts the region in the native top layer without light dismiss. The top layer is required because Dialog uses showModal() — also top layer — so a fixed region at --psi-z-overlay would paint *under* the modal backdrop, hiding the confirmation for the action a user just took inside a dialog. And `auto` would be dismissed by the very click that raised the toast. Routing reads `variant` off each child, the same Children.map technique Table uses for select-all injection. Consequence of the split, visible in the `InRegion` VR baseline: the stack is grouped by politeness, not strictly chronological — every assertive toast sorts below every polite one regardless of arrival order. Chronological order is preserved *within* each group. Keeping it exact across both would mean one live region with a politeness that changes per message, which is the thing the two wrappers exist to avoid. Accepted: at `limit` 3 the grouping reads as severity ordering, and the newest toast still lands nearest the screen edge within its group.
4
+ The positioned live region that holds the toast stack (D64). Two things here are load-bearing and both look like details: 1. **The two live wrappers are always rendered**, empty queue included. A live region announces mutations to a subtree that already existed; a wrapper that mounts together with its first toast reads as a new subtree, and that first toast is never announced. 2. **`popover="manual"`, not `"auto"`.** Manual puts the region in the native top layer without light dismiss. The top layer is required because Dialog uses showModal() — also top layer — so a fixed region at --psi-z-overlay would paint *under* the modal backdrop, hiding the confirmation for the action a user just took inside a dialog. And `auto` would be dismissed by the very click that raised the toast. Routing reads `politeness`, then `variant`, off each child — the same Children.map technique Table uses for select-all injection. `politeness` wins (D83): the owner of an announcement knows what kind of event it is, and tone is only the default. Consequence of the split, visible in the `InRegion` VR baseline: the stack is grouped by politeness, not strictly chronological — every assertive toast sorts below every polite one regardless of arrival order. Chronological order is preserved *within* each group. Keeping it exact across both would mean one live region with a politeness that changes per message, which is the thing the two wrappers exist to avoid. Accepted: at `limit` 3 the grouping reads as severity ordering, and the newest toast still lands nearest the screen edge within its group. Remaining HTML attributes, `data-*` included, are spread onto the root element (D83). That is how an application marks the region `data-react-aria-top-layer`, so that a React Aria overlay — which hides everything outside itself with `aria-hidden` — leaves the two live regions exposed. The region's own attributes always win.
5
5
 
6
6
  ## Props
7
7
 
@@ -9,7 +9,7 @@ The positioned live region that holds the toast stack (D64). Two things here are
9
9
  |---|---|---|---|---|
10
10
  | `placement` | `"top-start" \| "top-end" \| "bottom-start" \| "bottom-end"` | bottom-end | no | Corner the stack occupies. |
11
11
  | `aria-label` | `string` | Notifications | no | Accessible name for the region. |
12
- | `children` | `ReactNode` | — | yes | The toast stack — `Toast` elements, routed to a live wrapper by variant. |
12
+ | `children` | `ReactNode` | — | yes | The stack — `Toast` and `Announcement` elements, each routed to a live wrapper by its `politeness`, or by a Toast's variant when it names none. |
13
13
  | `className` | `string` | — | no | Additional CSS class name(s) merged onto the component's root element. |
14
14
  | `ref` | `Ref<HTMLDivElement>` | — | no | Forwarded ref to the region element. |
15
15
 
@@ -19,7 +19,7 @@ The positioned live region that holds the toast stack (D64). Two things here are
19
19
  |---|---|
20
20
  | Tab | Moves into the stacked toasts' controls in DOM order; the region itself is not focusable. |
21
21
 
22
- Renders two always-present live wrappers — role="status"/aria-live="polite" and role="alert"/aria-live="assertive" — and routes each toast into one by variant (neutral/success polite, warning/danger assertive). Both stay in the DOM when the queue is empty: a live region announces mutations to a subtree that already existed, so a wrapper mounting with its first toast would leave that toast unannounced. Sits on the native top layer via popover="manual", so a toast raised from inside a modal Dialog is still painted above the backdrop and still announced — though showModal() makes everything outside the dialog inert, so it cannot be clicked until the dialog closes; manual (not auto) means no light dismiss, so the click that raised the toast cannot close it. The region is click-through (pointer-events: none) and each toast takes its own clicks back.
22
+ Renders two always-present live wrappers — role="status"/aria-live="polite" and role="alert"/aria-live="assertive" — and routes each child into one. A Toast or an Announcement that names its politeness goes where it says (D83); a Toast that names none is routed by variant (neutral/success polite, warning/danger assertive). Whatever it holds, the region contains exactly two live regions. Remaining HTML attributes, data-* included, land on the root element, so it can be marked data-react-aria-top-layer and stay exposed while a React Aria overlay is open. Both stay in the DOM when the queue is empty: a live region announces mutations to a subtree that already existed, so a wrapper mounting with its first toast would leave that toast unannounced. Sits on the native top layer via popover="manual", so a toast raised from inside a modal Dialog is still painted above the backdrop and still announced — though showModal() makes everything outside the dialog inert, so it cannot be clicked until the dialog closes; manual (not auto) means no light dismiss, so the click that raised the toast cannot close it. The region is click-through (pointer-events: none) and each toast takes its own clicks back.
23
23
 
24
24
  ## Theming
25
25
 
@@ -36,5 +36,5 @@ This component has no `--psi-toast-region-*` tokens — its styling binds scale
36
36
  - Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.
37
37
  - --psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).
38
38
  - Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.
39
- - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field).
39
+ - Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination ("Rows per page" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as="form" align="end" of Fields (D87).
40
40
  - Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group.