@uxelle/skills 0.2.1-beta.0 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +3 -5
  2. package/dist/index.js +106 -96
  3. package/index.json +106 -96
  4. package/package.json +1 -1
  5. package/skills/uxelle-components/ChoiceChip.md +1 -1
  6. package/skills/uxelle-components/ChoiceChipGroup.md +3 -3
  7. package/skills/uxelle-components/FilterChip.md +2 -2
  8. package/skills/uxelle-components/FilterChipGroup.md +1 -1
  9. package/skills/uxelle-components/Hero.md +2 -2
  10. package/skills/uxelle-components/Icon.md +4 -4
  11. package/skills/uxelle-components/Image.md +10 -3
  12. package/skills/uxelle-components/MultiSelect.md +3 -2
  13. package/skills/uxelle-components/NavigationSide.md +3 -3
  14. package/skills/uxelle-components/NavigationSideItem.md +4 -4
  15. package/skills/uxelle-components/NavigationSideSubItem.md +3 -3
  16. package/skills/uxelle-components/SKILL.md +9 -2
  17. package/skills/uxelle-components/Select.md +2 -1
  18. package/skills/uxelle-components/StatTile.md +13 -42
  19. package/skills/uxelle-components/Table.md +7 -4
  20. package/skills/uxelle-components/getting-started.md +85 -0
  21. package/skills/uxelle-design-harness/SKILL.md +44 -20
  22. package/skills/uxelle-design-harness/a2ui.md +31 -2
  23. package/skills/uxelle-design-harness/density.md +138 -0
  24. package/skills/uxelle-design-harness/how-to-accessibility.md +3 -1
  25. package/skills/uxelle-design-harness/how-to-color.md +6 -3
  26. package/skills/uxelle-design-harness/how-to-host.md +8 -21
  27. package/skills/uxelle-design-harness/how-to-page-layout.md +51 -6
  28. package/skills/uxelle-design-harness/principles.md +10 -6
  29. package/skills/uxelle-design-harness/recipe-app-chrome.md +20 -16
  30. package/skills/uxelle-design-harness/recipe-card-grid.md +14 -5
  31. package/skills/uxelle-design-harness/recipe-cta-band.md +16 -6
  32. package/skills/uxelle-design-harness/recipe-dashboard-overview.md +21 -7
  33. package/skills/uxelle-design-harness/recipe-data-table-page.md +30 -137
  34. package/skills/uxelle-design-harness/recipe-feature-section.md +9 -7
  35. package/skills/uxelle-design-harness/recipe-footer.md +7 -4
  36. package/skills/uxelle-design-harness/recipe-form-section.md +2 -2
  37. package/skills/uxelle-design-harness/recipe-hero.md +35 -15
  38. package/skills/uxelle-design-harness/recipe-landing-page.md +27 -4
  39. package/skills/uxelle-design-harness/recipe-logo-wall.md +11 -9
  40. package/skills/uxelle-design-harness/recipe-multi-step-flow.md +5 -3
  41. package/skills/uxelle-design-harness/recipe-page-header.md +14 -4
  42. package/skills/uxelle-design-harness/recipe-page-shell.md +2 -2
  43. package/skills/uxelle-design-harness/recipe-pricing.md +5 -2
  44. package/skills/uxelle-design-harness/recipe-query-bar.md +21 -5
  45. package/skills/uxelle-design-harness/recipe-record-detail.md +5 -2
  46. package/skills/uxelle-design-harness/recipe-settings-page.md +3 -3
  47. package/skills/uxelle-design-harness/recipe-stat-callouts.md +14 -7
  48. package/skills/uxelle-design-harness/recipe-states.md +9 -4
  49. package/skills/uxelle-design-harness/recipe-summary-list.md +5 -3
  50. package/skills/uxelle-design-harness/recipe-template.md +6 -0
  51. package/skills/uxelle-design-harness/recipe-testimonial.md +5 -3
  52. package/skills/uxelle-design-harness/spacing-steps.md +17 -8
  53. package/skills/uxelle-design-harness/tokens.md +45 -4
@@ -17,10 +17,10 @@ Destination row for a vertical side navigation rail. Shows a leading icon, label
17
17
  | trailingSlotContent | `ReactNode` | `undefined` | Content for the trailing region, typically a `LabelBadge`, short text, or a decorative icon. Rendered only when `trailingSlot` is true, and it must stay non-interactive because it sits inside the row's own link or button. |
18
18
  | subNavigationItemsSlot | `ReactNode` | `undefined` | Nested `NavigationSideSubItem` rows revealed when `accordion` is true; takes precedence over `children`. On the collapsed rail they are re-rendered as a `List` of `ListItem`s inside the flyout. |
19
19
  | children | `ReactNode` | `undefined` | Nested `NavigationSideSubItem` rows revealed when `accordion` is true, used when `subNavigationItemsSlot` is omitted. Not the row label — that is `label`. On the collapsed rail they become a `List` of `ListItem`s in the flyout. |
20
- | activated | `boolean` | `false` | Marks this row as the current page: filled surface plus a leading indicator bar. Destination rows also get `aria-current="page"`; accordion parents do not, so set it on the matching child instead. |
20
+ | activated | `boolean` | `false` | Marks this row as the current page: filled surface plus a leading indicator bar. Destination rows also get `aria-current="page"`; accordion parents do not, so set it on the matching child instead. When the rail is collapsed, an accordion parent also shows this selected appearance if any nested `NavigationSideSubItem` is activated (pass the sub item directly, not wrapped in a custom component). |
21
21
  | navCollapsed | `boolean` | `false` | Icon-only rail layout: hides the label, the trailing slot, and the inline accordion panel, and shows the label in a tooltip on hover and focus. Inherited from the enclosing `NavigationSideGroup` when omitted. |
22
22
  | accordion | `boolean` | `false` | Turns the row into an expandable parent with a trailing chevron that reveals its nested rows. The control is always a button, so `href` is ignored. |
23
- | open | `boolean` | `undefined` | Controlled expanded state of the accordion panel (or of the collapsed-rail flyout). Pass with `onOpenChange`; omit to use `defaultOpen` instead. |
23
+ | open | `boolean` | `undefined` | Controlled expanded state of the accordion panel (or of the collapsed-rail flyout). Pass with `onOpenChange`; omit to use `defaultOpen` instead. Collapsing the rail hides an open nested list even if this stays true; pass `true` again (or let the user open the flyout) to show it while collapsed. |
24
24
  | defaultOpen | `boolean` | `false` | Whether the accordion panel starts expanded when `open` is omitted. |
25
25
  | onOpenChange | `(open: boolean) => void` | `undefined` | Called with the next expanded state each time the accordion parent toggles. |
26
26
  | notificationBadge | `boolean` | `false` | Overlays an unread dot on the leading icon. The dot itself is decorative; the state is folded into the control name as `"{label}, notifications"`. |
@@ -48,8 +48,8 @@ Destination row for a vertical side navigation rail. Shows a leading icon, label
48
48
  - Do not use as a top or header nav link (`NavLink`). Nested accordion destinations use `NavigationSideSubItem`.
49
49
  - Compose inside `NavigationSideGroup` (a `<nav>` list). This row is a list item; other HTML attributes (`aria-*`, `data-*`, `id`) go to the interactive control, and `className` is on the row wrapper.
50
50
  - Renders as an anchor when `href` is set; otherwise a button. Accordion parents are **not** destinations: `href` is ignored and the control is always a button. Put a section landing page in a `NavigationSideSubItem` child instead.
51
- - Set `activated` for the current page — fill and leading indicator. Destinations also set `aria-current="page"`; accordion parents do not. When a child is the current page, set `activated` on that child, not only on the parent.
52
- - Set `accordion` for a parent that reveals `subNavigationItemsSlot` (or `children`): `NavigationSideSubItem` rows (`href` for destinations). Omit `open` for uncontrolled (`defaultOpen` + `onOpenChange`). The trigger is a button with `aria-expanded` / `aria-controls`; the nested list animates open and closed (inert and `aria-hidden` when collapsed). When `navCollapsed` is also true, those rows open in a disclosure flyout (`Menu` `pattern="disclosure"`, `direction="Right Top"`) as a `List` of interactive `ListItem`s. Keyboard: Enter/Space toggles; Tab moves through destinations; Tab-out and Escape close and return focus to the trigger.
51
+ - Set `activated` for the current page — fill and leading indicator. Destinations also set `aria-current="page"`; accordion parents do not. When a child is the current page, set `activated` on that child, not only on the parent. Collapsing the rail still shows the selected appearance on that accordion parent so the section remains visible on the icon-only rail.
52
+ - Set `accordion` for a parent that reveals `subNavigationItemsSlot` (or `children`): `NavigationSideSubItem` rows (`href` for destinations). Omit `open` for uncontrolled (`defaultOpen` + `onOpenChange`). The trigger is a button with `aria-expanded` / `aria-controls`; the nested list animates open and closed (inert and `aria-hidden` when collapsed). Collapsing the rail hides any open nested list (and moves keyboard focus to the parent if a nested row had it). Expanding restores that open state unless the flyout was dismissed while collapsed. Opening a disclosure flyout from the collapsed parent is still available. When `navCollapsed` is also true, those rows open in a disclosure flyout (`Menu` `pattern="disclosure"`, `direction="Right Top"`) as a `List` of interactive `ListItem`s. Keyboard: Enter/Space toggles; Tab moves through destinations; Tab-out and Escape close and return focus to the trigger.
53
53
  - The leading icon is always shown. Group collapsed layout with `NavigationSideGroup` (`collapsed`) or set `navCollapsed`.
54
54
  - When `navCollapsed`, the visible label is hidden; pass `label` (or `aria-label`) for the accessible name. A tooltip shows the label on hover and focus.
55
55
  - When `notificationBadge` is true, the dot stays decorative; unread state is included in the control name (`"{label}, notifications"`). Override with `aria-label` when you need a different name.
@@ -14,7 +14,7 @@ Nested destination row under an accordion side-nav parent.
14
14
  | leadingIcon | `boolean` | `true` | Renders the leading icon before the label. Set `false` for text-only nested rows, which is the usual look under an accordion parent. |
15
15
  | leadingIconName | `string` | `"outbound"` | Material Symbol name for the leading icon. Ignored when `leadingIcon` is false. |
16
16
  | iconVariant | `"sharpUnfilled" \| "sharpFilled"` | `"sharpUnfilled"` | Fill style for the leading icon: `sharpUnfilled` for the outline glyph, `sharpFilled` for the solid one. |
17
- | activated | `boolean` | `false` | Marks this row as the current page: filled surface, leading indicator bar, and `aria-current="page"`. Set it here rather than on the accordion parent. |
17
+ | activated | `boolean` | `false` | Marks this row as the current page: filled surface, leading indicator bar, and `aria-current="page"`. Set it here rather than on the accordion parent. When the rail is collapsed, that parent also shows the selected appearance. |
18
18
  | href | `string` | `undefined` | Destination URL, which renders the row as an anchor instead of a button. Omit for a button that only reports clicks through `onClick`. |
19
19
  | className | `string` | `—` | |
20
20
 
@@ -34,6 +34,6 @@ Nested destination row under an accordion side-nav parent.
34
34
 
35
35
  - Use as a child of an accordion `NavigationSideItem`, not as a top-level rail row (`NavigationSideItem`). Compose inside `NavigationSideGroup` so this row is a list item in the side `nav`.
36
36
  - Renders as an anchor when `href` is set; otherwise a button. `className` is on the row wrapper; other HTML attributes (`aria-*`, `data-*`, `id`) go to the interactive control.
37
- - Set `activated` for the current page — fill, leading indicator, and `aria-current="page"`.
37
+ - Set `activated` for the current page — fill, leading indicator, and `aria-current="page"`. When the rail is collapsed, the accordion parent also shows the selected appearance so the section remains visible on the icon-only rail.
38
38
  - Set `leadingIcon={false}` to hide the icon.
39
- - When the parent accordion is collapsed (`navCollapsed`), this row is presented as a `ListItem` in the disclosure flyout (a list of links, not a menu).
39
+ - When the parent accordion is collapsed (`navCollapsed`), this row is presented as a `ListItem` in the disclosure flyout (a list of links, not a menu). Collapsing the rail hides an open nested list; the parent can still open the flyout.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: uxelle-components
3
- description: Reference for uxElle Generative Product Foundation (GPF) components. Use when generating UI with uxElle, implementing designs, building layouts, or when the user mentions uxElle components, design system, or enterprise UI.
3
+ description: Reference for uxElle Generative Product Foundation (GPF) components. Use when generating UI with uxElle, implementing designs, building layouts, installing or setting up a React or Next.js app with @uxelle/components, loading theme CSS, or when the user mentions uxElle components or enterprise UI.
4
4
  ---
5
5
 
6
6
  # uxElle Components — Generative Workflow Reference
@@ -10,6 +10,7 @@ Reference for accurately using uxElle Generative Product Foundation (GPF) compon
10
10
  ## Context discipline
11
11
 
12
12
  - Load this SKILL.md when working with uxElle components.
13
+ - New app or host without packages and CSS? Read [getting-started.md](getting-started.md) first, then [how-to-host.md](../uxelle-design-harness/how-to-host.md). Skip getting-started when the host already loads theme CSS, component CSS, and `data-*`.
13
14
  - Load individual component files (e.g. Button.md, ListItem.md) only when you need that component's props or examples.
14
15
  - Do not preload all component files.
15
16
 
@@ -220,7 +221,12 @@ Use `List` + `ListItem` for menus and lists. Prefer `centerText` / `bottomText`
220
221
  Prefer **`ListControls`** when each row has title/description plus an embedded trailing control (settings and exclusive choices). Page layout: harness [recipe-settings-page.md](../uxelle-design-harness/recipe-settings-page.md). Set **`controlType`** to **`switch`**, **`checkbox`**, or **`radio`**. Same selection roles as **`RadioGroup`** / **`CheckboxGroup`** for checkbox/radio, list layout instead of stacked form options. Use **`interactive`** **`ListItem`** rows with **`embedded`** Switch / Checkbox / Radio in **`trailing`**. Set **`dense`** on **`ListControls`** (or per **`ListItem`**) for compact row padding.
221
222
 
222
223
  ### Icon variants
223
- Icons use `sharpFilled` or `sharpUnfilled`. Default is `sharpUnfilled`.
224
+ Icons use `sharpFilled` or `sharpUnfilled`. Default is `sharpUnfilled`. GPF chrome
225
+ names always resolve from `@uxelle/components`. Any other Material Symbol requires
226
+ `import "@uxelle/icons/register"` in the host (`@uxelle/a2ui` already does this).
227
+ Unknown names render no glyph (empty span, production-silent) — a visual-only miss
228
+ that a11y tests will not catch on icon-only buttons that already have `aria-label`.
229
+ Type product names with `UxelleIconName` from `@uxelle/icons`.
224
230
 
225
231
  ### Theme tokens
226
232
  Generated app CSS uses `--uxl-color-switcher-*` and layout/page-chrome tokens. Do not use `--uxl-component-*` in app CSS (those tokens belong inside components). Focus rings on catalog controls already use `--uxl-color-switcher-interactive-icon`.
@@ -236,6 +242,7 @@ For secondary nav or brand hero bands: `data-color-switcher="brand-2"` on second
236
242
 
237
243
  ## Additional Resources
238
244
 
245
+ - [Getting started](getting-started.md) — install `@uxelle/components` and `@uxelle/themes`, load CSS, theme provider, Next.js/SSR
239
246
  - [Design harness](../uxelle-design-harness/SKILL.md) — principles, variables, page chrome, responsiveness, WCAG AA, recipes
240
247
  - [Form field contract](../../docs/guides/form-field-contract.md) — HTML `value` / `checked` (not Figma `Selected` / `Activated`)
241
248
  - [Design variables (`--uxl-*`)](../uxelle-design-harness/tokens.md) — spacing, radius, elevation, color switcher, type
@@ -34,6 +34,7 @@ Single-select dropdown with label, design tokens, optional `FieldMessage`, and l
34
34
  | readOnly | `boolean` | `false` | Shows the current selection but blocks opening the listbox and hides the trailing chevron; the trigger stays focusable and reports `aria-readonly`. |
35
35
  | required | `boolean` | `—` | Adds the asterisk to `Label` and `aria-required` on the trigger. Advisory only — no native constraint validation runs on the hidden input. |
36
36
  | name | `string` | `—` | Form field name for the hidden input that posts the selected value. No input is rendered while nothing is selected, so an empty selection is never submitted. |
37
+ | onClick | `MouseEventHandler<HTMLDivElement>` | `—` | Fired on the field chrome. `currentTarget` is the trigger wrapper, not the combobox input. |
37
38
  | disabled | `boolean` | `false` | Blocks interaction, removes the trigger from the tab order, and applies disabled styling; a `name` hidden input still submits the current value. |
38
39
 
39
40
  <!-- prettier-ignore-end -->
@@ -51,5 +52,5 @@ Single-select dropdown with label, design tokens, optional `FieldMessage`, and l
51
52
  - **Simple API** — pass `options` with `value` / `onChange` for controlled usage; optional `leadingIcon` / `leadingIconName`.
52
53
  - **Composable API** — pass `children` to render custom `ListItem` rows in the listbox; pass `leading` for a custom leading slot. Each row should spread `listboxOptionProps()` plus selection handlers (see dev console when the list opens).
53
54
  - **Accessibility**: Visible label uses `<Label htmlFor>`; the combobox name uses `aria-labelledby` on the label text only (not `fieldDescription`, which is linked via `aria-describedby`). `FieldMessage` uses `aria-describedby` (info) or `aria-errormessage` (error). Pass `id` for stable ids; use `aria-label` when `label={false}`.
54
- - **Focus**: Tab focuses the combobox trigger only; **Arrow** keys move the active option (`aria-activedescendant`) while open. **Enter** commits the active option; **Escape** closes the list. Options use `tabIndex={-1}` and are not separate tab stops.
55
+ - **Focus**: The combobox is a readonly `<input>` (not a `<button>`) so Safari's default Tab cycle — text fields, not buttons — can land on it, and a click actually focuses the control. **Arrow** keys move the active option (`aria-activedescendant`) while open. **Enter** commits the active option; **Escape** closes the list. Options use `tabIndex={-1}` and are not separate tab stops.
55
56
  - **Forms**: With `name`, a hidden input posts the selected value; when nothing is selected, no hidden input is rendered.
@@ -10,11 +10,11 @@ Card-styled readout for a headline figure with a label, optional qualifier, info
10
10
 
11
11
  | Prop | Type | Default | Description |
12
12
  |------|------|---------|-------------|
13
- | order | `"labelFirst" \| "valueFirst"` | `"labelFirst" as StatTileOrder` | Vertical order of the label row and the value row. |
14
- | label | `ReactNode` | `—` | Label in the label row. Occupies remaining width beside the info control. Pass a `string` (or `number`) to render with the default label typography (`Text type="Body Medium"`), or pass a `ReactNode` for full control. When you pass a `ReactNode` you own its accessibility: use meaningful text (not color or icon alone) and keep it non-interactive so it can name the group. |
15
- | value | `ReactNode` | `—` | Headline figure in the value row. Pass a `string` (or `number`) to render with the default value typography (`Text type="Display Medium"` as `<p>`), or pass a `ReactNode` for full control. A library `Text` node is rendered as `<p>` so a grid of StatTiles does not become a heading outline. When you pass any other `ReactNode`, make sure the figure reads as text and is not conveyed through color or an icon alone (WCAG 1.4.1). |
13
+ | order | `"labelFirst" \| "valueFirst"` | `"labelFirst"` | Vertical order of the label row and the value row. |
14
+ | label | `ReactNode` | `—` | Label in the label row. Occupies remaining width beside the info control and stays on one line: overflow ellipsizes. Hover or focus a truncated label to read the full name in a tooltip. A string (or number) is wrapped in **Text** with `type="Body Medium"`; pass a `Text` node for a different type (truncation is still applied). When you pass a `Text` or other `ReactNode` you own its accessibility: use meaningful text (not color or icon alone) and keep it non-interactive so it can name the group. |
15
+ | value | `ReactNode` | `—` | Headline figure in the value row. A string (or number) is wrapped in **Text** with `type="Display Small"` as `<p>`; pass a `Text` node for a different type. A `Text` keeps its `type` and is rendered as `<p>` so a grid of StatTiles does not become a heading outline. Any other `ReactNode` is allowed; make sure the figure reads as text and is not conveyed through color or an icon alone (WCAG 1.4.1). |
16
16
  | qualifier | `boolean` | `true` | When true, allows the qualifier to render when content is present. |
17
- | qualifierContent | `ReactNode` | `—` | Qualifier beside the value. Pass a `string` (or `number`) to render with the default qualifier typography (`Text type="Body Medium"`), or a `ReactNode` for full control. Only rendered when `qualifier` is true and content is present. When you pass a `ReactNode`, ensure it reads as text; do not rely on color alone to distinguish comparison periods. |
17
+ | qualifierContent | `ReactNode` | `—` | Qualifier beside the value. A string (or number) is wrapped in **Text** with `type="Body Medium"`; pass a `Text` node for a different type. Only rendered when `qualifier` is true and content is present. When you pass a `Text` or other `ReactNode`, ensure it reads as text; do not rely on color alone to distinguish comparison periods. |
18
18
  | tooltipSlot | `boolean` | `true` | When true, allows the tooltip slot to render when content is present. |
19
19
  | tooltipContent | `ReactNode` | `—` | Info control at the end of the label row (typically `Tooltip` wrapping `IconButton`). Only rendered when `tooltipSlot` is true and content is present. Icon-only triggers within this slot must carry an accessible name via `aria-label` on the interactive element (e.g. `<IconButton aria-label="More information" />`). |
20
20
  | trailingSlot | `boolean` | `false` | When true, allows the trailing region to render when content is present. The slot accepts any content. |
@@ -70,7 +70,7 @@ Card-styled readout for a headline figure with a label, optional qualifier, info
70
70
  // ReactNode override — caller owns accessibility (semantic element, text)
71
71
  <StatTile
72
72
  label={<Text type="Body Medium" width={false}>Fill rate</Text>}
73
- value={<Text type="Display Medium" as="p">94%</Text>}
73
+ value={<Text type="Display Small" as="p">94%</Text>}
74
74
  />
75
75
  ```
76
76
 
@@ -78,7 +78,7 @@ Card-styled readout for a headline figure with a label, optional qualifier, info
78
78
  // Polling dashboard — aria-live on the value announces only the change
79
79
  <StatTile
80
80
  label="Active sessions"
81
- value={<Text type="Display Medium" as="p" aria-live="polite">{count}</Text>}
81
+ value={<Text type="Display Small" as="p" aria-live="polite">{count}</Text>}
82
82
  />
83
83
  ```
84
84
 
@@ -111,40 +111,11 @@ Card-styled readout for a headline figure with a label, optional qualifier, info
111
111
 
112
112
  ## Notes
113
113
 
114
- - Pass a `string` (or `number`) to `label`, `value`, and `qualifierContent` to
115
- - render with each region's default typography — no `Text` wrapper needed. Pass
116
- - a `ReactNode` instead when you need full control (e.g. truncation, a custom
117
- - element); in that case you own its accessibility (see each prop). A library
118
- - `Text` passed as `value` is rendered as `<p>`.
119
- - Pass a library `Tooltip` (typically wrapping `IconButton`) through
120
- - `tooltipContent`. Toggle regions with `qualifier`, `tooltipSlot`, and
121
- - `trailingSlot`; empty content is not rendered. `trailingSlotContent` is an
122
- - open slot — any content is allowed. Surface, elevation, radius, and
123
- - padding come from `Card`. StatTile is a standalone tile: place it next to
124
- - other StatTiles in a grid. Do not wrap it in `Card`. A figure inside a chart
125
- - card or table cell is a different surface — compose `Text` there.
126
- - **Accessibility.** The root is a `role="group"` region. It is named
127
- - automatically from `label` (via `aria-labelledby`), so the group is announced
128
- - with context in both orders — including `"valueFirst"`, where the value
129
- - precedes the label in the DOM. Supply your own `aria-label` or
130
- - `aria-labelledby` to override the derived name; this is **required** when
131
- - there is no `label` (e.g. `aria-label="Median first response time, 1 day 12
132
- - hours"`) so the figure is not announced without context. In development,
133
- - StatTile warns when content is present but the group has no name. The trailing
134
- - slot is open, so its accessibility is the caller's responsibility — see
135
- - `trailingSlotContent`.
136
- - **Navigation.** Do not put `onClick` on StatTile — the root is a named group,
137
- - not a control, and a wrapped link would nest interactives when a tooltip is
138
- - present. Put the destination in `trailingSlotContent` as a `Link` or
139
- - `IconButton`.
114
+ - `label`, `value`, and `qualifierContent` each accept a string (or number) or a **Text** node. A string is wrapped in that region's default `Text` type — no wrapper needed. Pass a `Text` when you need a different `type` or a live region. A `Text` passed as `value` keeps its `type` and is rendered as `<p>`. The label always truncates to one line so a row of tiles stays scannable; hover or focus a truncated label to read the full name (`Text` Body Small). This overflow tooltip is separate from `tooltipContent` (the info control).
115
+ - Pass a library `Tooltip` (typically wrapping `IconButton`) through `tooltipContent`. Toggle regions with `qualifier`, `tooltipSlot`, and `trailingSlot`; empty content is not rendered. `trailingSlotContent` is an open slot — any content is allowed. Surface, elevation, radius, and padding come from `Card`. StatTile is a standalone tile: place it next to other StatTiles in a grid. Do not wrap it in `Card`. A figure inside a chart card or table cell is a different surface — compose `Text` there.
116
+ - **Accessibility.** The root is a `role="group"` region. It is named automatically from `label` (via `aria-labelledby`), so the group is announced with context in both orders — including `"valueFirst"`, where the value precedes the label in the DOM. Supply your own `aria-label` or `aria-labelledby` to override the derived name; this is **required** when there is no `label` (e.g. `aria-label="Median first response time, 1 day 12 hours"`) so the figure is not announced without context. In development, StatTile warns when content is present but the group has no name. The trailing slot is open, so its accessibility is the caller's responsibility — see `trailingSlotContent`.
117
+ - **Navigation.** Do not put `onClick` on StatTile — the root is a named group, not a control, and a wrapped link would nest interactives when a tooltip is present. Put the destination in `trailingSlotContent` as a `Link` or `IconButton`.
140
118
  - Set `order` to `"valueFirst"` to put the value row above the label row.
141
- - The root fills its parent by default (`fullWidth`). Set `fullWidth={false}`
142
- - to hug contents. Long label, value, and qualifier copy wrap inside the tile.
143
- - While data is loading, replace the tile with a `Skeleton` sized to the card
144
- - (full-width rectangle) inside `SkeletonGroup`. Do not pass a skeleton as
145
- - `value`.
146
- - For polling dashboards where the value updates in place, prefer putting
147
- - `aria-live="polite"` on the **value** node so only the changing figure is
148
- - announced (a root-level `aria-live` also works but re-announces the whole
149
- - group). When using a `string` `value`, wrap it yourself to place the live
150
- - region precisely.
119
+ - The root fills its parent by default (`fullWidth`). Set `fullWidth={false}` to hug contents. Long value and qualifier copy wrap inside the tile; the label ellipsizes instead of wrapping.
120
+ - While data is loading, replace the tile with a `Skeleton` sized to the card (full-width rectangle) inside `SkeletonGroup`. Do not pass a skeleton as `value`.
121
+ - For polling dashboards where the value updates in place, prefer putting `aria-live="polite"` on the **value** node so only the changing figure is announced (a root-level `aria-live` also works but re-announces the whole group). When using a `string` `value`, wrap it yourself to place the live region precisely.
@@ -11,7 +11,7 @@ Composable table shell and semantic grid for tabular data. Columns support resiz
11
11
  | Prop | Type | Default | Description |
12
12
  |------|------|---------|-------------|
13
13
  | topSlot | `ReactNode` | `—` | Band rendered above the table and outside its horizontal scrollport — typically a title, toolbar, filters, or search. Omitted entirely when unset. |
14
- | bottomSlot | `ReactNode` | `—` | Band rendered below the table and outside its horizontal scrollport — typically pagination or bulk actions. Omitted entirely when unset. |
14
+ | bottomSlot | `ReactNode` | `—` | Band rendered below the table and outside its horizontal scrollport — typically **Pagination** or bulk actions. Omitted entirely when unset. |
15
15
  | tableSlot | `ReactNode` | `—` | The table itself: one **TableGrid** with its head, body, rows, and cells, placed in the scroll region that drives the horizontal scroll hint. Same slot as `children`, and it wins when both are passed. |
16
16
  | children | `ReactNode` | `—` | Alias for `tableSlot`, ignored when `tableSlot` is also passed. Compose one `TableGrid` (give it `aria-label` and `gridTemplateColumns`) holding a `TableHead` with a `TableRow` of `TableHeaderCell` children, then a `TableBody` with one `TableRow` of `TableCell` children per record — all exported from `@uxelle/components`. |
17
17
 
@@ -23,7 +23,7 @@ Extends `HTMLAttributes` — supports standard HTML attributes.
23
23
 
24
24
  ```tsx
25
25
  const columns = [
26
- { flex: 0.5, minWidth: 48 },
26
+ { flex: 0, minWidth: 48, maxWidth: 72 },
27
27
  { flex: 1, minWidth: 120 },
28
28
  ] as const;
29
29
 
@@ -44,12 +44,15 @@ function OrdersGrid() {
44
44
  <OrdersGrid />
45
45
  </TableColumnSizingProvider>
46
46
  }
47
+ bottomSlot={
48
+ <Pagination totalItems={100} defaultItemsPerPage={10} aria-label="Orders pagination" />
49
+ }
47
50
  />
48
51
  ```
49
52
 
50
53
 
51
54
  ## Notes
52
55
 
53
- - Optional top and bottom slots frame the table region; a horizontal scroll hint appears when the grid is wider than its container. Fixed columns are implemented as sticky cells inside one `TableGrid`—not as a separate grid—so each row stays a single unit for assistive technology. Use **TableColumnPinProvider** or `pinned="start"` on **TableHeaderCell** and **TableCell** with **usePinnedStartOffsets** to keep leading columns visible during horizontal scroll.
54
- - Horizontal scroll and the trailing-edge hint appear automatically when content overflows the container. Define column proportions with **TableColumnSizingProvider** and **useColumnGridTemplate**.
56
+ - Optional top and bottom slots frame the table region; pass **Pagination** in `bottomSlot` when the dataset is paged. A horizontal scroll hint appears when the grid is wider than its container. Fixed columns are implemented as sticky cells inside one `TableGrid`—not as a separate grid—so each row stays a single unit for assistive technology. Use **TableColumnPinProvider** or `pinned="start"` on **TableHeaderCell** and **TableCell** with **usePinnedStartOffsets** to keep leading columns visible during horizontal scroll.
57
+ - Horizontal scroll and the trailing-edge hint appear automatically when content overflows the container. Define column proportions with **TableColumnSizingProvider** and **useColumnGridTemplate**. Set `flex: 0` and `maxWidth` on compact columns (selection, icons) so leftover `fr` space does not stretch them on a wide viewport. `flex: 0` without a pixel `minWidth` or `maxWidth` sizes the track to content (`max-content`) instead of collapsing. `maxWidth` on a growing column (`flex > 0`) is a resize ceiling only. Cell copy wraps and the row height follows the tallest cell. Header titles stay on one line and ellipsize when the column is narrower than the name. Pass `truncation` on **Text** in a body cell only when that column must stay single-line. Resizing a column persists every column as a pixel track at the widths on screen, so siblings are not stretched or shrunk to fit the container. Call **useResetTableColumnWidths** to restore every column to its template.
55
58
  - Layout is left-to-right only; right-to-left document direction is not supported yet.
@@ -0,0 +1,85 @@
1
+ <!-- Generated from README.md (getting-started region). Do not edit. -->
2
+
3
+ # Getting started
4
+
5
+ New app or host without uxElle packages and CSS? Read this file first, then set up page chrome in [how-to-host.md](../uxelle-design-harness/how-to-host.md). Component APIs live in [SKILL.md](SKILL.md). Optional A2UI runtime: [a2ui.md](../uxelle-design-harness/a2ui.md). Do not preload every component file.
6
+
7
+ ## Quick Start
8
+
9
+ Peer dependencies: React 18 or 19.
10
+
11
+ ```bash
12
+ npm install @uxelle/components @uxelle/themes
13
+ ```
14
+
15
+ Import the two stylesheets:
16
+
17
+ ```tsx
18
+ import "@uxelle/components/base.css";
19
+ import "@uxelle/themes/open-source.css";
20
+
21
+ import { Button } from "@uxelle/components";
22
+
23
+ function App() {
24
+ return <Button emphasis="high">Get Started</Button>;
25
+ }
26
+ ```
27
+
28
+ Then set the theme attributes on `<html>`:
29
+
30
+ ```html
31
+ <html data-open-source="light" data-color-switcher="default"></html>
32
+ ```
33
+
34
+ Both attributes are required. Every `--uxl-*` token is declared under
35
+ `[data-open-source="light"]` or `[data-open-source="dark"]`, so components render
36
+ unstyled without them. See [Themes](#themes) to switch modes at runtime.
37
+
38
+ `base.css` is the single component stylesheet. `@uxelle/components/styles.css`
39
+ (global baseline) and `@uxelle/components/index.css` (component rules)
40
+ stay exported if you need to load them separately.
41
+
42
+ ### Next.js / SSR
43
+
44
+ - Import both stylesheets in `app/layout.tsx` (or a top-level provider). JS side effects will not style App Router.
45
+ - Published `@uxelle/components` JS is prefixed with `"use client"`. Importing any component places that file in the client graph.
46
+ - Pass `mobile={true}` or `mobile={false}` on `NavigationSide` so server and client markup match. Omit `mobile` only in client-only surfaces.
47
+
48
+ Optional: for an agent-driven UI surface, also install `@uxelle/a2ui`.
49
+
50
+ ### Icons
51
+
52
+ `@uxelle/components` ships GPF chrome glyphs (`close`, `chevron_*`, `check`, and other names the library itself uses). For any other Material Symbol Sharp name, install `@uxelle/icons` and register once before the first `Icon` render:
53
+
54
+ ```bash
55
+ npm install @uxelle/icons
56
+ ```
57
+
58
+ ```tsx
59
+ import "@uxelle/icons/register";
60
+ ```
61
+
62
+ `@uxelle/a2ui` registers the full catalog when you import it. Unknown names render no glyph (an empty span). Development warns; production is silent, so an icon-only control with `aria-label` can still pass a11y tests while showing nothing. Register `@uxelle/icons` before the first render, or type names with `UxelleIconName` from `@uxelle/icons`.
63
+
64
+ ## Themes
65
+
66
+ Theme CSS is published as `@uxelle/themes` and generated from design tokens. Quick Start covers the import and the two required attributes; this section covers changing them.
67
+
68
+ - `data-open-source` — `"light"` or `"dark"`
69
+ - `data-color-switcher` — `"default"`, `"default-subtle"`, `"success"`, `"warning"`, `"danger"`, or `"info"`
70
+
71
+ To drive both from React instead of hardcoding them, wrap the app with `UXelleThemeProvider`, which sets the attributes on `document.documentElement`:
72
+
73
+ ```tsx
74
+ import { UXelleThemeProvider } from "@uxelle/themes/react";
75
+
76
+ function AppShell({ children }: { children: React.ReactNode }) {
77
+ return (
78
+ <UXelleThemeProvider theme="open-source" darkMode={false} colorSwitcher="default">
79
+ {children}
80
+ </UXelleThemeProvider>
81
+ );
82
+ }
83
+ ```
84
+
85
+ Open Source uses **Open Sans** (self-hosted in the theme package). Do not set `font-family` in app CSS — `Text` uses theme tokens.
@@ -37,29 +37,38 @@ palette. The full mapping of principles to uxElle mechanisms lives in
37
37
  1. Load this `SKILL.md`. The foundations below always apply.
38
38
  2. Read the variable foundation once: [tokens.md](tokens.md) and
39
39
  [spacing-steps.md](spacing-steps.md).
40
- 3. Creating an app shell? [how-to-host.md](how-to-host.md). Building a page body?
41
- [how-to-page-layout.md](how-to-page-layout.md) (layout **and** responsiveness),
42
- [how-to-color.md](how-to-color.md), and [how-to-accessibility.md](how-to-accessibility.md).
43
- 4. Prefer an existing component. Link its doc at point of use, e.g.
40
+ 3. Pick the **density mode** for the experience — `compact` (data-dense product),
41
+ `standard` (task product, the default), `spacious` (marketing). One mode per
42
+ experience, chosen before any layout: [density.md](density.md).
43
+ 4. Creating a **new** app/host? Install and load CSS from
44
+ [getting-started.md](../uxelle-components/getting-started.md), then the
45
+ shell in [how-to-host.md](how-to-host.md). Building a page body in an
46
+ existing host? [how-to-page-layout.md](how-to-page-layout.md) (layout
47
+ **and** responsiveness), [how-to-color.md](how-to-color.md), and
48
+ [how-to-accessibility.md](how-to-accessibility.md).
49
+ 5. Prefer an existing component. Link its doc at point of use, e.g.
44
50
  `Table` -> `../uxelle-components/Table.md`. Import from `@uxelle/components`.
45
- 5. Otherwise follow a recipe (see the index). If nothing fits, compose `Layout` +
51
+ 6. Otherwise follow a recipe (see the index). If nothing fits, compose `Layout` +
46
52
  `Text` + primitives on the grid.
47
- 6. Building a runtime surface? Read [a2ui.md](a2ui.md) and each recipe's **A2UI** note.
48
- 7. Before you finish, run the quality checklist below.
53
+ 7. Building a runtime surface? Read [a2ui.md](a2ui.md) and each recipe's **A2UI** note.
54
+ 8. Before you finish, run the quality checklist below.
49
55
 
50
56
  ## Decision tree
51
57
 
52
- - **New app/host** (loads theme CSS, `data-*`, skip link, `main`) -> [how-to-host.md](how-to-host.md).
53
- Composing inside an existing host -> skip host setup; do not set theme `data-*` on `<html>`.
58
+ - **New app/host** (packages not installed, or CSS / `data-*` not loaded) ->
59
+ [getting-started.md](../uxelle-components/getting-started.md), then
60
+ [how-to-host.md](how-to-host.md) for skip link, `main`, and column chrome.
61
+ Composing inside an existing host -> skip both; do not set theme `data-*` on `<html>`.
54
62
  - **Product experience** (signed-in app):
55
63
  app frame -> [recipe-app-chrome.md](recipe-app-chrome.md);
56
- landing/overview -> [recipe-dashboard-overview.md](recipe-dashboard-overview.md);
57
- records list -> [recipe-data-table-page.md](recipe-data-table-page.md);
64
+ landing/overview -> [recipe-dashboard-overview.md](recipe-dashboard-overview.md) (`compact`);
65
+ records list -> [recipe-data-table-page.md](recipe-data-table-page.md) (`compact`);
58
66
  one record -> [recipe-record-detail.md](recipe-record-detail.md);
59
67
  settings -> [recipe-settings-page.md](recipe-settings-page.md);
60
68
  wizard/checkout -> [recipe-multi-step-flow.md](recipe-multi-step-flow.md);
61
69
  generic body -> [recipe-page-shell.md](recipe-page-shell.md).
62
- - **Marketing experience** (public): whole page -> [recipe-landing-page.md](recipe-landing-page.md),
70
+ Unmarked product recipes default to `standard` ([density.md](density.md)).
71
+ - **Marketing experience** (public, `spacious`): whole page -> [recipe-landing-page.md](recipe-landing-page.md),
63
72
  composed from the marketing pieces below.
64
73
  - **A piece** (part of an experience): page intro -> [recipe-page-header.md](recipe-page-header.md);
65
74
  filter/search toolbar -> [recipe-query-bar.md](recipe-query-bar.md);
@@ -79,6 +88,11 @@ palette. The full mapping of principles to uxElle mechanisms lives in
79
88
  padding / margin. Page chrome (inset, column cap, `fr` gaps) uses
80
89
  `--uxl-breakpoints-margin` / `-max-container-width` / `-gutter`. See
81
90
  [spacing-steps.md](spacing-steps.md) and [how-to-page-layout.md](how-to-page-layout.md).
91
+ - **Density**: choose the step by **role** (Flush / Tight / Control / Group /
92
+ Section / Band) and the heading type by **rank**, both resolved through one
93
+ **mode** for the whole experience — `compact` | `standard` | `spacious`. Page
94
+ chrome and component internals are density-invariant. Never mix modes or tighten
95
+ one role alone. See [density.md](density.md).
82
96
  - **Color**: style via `--uxl-color-switcher-*` roles; apply a palette with
83
97
  `data-color-switcher` on a **region** (never invent switcher names). See
84
98
  [how-to-color.md](how-to-color.md).
@@ -86,10 +100,12 @@ palette. The full mapping of principles to uxElle mechanisms lives in
86
100
  never use `--uxl-component-*` in app/recipe CSS (those belong inside components).
87
101
  - **No hardcoded values**: no hex, no font-family, no breakpoint px. Read
88
102
  breakpoint bounds from theme variables when JS is unavoidable
89
- ([how-to-page-layout.md](how-to-page-layout.md)). Exception: one-column reading
90
- content (a form/settings page, or a form / FAQ / prose section on a wider page)
91
- may cap at the `maxWidth="600px"` **reading measure** — always centered with
92
- `mh="auto"` ([how-to-page-layout.md](how-to-page-layout.md#reading-measure)).
103
+ ([how-to-page-layout.md](how-to-page-layout.md)). Two exceptions: one-column
104
+ reading content (a form/settings page, or a form / FAQ / prose section on a wider
105
+ page) may cap at the `maxWidth="600px"` **reading measure**, always centered with
106
+ `mh="auto"` ([how-to-page-layout.md](how-to-page-layout.md#reading-measure)); and
107
+ **intrinsic sizes** — `minmax()` mins and a bounded search field — use `rem`
108
+ ([how-to-page-layout.md](how-to-page-layout.md#intrinsic-sizes-in-rem)).
93
109
  - **Components first**: prefer a catalog component over bespoke markup; link its
94
110
  doc at point of use (`../uxelle-components/<Name>.md`); do not open package source.
95
111
  - **Recipes are patterns, not exports**: never add `PageShell` / `AppChrome` /
@@ -104,14 +120,21 @@ Every generated screen must pass this. It is the definition of done.
104
120
 
105
121
  - **Grid & rhythm**: content sits on the grid; one capped content column via
106
122
  `--uxl-breakpoints-max-container-width` / `-gutter` / `-margin`; section spacing
107
- from the ramp (page sections use `medium-12`). One-column reading sections
123
+ from the ramp at the experience's **Section** step. One-column reading sections
108
124
  (form / FAQ / prose) share the `600px` reading measure and are centered
109
125
  (`mh="auto"`); a decision cluster (form actions) is separated from its fields by
110
- `medium-12`, not the field gap.
126
+ the **Section** step, not the field gap.
127
+ - **Density**: one mode declared for the surface and applied to every role; the gap
128
+ roles (Tight -> Control -> Group -> Section) stay ~1.5x apart; page chrome and
129
+ component internals unchanged; no breakpoint logic added for density
130
+ ([density.md](density.md)).
111
131
  - **Tokens only**: spacing from `--uxl-theme-layout-spacing-*`; color from
112
132
  `--uxl-color-switcher-*`; no hardcoded hex or px, no invented `--uxl-*`, no
113
133
  `--uxl-component-*` in app CSS, no `var(--x, fallback)`.
114
- - **Hierarchy**: exactly one `h1`; heading ranks unbroken; few `Text` type roles.
134
+ - **Hierarchy**: exactly one `h1`; heading ranks unbroken; each rank resolved to a
135
+ **distinct** `Text` type through the density mode — consecutive ranks never share
136
+ a type (an `h1` and `h2` both on `Display Extra Small` flatten the page), and
137
+ headings inside a `Card` step down one stop ([density.md](density.md)).
115
138
  - **Responsive**: reflows mobile -> up with no px literals; clusters stack, `Table`
116
139
  scrolls, nav collapses; touch targets stay >= 24 CSS px.
117
140
  - **Color discipline**: at most one dominant brand band per view; status palettes
@@ -126,9 +149,10 @@ Every generated screen must pass this. It is the definition of done.
126
149
 
127
150
  **Foundations** (always apply): [principles.md](principles.md) ·
128
151
  [tokens.md](tokens.md) · [spacing-steps.md](spacing-steps.md) ·
152
+ [density.md](density.md) ·
129
153
  [how-to-page-layout.md](how-to-page-layout.md) · [how-to-color.md](how-to-color.md) ·
130
154
  [how-to-accessibility.md](how-to-accessibility.md) · [how-to-host.md](how-to-host.md) ·
131
- [a2ui.md](a2ui.md)
155
+ [getting-started](../uxelle-components/getting-started.md) · [a2ui.md](a2ui.md)
132
156
 
133
157
  **Experiences** — product: [app-chrome](recipe-app-chrome.md) ·
134
158
  [page-shell](recipe-page-shell.md) · [dashboard-overview](recipe-dashboard-overview.md) ·
@@ -13,12 +13,20 @@ Hosts inject `uxelleCatalogSchema` (and optionally a recipe's messages) into the
13
13
  agent prompt and render the result. Do not open package source to "discover" props —
14
14
  use the catalog schema the host provides.
15
15
 
16
+ Importing `@uxelle/a2ui` registers the full Material Symbols Sharp catalog. React
17
+ hosts that do not use A2UI must `import "@uxelle/icons/register"` themselves if
18
+ generated UI uses icon names beyond GPF chrome.
19
+
16
20
  ## Hard constraints
17
21
 
18
22
  - **Catalog adapters only.** No raw HTML, no custom components.
19
23
  - **No `className` / `style`.** Styling comes from the theme and adapter props.
20
24
  - **Layout-spacing tokens only.** `A2uiLayout` accepts `0` or
21
25
  `var(--uxl-theme-layout-spacing-*)`; never emit page-chrome breakpoint tokens.
26
+ - **`standard` or `compact` density only.** Chat surfaces are narrow and have no
27
+ full-bleed bands, so `spacious` never applies and the **Band** role has no
28
+ meaning. Use `standard` by default, `compact` for data-heavy runtime output
29
+ ([density.md](density.md)).
22
30
  - **No responsive JS.** There is no `matchMedia`. Default to a single, narrow
23
31
  column (chat is mobile-like) and stack action clusters (`"direction": "column"`).
24
32
  - **Host owns theme.** Never set theme `data-*` or a skip link from adapters; the
@@ -45,12 +53,32 @@ recipe JSON into the customer app.
45
53
  Most catalog components map 1:1 to an `A2ui<Name>` adapter (e.g. `Button` ->
46
54
  `A2uiButton`, `Textfield` -> `A2uiTextfield`, `Textarea` -> `A2uiTextarea`,
47
55
  `Table` -> `A2uiTable`, `Sheet` -> `A2uiSheet`, `Hero` -> `A2uiHero`,
48
- `Stepper` -> `A2uiStepper` + `A2uiStepperItem`, `ListControls` -> `A2uiListControls`,
56
+ `Image` -> `A2uiImage`, `Stepper` -> `A2uiStepper` + `A2uiStepperItem`,
57
+ `ListControls` -> `A2uiListControls`,
49
58
  `Banner` -> `A2uiBanner`, `LabelBadge` -> `A2uiLabelBadge`, `StatTile` -> `A2uiStatTile`,
59
+ `ChoiceChip` -> `A2uiChoiceChip`, `ChoiceChipGroup` -> `A2uiChoiceChipGroup`,
60
+ `FilterChip` -> `A2uiFilterChip`, `FilterChipGroup` -> `A2uiFilterChipGroup`,
50
61
  `DynamicAngle*` -> `A2uiDynamicAngle*`). `Table` has the full family (`A2uiTableGrid`,
51
62
  `A2uiTableHead`, `A2uiTableBody`, `A2uiTableRow`, `A2uiTableCell`,
52
63
  `A2uiTableHeaderCell`, `A2uiTableColumnSizingProvider`).
53
64
 
65
+ `A2uiImage` box size (`width`, `height`, `minWidth`, `maxWidth`, `minHeight`,
66
+ `maxHeight`) is `number|string` — numbers are pixels, strings pass through as CSS
67
+ (including `var(--uxl-…)`). `aspectRatio` crops and computes the missing axis.
68
+ Omit size and `aspectRatio` on an `A2uiImage` nested in `A2uiHero`.
69
+
70
+ `A2uiChoiceChip` / `A2uiFilterChip` take a `label` string. Do not nest `A2uiText`
71
+ inside a chip. Choice-chip labels use Condensed when unchecked and Condensed Alt
72
+ when checked; filter-chip labels use Condensed. `A2uiFilterChipGroup` wraps whole
73
+ chips; a chip wider than its container ellipsizes so dismiss stays visible.
74
+ Exclusive `A2uiChoiceChip` selection is one checked value in host state (toggles,
75
+ not radios). Use `A2uiRadioGroup` or `A2uiSegmentedControl` for radio semantics.
76
+
77
+ `A2uiStatTile` labels stay on one line and ellipsize; hover or focus a truncated
78
+ label to read the full name. Pass `label` as a string — do not wrap it in
79
+ `A2uiText` or set truncation. Value and qualifier copy wrap. The overflow tooltip
80
+ is automatic and separate from `tooltipContent`.
81
+
54
82
  Known gaps — substitute rather than invent:
55
83
 
56
84
  | React | A2UI |
@@ -66,7 +94,8 @@ If a design needs a gap component as its backbone, say so in the recipe's
66
94
 
67
95
  ## Translating a React recipe
68
96
 
69
- 1. Keep the same region order and layout-spacing values.
97
+ 1. Keep the same region order. Keep the layout-spacing values too, unless the React
98
+ surface was `spacious` — then re-resolve its roles at `standard`.
70
99
  2. Swap each component for its adapter; expand `Lockup` to stacked `A2uiText`.
71
100
  3. Replace `useBreakpointUp` switches with a single narrow column.
72
101
  4. Replace React state with host bindings.