@uxelle/skills 0.2.0-beta.2 → 0.2.2

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 (49) hide show
  1. package/README.md +3 -5
  2. package/dist/index.js +98 -88
  3. package/index.json +98 -88
  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/Image.md +10 -3
  11. package/skills/uxelle-components/Logo.md +1 -1
  12. package/skills/uxelle-components/MultiSelect.md +3 -2
  13. package/skills/uxelle-components/SKILL.md +3 -1
  14. package/skills/uxelle-components/Select.md +2 -1
  15. package/skills/uxelle-components/Table.md +7 -4
  16. package/skills/uxelle-components/getting-started.md +54 -0
  17. package/skills/uxelle-design-harness/SKILL.md +44 -20
  18. package/skills/uxelle-design-harness/a2ui.md +22 -2
  19. package/skills/uxelle-design-harness/density.md +138 -0
  20. package/skills/uxelle-design-harness/how-to-accessibility.md +2 -0
  21. package/skills/uxelle-design-harness/how-to-color.md +6 -3
  22. package/skills/uxelle-design-harness/how-to-host.md +5 -22
  23. package/skills/uxelle-design-harness/how-to-page-layout.md +51 -6
  24. package/skills/uxelle-design-harness/principles.md +10 -6
  25. package/skills/uxelle-design-harness/recipe-app-chrome.md +20 -16
  26. package/skills/uxelle-design-harness/recipe-card-grid.md +14 -5
  27. package/skills/uxelle-design-harness/recipe-cta-band.md +16 -6
  28. package/skills/uxelle-design-harness/recipe-dashboard-overview.md +17 -5
  29. package/skills/uxelle-design-harness/recipe-data-table-page.md +30 -137
  30. package/skills/uxelle-design-harness/recipe-feature-section.md +9 -7
  31. package/skills/uxelle-design-harness/recipe-footer.md +7 -4
  32. package/skills/uxelle-design-harness/recipe-form-section.md +2 -2
  33. package/skills/uxelle-design-harness/recipe-hero.md +35 -15
  34. package/skills/uxelle-design-harness/recipe-landing-page.md +27 -4
  35. package/skills/uxelle-design-harness/recipe-logo-wall.md +11 -9
  36. package/skills/uxelle-design-harness/recipe-multi-step-flow.md +5 -3
  37. package/skills/uxelle-design-harness/recipe-page-header.md +14 -4
  38. package/skills/uxelle-design-harness/recipe-page-shell.md +2 -2
  39. package/skills/uxelle-design-harness/recipe-pricing.md +5 -2
  40. package/skills/uxelle-design-harness/recipe-query-bar.md +21 -5
  41. package/skills/uxelle-design-harness/recipe-record-detail.md +5 -2
  42. package/skills/uxelle-design-harness/recipe-settings-page.md +3 -3
  43. package/skills/uxelle-design-harness/recipe-stat-callouts.md +8 -3
  44. package/skills/uxelle-design-harness/recipe-states.md +9 -4
  45. package/skills/uxelle-design-harness/recipe-summary-list.md +5 -3
  46. package/skills/uxelle-design-harness/recipe-template.md +6 -0
  47. package/skills/uxelle-design-harness/recipe-testimonial.md +5 -3
  48. package/skills/uxelle-design-harness/spacing-steps.md +17 -8
  49. package/skills/uxelle-design-harness/tokens.md +45 -4
@@ -19,7 +19,8 @@ The first impression: one sentence of value, one clear action, and supporting me
19
19
  - One `h1` for the whole page lives here — later sections are `h2`.
20
20
  - Keep the media non-interactive; actions go in `contentSlot`.
21
21
  - Pass media as a **direct** `Image` / `img` / `picture` / `video` — no wrappers
22
- (Hero owns the crop; a nested `Image.aspectRatio` has no effect).
22
+ (Hero owns the crop; nested `Image.aspectRatio` and box size — `width`,
23
+ `height`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight` — have no effect).
23
24
 
24
25
  ## Regions
25
26
 
@@ -27,16 +28,24 @@ The first impression: one sentence of value, one clear action, and supporting me
27
28
 
28
29
  ## Responsive
29
30
 
30
- `Hero` owns it: `contentDirection="Left" | "Right"` pins a side panel on tablet and
31
- up and drops to a full-width bottom panel below; `Bottom` is full-width at every
32
- size. The crop adapts automatically. If the two actions feel tight on mobile, switch
33
- the `ButtonGroup` `direction` to `column` (`useBreakpointUp("tablet")`).
31
+ `Hero` owns the panel and crop: `contentDirection="Left" | "Right"` pins a side
32
+ panel on tablet and up and drops to a full-width bottom panel below; `Bottom` is
33
+ full-width at every size. The crop adapts automatically.
34
+
35
+ The actions are **not** component-owned. Two text `Button`s take the tablet switch
36
+ here exactly as anywhere else — `direction={tabletUp ? "row" : "column"}` with
37
+ `fullWidth={!tabletUp}`, not a judgement call about whether they "feel tight"
38
+ ([how-to-page-layout.md](how-to-page-layout.md#action-clusters)). A hero with a
39
+ single action passes no `direction`.
34
40
 
35
41
  ## Spacing
36
42
 
37
- Content-area spacing is component-owned. The hero is a full-bleed band, so it sits
38
- outside the capped content column — following sections resume the column
39
- ([how-to-page-layout.md](how-to-page-layout.md)).
43
+ Content-area spacing is component-owned and **density-invariant** — `Hero` owns its
44
+ own band padding, so do not wrap it in compensating `p` to reach a mode. The hero is
45
+ a full-bleed band, so it sits outside the capped content column — following sections
46
+ resume the column at the page's **Section** gap (`large-14` on the `spacious`
47
+ marketing page where a hero lives) ([how-to-page-layout.md](how-to-page-layout.md),
48
+ [density.md](density.md)).
40
49
 
41
50
  ## A11y
42
51
 
@@ -53,7 +62,13 @@ dominant brand band; keep later sections calmer ([how-to-color.md](how-to-color.
53
62
 
54
63
  ## React
55
64
 
65
+ `tabletUp` is the shared `useBreakpointUp("tablet")` helper — copy it from
66
+ [how-to-page-layout.md](how-to-page-layout.md#3-token-driven-js-narrow-fallback),
67
+ do not reimplement it or inline a px literal.
68
+
56
69
  ```tsx
70
+ const tabletUp = useBreakpointUp("tablet");
71
+
57
72
  <Hero
58
73
  contentDirection="Left"
59
74
  data-color-switcher="brand-1"
@@ -63,7 +78,7 @@ dominant brand band; keep later sections calmer ([how-to-color.md](how-to-color.
63
78
  <Lockup
64
79
  overline={<Text type="Overline" width={false}>New</Text>}
65
80
  title={
66
- <Text type="Display Medium" as="h1" width={false}>
81
+ <Text type="Display Large" as="h1" width={false}>
67
82
  One workspace for every team
68
83
  </Text>
69
84
  }
@@ -73,9 +88,13 @@ dominant brand band; keep later sections calmer ([how-to-color.md](how-to-color.
73
88
  </Text>
74
89
  }
75
90
  />
76
- <ButtonGroup aria-label="Get started">
77
- <Button emphasis="high"><Text type="Button" width={false}>Start free</Text></Button>
78
- <Button emphasis="medium"><Text type="Button" width={false}>Book a demo</Text></Button>
91
+ <ButtonGroup
92
+ direction={tabletUp ? "row" : "column"}
93
+ fullWidth={!tabletUp}
94
+ aria-label="Get started"
95
+ >
96
+ <Button emphasis="high"><Text type="Button" width>Start free</Text></Button>
97
+ <Button emphasis="medium"><Text type="Button" width>Book a demo</Text></Button>
79
98
  </ButtonGroup>
80
99
  </>
81
100
  }
@@ -84,6 +103,7 @@ dominant brand band; keep later sections calmer ([how-to-color.md](how-to-color.
84
103
 
85
104
  ## A2UI
86
105
 
87
- `A2uiHero` with a nested `A2uiImage` and a `contentSlot` of stacked `A2uiText`
88
- (no `Lockup` adapter) plus `A2uiButtonGroup`. Set the palette via the adapter's
89
- `data-color-switcher` ([a2ui.md](a2ui.md)).
106
+ `A2uiHero` with a nested `A2uiImage` (`src` + `alt` only — omit `aspectRatio` and
107
+ box size) and a `contentSlot` of stacked `A2uiText` (no `Lockup` adapter) plus
108
+ `A2uiButtonGroup`. Set the palette via the adapter's `data-color-switcher`
109
+ ([a2ui.md](a2ui.md)).
@@ -41,8 +41,16 @@ clusters) — no page-level breakpoint logic. See
41
41
 
42
42
  ## Spacing
43
43
 
44
- `medium-12` between in-column sections; `large-13`+ block padding for full-bleed
45
- bands; `--uxl-breakpoints-gutter` inside grids ([spacing-steps.md](spacing-steps.md)).
44
+ **Density: `spacious`** — generous whitespace is the argument on a public page, and
45
+ one idea should land per band ([density.md](density.md)). **Section** gap between
46
+ in-column sections is `large-14`; full-bleed bands take the **Band** step
47
+ (`large-15`, or `large-16` for a deliberate full-viewport hero statement);
48
+ `--uxl-breakpoints-gutter` stays the grid gap in every mode
49
+ ([spacing-steps.md](spacing-steps.md)).
50
+
51
+ Hold one mode for the whole page. A dense piece inside it (a pricing comparison, an
52
+ embedded table) keeps its own internals tight but still sits at the page's
53
+ `large-14` **Section** gap — density describes the experience, not each component.
46
54
 
47
55
  ## A11y
48
56
 
@@ -56,9 +64,23 @@ One dominant brand band (usually the hero). Apply palettes per section with
56
64
  `data-color-switcher`, style with role tokens, keep the calmer sections on `default`
57
65
  / `default-subtle` ([how-to-color.md](how-to-color.md)).
58
66
 
67
+ This page ships **two** full-bleed bands, so make the choice explicit rather than
68
+ letting both default to `brand-1`: the hero takes the brand palette and the closing
69
+ [cta-band](recipe-cta-band.md) drops to `default-subtle`. Flip it if the CTA is the
70
+ louder moment. What you must not do is set both to a brand palette — that is the
71
+ "stacked loud bands" this recipe's **when not** rules out, and it costs the hero its
72
+ dominance.
73
+
59
74
  ## React
60
75
 
76
+ `<main>` comes from [app-chrome](recipe-app-chrome.md) and is shown here only for
77
+ context — this recipe owns what goes *inside* it
78
+ ([how-to-page-layout.md](how-to-page-layout.md#who-owns-the-content-column)). Note
79
+ that a marketing page caps only its mid-page sections: the full-bleed bands are
80
+ deliberately outside any column.
81
+
61
82
  ```tsx
83
+ // Density: spacious (public marketing page) — see density.md
62
84
  <main id="main-content" tabIndex={-1} style={{ flexGrow: 1, minHeight: 0, overflow: "auto" }}>
63
85
  {/* Full-bleed hero — recipe-hero.md (contains the h1) */}
64
86
  <MarketingHero />
@@ -71,7 +93,7 @@ One dominant brand band (usually the hero). Apply palettes per section with
71
93
  maxWidth="var(--uxl-breakpoints-max-container-width)"
72
94
  mh="auto"
73
95
  p="var(--uxl-breakpoints-margin)"
74
- gap="var(--uxl-theme-layout-spacing-medium-12)"
96
+ gap="var(--uxl-theme-layout-spacing-large-14)"
75
97
  >
76
98
  <LogoWall /> {/* recipe-logo-wall.md */}
77
99
  <FeatureSections /> {/* recipe-feature-section.md (alternating) */}
@@ -80,7 +102,8 @@ One dominant brand band (usually the hero). Apply palettes per section with
80
102
  <Pricing /> {/* recipe-pricing.md */}
81
103
  </Layout>
82
104
 
83
- {/* Full-bleed closing CTA — recipe-cta-band.md */}
105
+ {/* Full-bleed closing CTA — recipe-cta-band.md.
106
+ Hero above is the brand band, so this one runs default-subtle. */}
84
107
  <CtaBand />
85
108
 
86
109
  {/* Footer — recipe-footer.md (marketing: directory band + legal bar) */}
@@ -29,9 +29,10 @@ fit — no JS, no breakpoint literals. Bound each mark's `width` so rows stay ev
29
29
 
30
30
  ## Spacing
31
31
 
32
- `gap="var(--uxl-theme-layout-spacing-medium-8)"` between marks; `small-4` under the
33
- eyebrow. As a band, add section breathing with `large-13`+ block padding
34
- ([spacing-steps.md](spacing-steps.md)).
32
+ **Group** step between marks and **Control** under the eyebrow — `medium-10` and
33
+ `small-6` on the `spacious` marketing page where a trust bar lives (`medium-8` /
34
+ `small-4` under `standard`). As a band, add breathing with the **Band** step
35
+ (`large-15`) ([density.md](density.md), [spacing-steps.md](spacing-steps.md)).
35
36
 
36
37
  ## A11y
37
38
 
@@ -55,7 +56,7 @@ full-color marks on dark). Keep one dominant brand band per view
55
56
  display="flex"
56
57
  flexDirection="column"
57
58
  alignItems="center"
58
- gap="var(--uxl-theme-layout-spacing-small-4)"
59
+ gap="var(--uxl-theme-layout-spacing-small-6)"
59
60
  aria-label="Trusted by leading teams"
60
61
  >
61
62
  <Text type="Overline" width={false}>Trusted by teams at</Text>
@@ -64,17 +65,18 @@ full-color marks on dark). Keep one dominant brand band per view
64
65
  flexWrap="wrap"
65
66
  justifyContent="center"
66
67
  alignItems="center"
67
- gap="var(--uxl-theme-layout-spacing-medium-8)"
68
+ gap="var(--uxl-theme-layout-spacing-medium-10)"
68
69
  >
69
70
  <Logo name="generic" width={112} color="colorSwitcherIcon" aria-label="Northwind" />
70
71
  <Logo name="generic" width={112} color="colorSwitcherIcon" aria-label="Acme" />
71
- {/* Logos not in the catalog use Image */}
72
- <Image src="/logos/contoso.svg" alt="Contoso" />
72
+ {/* Logos not in the catalog use Image; bound height to match the row */}
73
+ <Image src="/logos/contoso.svg" alt="Contoso" height={40} />
73
74
  </Layout>
74
75
  </Layout>
75
76
  ```
76
77
 
77
78
  ## A2UI
78
79
 
79
- No `getUxelleRecipe("logo-wall")` — compose `A2uiLogo` / `A2uiImage` inside an
80
- `A2uiLayout` (single wrapping row, layout-spacing only) ([a2ui.md](a2ui.md)).
80
+ No `getUxelleRecipe("logo-wall")` — compose `A2uiLogo` (`width`) / `A2uiImage`
81
+ (`height` or `width` so marks stay even) inside an `A2uiLayout` (single wrapping
82
+ row, layout-spacing only) ([a2ui.md](a2ui.md)).
@@ -34,8 +34,10 @@ narrow. See [how-to-page-layout.md](how-to-page-layout.md#action-clusters).
34
34
 
35
35
  ## Spacing
36
36
 
37
- `medium-12` between the stepper, the step body, and the footer; the step form uses
38
- its own `medium-8` field stack ([spacing-steps.md](spacing-steps.md)).
37
+ **Density: `standard`** — a flow asks for one decision at a time, so keep it calm
38
+ even inside a `compact` app ([density.md](density.md)). **Section** gap between the
39
+ stepper, the step body, and the footer (`medium-12`); the step form uses its own
40
+ **Group** field stack (`medium-8`) ([spacing-steps.md](spacing-steps.md)).
39
41
 
40
42
  ## A11y
41
43
 
@@ -65,7 +67,7 @@ const isLast = step === steps.length;
65
67
  p="var(--uxl-breakpoints-margin)"
66
68
  gap="var(--uxl-theme-layout-spacing-medium-12)"
67
69
  >
68
- <Text type="Display Extra Small" as="h1" width={false}>Create a workspace</Text>
70
+ <Text type="Display Small" as="h1" width={false}>Create a workspace</Text>
69
71
 
70
72
  <nav aria-label="Progress">
71
73
  <Stepper
@@ -19,9 +19,16 @@ status, and the actions that operate on the whole page.
19
19
  ## When not
20
20
 
21
21
  - Not a `PageHeader` component — it is a `Layout` composition.
22
- - Page actions only (create, import, export, delete). Controls that filter a table
22
+ - Page actions only (create, import, delete). Controls that filter a table
23
23
  belong to the [query-bar](recipe-query-bar.md); tabs that swap a table's rows
24
24
  belong on the `Table` ([data-table-page](recipe-data-table-page.md)).
25
+ - **Export splits by scope.** Export of the *whole dataset* is a page action and
26
+ belongs here — label it for that scope ("Export all"). Export of the *rows
27
+ currently in view* (after filters and search) belongs on the
28
+ [query-bar](recipe-query-bar.md), labelled for the view ("Export view"). Never
29
+ put both on one page: pick the scope the user actually needs. A
30
+ [data-table-page](recipe-data-table-page.md) defaults to "Export view" — omit
31
+ export from this header when that query bar is present.
25
32
  - Do not wrap `Lockup` in a nested `Layout` to share the row — let it be the flex
26
33
  sibling that shrinks and put `flexShrink={0}` on the action cluster.
27
34
 
@@ -40,9 +47,12 @@ would claim the row and drop the actions).
40
47
 
41
48
  ## Spacing
42
49
 
43
- `small-4` gap between title and actions. Lockup internals are `micro-2`
44
- (component-owned). The header sits in the page column at the `medium-12` section gap
45
- ([spacing-steps.md](spacing-steps.md)).
50
+ **Control** step between title and actions. Lockup internals are component-owned
51
+ (`micro-2`) and do not shift with the mode. The header sits in the page column at
52
+ the **Section** gap ([spacing-steps.md](spacing-steps.md)).
53
+
54
+ Inherits the host experience's mode — `micro-3` / `medium-9` under `compact`,
55
+ `small-4` / `medium-12` under `standard` ([density.md](density.md)).
46
56
 
47
57
  ## A11y
48
58
 
@@ -19,7 +19,7 @@ host canvas → content column (`max-container-width`, `mh="auto"`, `p` = breakp
19
19
 
20
20
  ## Spacing
21
21
 
22
- React page column: [how-to-page-layout.md](how-to-page-layout.md). Section stacks: medium-8 or small-4. Intro lockup internals: micro-2. Form/dialog action groups: [how-to-page-layout.md](how-to-page-layout.md#action-clusters). A2UI: layout-spacing only (`p` large-13, `gap` medium-12).
22
+ **Density: `standard`** unless the body is data-dense (then `compact`) or public marketing (then `spacious`) — [density.md](density.md). React page column: [how-to-page-layout.md](how-to-page-layout.md). **Section** gap between sections (`medium-12`); section stacks use **Group** (`medium-8`) or **Control** (`small-4`); intro lockup internals **Tight** (`micro-2`). Form/dialog action groups: [how-to-page-layout.md](how-to-page-layout.md#action-clusters). A2UI: layout-spacing only, `spacious` never applies (`p` **Band**-equivalent `large-13`, `gap` **Section** `medium-12`).
23
23
 
24
24
  ## A11y
25
25
 
@@ -43,7 +43,7 @@ React page column: [how-to-page-layout.md](how-to-page-layout.md). Section stack
43
43
  <Lockup
44
44
  topSlot={false}
45
45
  title={
46
- <Text type="Display Extra Small" as="h1" width={false}>
46
+ <Text type="Display Small" as="h1" width={false}>
47
47
  Account settings
48
48
  </Text>
49
49
  }
@@ -33,8 +33,11 @@ comfortable tier. See [how-to-page-layout.md](how-to-page-layout.md#responsivene
33
33
 
34
34
  ## Spacing
35
35
 
36
- `--uxl-breakpoints-gutter` between tiers; feature rows are the `List` default;
37
- `large-13`+ block padding as a band ([spacing-steps.md](spacing-steps.md)).
36
+ `--uxl-breakpoints-gutter` between tiers in every mode; feature rows keep the
37
+ `List` default (component-owned, density-invariant); the **Band** step as a band —
38
+ `large-15` on the `spacious` marketing page. A tier comparison is the one dense
39
+ piece on an airy page: let its feature rows stay tight while the section still sits
40
+ at the page's **Section** gap ([density.md](density.md), [spacing-steps.md](spacing-steps.md)).
38
41
 
39
42
  ## A11y
40
43
 
@@ -18,6 +18,11 @@ Controls that filter, facet, search, or export the rows/cards currently in view.
18
18
  ## When not
19
19
 
20
20
  - Page-level actions (create, import, delete) belong to the [page-header](recipe-page-header.md).
21
+ - **Export here is view-scoped only.** This bar exports what the filters and search
22
+ currently leave on screen, so label it for that ("Export view"). Exporting the
23
+ *whole dataset* ignores this bar's controls and is a page action —
24
+ [page-header](recipe-page-header.md). Never put both on one page: pick the
25
+ scope the user actually needs.
21
26
  - Data tabs that swap which rows are shown sit above the query bar, first in the
22
27
  `Table` `topSlot` — not on this row.
23
28
  - No filter *fields* on the bar: a filter *trigger* opens a right `Sheet`.
@@ -33,14 +38,25 @@ row **below**.
33
38
  ## Responsive
34
39
 
35
40
  The bar stays a row of controls. When very tight, let the search field take the
36
- full line above the filter/export cluster; the applied `FilterChipGroup` wraps.
41
+ full line above the filter/export cluster; the applied `FilterChipGroup` wraps
42
+ whole chips. A chip wider than the row ellipsizes so dismiss stays visible.
37
43
  Keep the filter and export buttons together (`flexShrink={0}`).
38
44
 
45
+ Chip labels are component-owned: pass `label` on `ChoiceChip` / `FilterChip` — do
46
+ not wrap `Text`. Unchecked choice chips use Condensed; checked use Condensed Alt.
47
+ Filter chips use Condensed. Exclusive `ChoiceChip` selection is one checked value
48
+ in app state (`role="group"`); use `RadioGroup` or `SegmentedControl` for radio
49
+ semantics.
50
+
39
51
  ## Spacing
40
52
 
41
- `small-4` gap within and between the two clusters; the applied-chips row is
42
- `small-4` below. This whole stack lives in the table's `topSlot` at `small-4`, not
43
- the page-section gap ([spacing-steps.md](spacing-steps.md)).
53
+ The **Control** step throughout — within and between the two clusters, and for the
54
+ applied-chips row below. This whole stack lives in the table's `topSlot` at the
55
+ **Control** step, never the **Section** gap ([spacing-steps.md](spacing-steps.md)).
56
+
57
+ As a piece, it inherits the host experience's density mode: `micro-3` on a
58
+ `compact` data-table page (its usual home), `small-4` under `standard`
59
+ ([density.md](density.md)).
44
60
 
45
61
  ## A11y
46
62
 
@@ -92,7 +108,7 @@ See [how-to-accessibility.md](how-to-accessibility.md).
92
108
  />
93
109
  </Layout>
94
110
  <Layout flexShrink={0}>
95
- <Button emphasis="low"><Text type="Button" width>Export all</Text></Button>
111
+ <Button emphasis="low"><Text type="Button" width>Export view</Text></Button>
96
112
  </Layout>
97
113
  </Layout>
98
114
  </Layout>
@@ -37,8 +37,11 @@ scrolls when the labels overflow. See [how-to-page-layout.md](how-to-page-layout
37
37
 
38
38
  ## Spacing
39
39
 
40
- `medium-12` between page sections; `--uxl-breakpoints-gutter` between the main and
41
- aside columns; summary rows `small-4` ([spacing-steps.md](spacing-steps.md)).
40
+ **Density: `standard`**; step to `compact` when the record is a dense diagnostic
41
+ view (many fields scanned side by side) rather than a read-and-act page
42
+ ([density.md](density.md)). **Section** gap between page sections (`medium-12`);
43
+ **Control** for summary rows (`small-4`); `--uxl-breakpoints-gutter` between the
44
+ main and aside columns in every mode ([spacing-steps.md](spacing-steps.md)).
42
45
 
43
46
  ## A11y
44
47
 
@@ -38,7 +38,7 @@ Standalone fields (email, language, phone, about) stay in the section stack at `
38
38
 
39
39
  ## Spacing
40
40
 
41
- Page column: [how-to-page-layout.md](how-to-page-layout.md). Section gap: `medium-12`. Lockup internals: `micro-2`. Form field stack: `medium-8`. Inline related fields: `small-4`. Do not add extra padding around `ListControls`. Form actions: [how-to-page-layout.md](how-to-page-layout.md#action-clusters).
41
+ **Density: `standard`** — settings are read and decided on, so comprehension beats volume; do not compact a preferences page ([density.md](density.md)). Page column: [how-to-page-layout.md](how-to-page-layout.md). **Section** gap `medium-12`. **Tight** lockup internals `micro-2`. **Group** form field stack `medium-8`. **Control** inline related fields `small-4`. Do not add extra padding around `ListControls` — its row rhythm is component-owned. Form actions: [how-to-page-layout.md](how-to-page-layout.md#action-clusters).
42
42
 
43
43
  ## A11y
44
44
 
@@ -65,7 +65,7 @@ Import from `@uxelle/components`. Pass `bottomText` (or `""`) so the catalog dem
65
65
  <Lockup
66
66
  topSlot={false}
67
67
  title={
68
- <Text type="Display Extra Small" as="h1" width={false}>
68
+ <Text type="Display Small" as="h1" width={false}>
69
69
  Settings
70
70
  </Text>
71
71
  }
@@ -163,7 +163,7 @@ Import from `@uxelle/components`. Pass `bottomText` (or `""`) so the catalog dem
163
163
  </Layout>
164
164
  ```
165
165
 
166
- If the host already renders `<main>` and the page column (skip-link target), omit `as="main"` and extra `p` on this stack — still set `maxWidth="600px"` on the settings column (or on the host column when this is the only page in view).
166
+ If chrome already renders `<main>` (the skip-link target), omit `as="main"` — but still render this column yourself, `p` and `maxWidth="600px"` included. Chrome supplies only the scrollport, never a column, so there is nothing to avoid duplicating ([how-to-page-layout.md](how-to-page-layout.md#who-owns-the-content-column)).
167
167
 
168
168
  ## A2UI
169
169
 
@@ -38,8 +38,13 @@ its track. A row variant can use vertical `Divider`s between cells that simply w
38
38
 
39
39
  ## Spacing
40
40
 
41
- `gap="var(--uxl-breakpoints-gutter)"` between cells. As a marketing band, add
42
- section breathing with `large-13`+ block padding
41
+ `gap="var(--uxl-breakpoints-gutter)"` between cells — the grid gap is
42
+ density-invariant, so KPI tiles align identically in every mode.
43
+
44
+ This piece serves both ends of the density range and inherits the host's mode
45
+ ([density.md](density.md)). As dashboard KPIs on a `compact` page it takes no band
46
+ padding at all and sits at the page's `medium-9` **Section** gap. As a marketing
47
+ band on a `spacious` page it adds breathing with the **Band** step (`large-15`)
43
48
  ([spacing-steps.md](spacing-steps.md)).
44
49
 
45
50
  ## A11y
@@ -69,7 +74,7 @@ Plain figures (marketing proof band):
69
74
  gap="var(--uxl-breakpoints-gutter)"
70
75
  aria-label="By the numbers"
71
76
  >
72
- <Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-2)">
77
+ <Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-3)">
73
78
  <Text type="Display Medium" as="p" width={false}>10k+</Text>
74
79
  <Text type="Body Small" width={false}>Teams onboarded</Text>
75
80
  </Layout>
@@ -19,8 +19,10 @@ Any region backed by data or an async action: lists, tables, cards, forms, panel
19
19
  - Do not blank the whole page for a partial load — scope the state to the region.
20
20
  - Do not signal status with color alone; pair with an icon or words
21
21
  ([how-to-color.md](how-to-color.md)).
22
- - Do not swap a `SkeletonGroup` out of the tree while loading — keep it mounted and
23
- toggle `loading`.
22
+ - Do not swap a `SkeletonGroup` out of the tree while loading. Keep **the group**
23
+ mounted across the whole load and toggle its `loading` prop; swapping its
24
+ *children* between shapes and real content is expected (see the example).
25
+ Unmounting the group loses the load/complete announcement.
24
26
 
25
27
  ## Loading
26
28
 
@@ -68,7 +70,10 @@ where the data would be, plus the action that creates the first item.
68
70
  ## Error
69
71
 
70
72
  A recoverable, in-flow failure uses a non-dismissible `danger` `Banner` with a retry
71
- action.
73
+ action. The single action passes no `direction` — row is the `ButtonGroup` default
74
+ and one child looks the same either way. Add a second text button (a Dismiss beside
75
+ Try again) and the cluster takes the tablet switch like any other
76
+ ([how-to-page-layout.md](how-to-page-layout.md#action-clusters)).
72
77
 
73
78
  ```tsx
74
79
  <Banner
@@ -77,7 +82,7 @@ action.
77
82
  alert
78
83
  leadingSlotContent={<Icon iconName="error" variant="sharpUnfilled" aria-hidden />}
79
84
  actionsSlotContent={
80
- <ButtonGroup direction="row" aria-label="Error actions">
85
+ <ButtonGroup aria-label="Error actions">
81
86
  <Button emphasis="medium" onClick={retry}>
82
87
  <Text type="Button" width>Try again</Text>
83
88
  </Button>
@@ -33,9 +33,11 @@ long, stack each pair (label above value) below tablet by switching
33
33
 
34
34
  ## Spacing
35
35
 
36
- Row/column `gap` = `var(--uxl-theme-layout-spacing-small-4)`. Reset the browser
37
- `dd` indent with `margin: 0`. Group inside a section at the page rhythm
38
- ([spacing-steps.md](spacing-steps.md)).
36
+ Row/column `gap` = the **Control** step
37
+ (`var(--uxl-theme-layout-spacing-small-4)` under `standard`, `micro-3` under
38
+ `compact` — a key/value summary is a common dense surface). Reset the browser `dd`
39
+ indent with `margin: 0`. Group inside a section at the page rhythm
40
+ ([spacing-steps.md](spacing-steps.md), [density.md](density.md)).
39
41
 
40
42
  ## A11y
41
43
 
@@ -32,6 +32,12 @@ narration.
32
32
  ([how-to-page-layout.md](how-to-page-layout.md#responsiveness)).
33
33
  5. **Spacing** — stacks use `var(--uxl-theme-layout-spacing-*)` (or `0`); page
34
34
  chrome uses the breakpoint tokens ([spacing-steps.md](spacing-steps.md)).
35
+ Name spacing by **role** (Flush / Tight / Control / Group / Section / Band), not
36
+ by a bare step. An **experience** declares its default **density mode**
37
+ (`compact` | `standard` | `spacious`) and why; a **piece** says it inherits the
38
+ host's mode and gives the resolution for the modes it realistically appears in.
39
+ Call out anything density-invariant (component-owned padding, grid gutters)
40
+ ([density.md](density.md)).
35
41
  6. **A11y** — landmarks, heading ranks, names, live regions
36
42
  ([how-to-accessibility.md](how-to-accessibility.md)).
37
43
  7. **Color** — include when the recipe uses palettes or brand bands: set
@@ -32,8 +32,10 @@ A single testimonial is a centered, measure-capped block. A set reflows with
32
32
 
33
33
  ## Spacing
34
34
 
35
- `small-4` between the quote and attribution; `--uxl-breakpoints-gutter` between cards
36
- in a grid; `large-13`+ block padding as a band ([spacing-steps.md](spacing-steps.md)).
35
+ **Control** step between the quote and its attribution (`small-6` on the `spacious`
36
+ marketing page, `small-4` under `standard`); `--uxl-breakpoints-gutter` between
37
+ cards in a grid in every mode; the **Band** step (`large-15`) as a band
38
+ ([density.md](density.md), [spacing-steps.md](spacing-steps.md)).
37
39
 
38
40
  ## A11y
39
41
 
@@ -54,7 +56,7 @@ associated. A customer `Logo` is decorative when the name is in text
54
56
  </Text>
55
57
  </blockquote>
56
58
  <figcaption>
57
- <Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-2)">
59
+ <Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-3)">
58
60
  <Text type="Body Small Alt" width={false}>Dana Lee</Text>
59
61
  <Text type="Condensed" width={false}>VP Operations, Northwind</Text>
60
62
  </Layout>
@@ -18,16 +18,24 @@ number"). The size band must match the index, so `medium-10` exists but
18
18
 
19
19
  ## Choosing a step
20
20
 
21
+ Do not pick a step directly. Pick the **role** the space plays, then resolve it
22
+ through the experience's **density mode** — [density.md](density.md).
23
+
21
24
  - **Flush** (line boxes already separate lines, e.g. name over email) -> `0`.
22
- - **Tight stacks / chips** -> `micro-2`.
23
- - **Related controls / field stacks** -> `small-4`. Same-row clusters (search +
25
+ - **Tight** stacks / chips / lockup internals.
26
+ - **Control** — related controls and field stacks. Same-row clusters (search +
24
27
  export, filter + facet) use this gap and stay `nowrap`.
25
- - **Card / section padding** (also the default A2UI gap) -> `medium-8`.
26
- - **Page-section gap** (lockup vs table vs a later block) -> `medium-12`.
27
- - **Hero / extra breathing inside a section** -> `large-13`+.
28
+ - **Group** — bespoke section padding and subgroups (also the default A2UI gap).
29
+ - **Section** — between page sections (lockup vs table vs a later block).
30
+ - **Band** — block padding on a full-bleed band (hero, CTA).
31
+
32
+ The `standard` mode resolves these to `0` / `micro-2` / `small-4` / `medium-8` /
33
+ `medium-12` / `large-13` — the harness baseline. `compact` shifts the column down
34
+ for data-dense product surfaces, `spacious` up for marketing. Resolve the whole
35
+ column from one mode; never tighten a single role in isolation.
28
36
 
29
- Do not use the page-section gap (`medium-12`) between a table's tabs and its grid —
30
- those belong together at `small-4`.
37
+ Do not use the **Section** gap between a table's tabs and its grid — those belong
38
+ together at the **Control** gap.
31
39
 
32
40
  ## Usage
33
41
 
@@ -50,7 +58,8 @@ emit them in new generated UI** — use the indexed tokens.
50
58
  ## Not part of this ramp
51
59
 
52
60
  Page inset, column cap, and column/`fr` gaps are **page chrome**, not ramp steps:
53
- `--uxl-breakpoints-margin` / `-max-container-width` / `-gutter`. See
61
+ `--uxl-breakpoints-margin` / `-max-container-width` / `-gutter`. They are also
62
+ **density-invariant** — they do not change with the mode ([density.md](density.md)). See
54
63
  [how-to-page-layout.md](how-to-page-layout.md) and [tokens.md](tokens.md). One-column
55
64
  reading content (a settings/form page, or a form / FAQ / prose section on a wider
56
65
  page) may cap at the `maxWidth="600px"` reading measure — centered — instead of the
@@ -38,8 +38,10 @@ Never hardcode hex/rgb for themeable UI. Palette selection (`default`,
38
38
  ## Spacing (layout ramp)
39
39
 
40
40
  Stacks on `Layout` / `A2uiLayout` pass `0` or `var(--uxl-theme-layout-spacing-*)`
41
- on `gap` / padding / margin. The full 0-16 ramp and how to choose a step is in
42
- [spacing-steps.md](spacing-steps.md). Set `display="flex"` (or `grid`) when using `gap`.
41
+ on `gap` / padding / margin. The full 0-16 ramp and the spacing **roles** are in
42
+ [spacing-steps.md](spacing-steps.md); which step each role resolves to depends on
43
+ the experience's **density mode** ([density.md](density.md)). Set `display="flex"`
44
+ (or `grid`) when using `gap`.
43
45
 
44
46
  ## Page chrome (React app/page body)
45
47
 
@@ -50,6 +52,13 @@ Usage in [how-to-page-layout.md](how-to-page-layout.md).
50
52
  - `--uxl-breakpoints-margin` — padding around the body
51
53
  - `--uxl-breakpoints-gutter` — gap between columns / `fr` tracks
52
54
 
55
+ `--uxl-breakpoints-padding-<band>-<index>` is the per-breakpoint source the spacing
56
+ ramp aliases (`--uxl-theme-layout-spacing-medium-8` resolves to
57
+ `--uxl-breakpoints-padding-medium-8`). Use the `--uxl-theme-layout-spacing-*` name
58
+ in app and recipe CSS; the `padding` family appears only where a catalog component
59
+ documents its own band padding (e.g. `Footer` —
60
+ [recipe-app-chrome.md](recipe-app-chrome.md)). Same index rule applies.
61
+
53
62
  Breakpoint bounds (read-only, for the rare JS switch) are exposed as
54
63
  `--uxl-theme-layout-{mobile,tablet,desktop,desktop-plus}-screen-width-{min,max}`,
55
64
  and the active band as `--uxl-breakpoints-screen-width-{min,max}`. Reference these
@@ -71,12 +80,44 @@ must, compose a `box-shadow` from levels `1`-`3` at depths `1`-`4`:
71
80
  ## Typography
72
81
 
73
82
  Render copy with the `Text` component (`type="Display Small"`, `Body Medium`,
74
- `Button`, …) and its `as` prop — never set font-size in app CSS. Font-family
75
- variables exist for custom chrome only:
83
+ `Button`, …) and its `as` prop — never set font-size in app CSS. Which `type` a
84
+ heading rank gets depends on the density mode ([density.md](density.md)).
85
+ Font-family variables exist for custom chrome only:
76
86
 
77
87
  - `--uxl-component-text-font-family-display`
78
88
  - `--uxl-component-text-font-family-body`
79
89
 
90
+ ### `width` on `Text`
91
+
92
+ `width` defaults to `true` and makes the text box **hug its content**;
93
+ `width={false}` makes it **span its container**, which is what lets long copy wrap
94
+ and `truncation` clip to a stable width.
95
+
96
+ - **`width={false}`** for anything that wraps or truncates — headings, body copy,
97
+ descriptions, `Lockup` slots, `Dialog` titles and bodies.
98
+ - **`width`** (the default) for a short label inside a control — `type="Button"`
99
+ labels, chips, badges — so the control sizes to its text.
100
+
101
+ Chip labels are **component-owned**. Pass the `label` string on `ChoiceChip` /
102
+ `FilterChip` (and their A2UI adapters) — do not wrap a `Text` node.
103
+
104
+ - **ChoiceChip** — `Condensed` when unchecked, `Condensed Alt` when checked.
105
+ Exclusive single-select is one checked value in app/host state. Keep
106
+ `role="group"`; chips stay `aria-pressed` toggles. Use `RadioGroup` or
107
+ `SegmentedControl` for radio semantics.
108
+ - **FilterChip** — `Condensed`. Labels hug content so `FilterChipGroup` wraps
109
+ whole chips. A chip wider than its container ellipsizes so the dismiss
110
+ control stays visible.
111
+
112
+ ## Inline `style` on the React track
113
+
114
+ React recipes may use inline `style` for what `Layout` props do not express —
115
+ a scrollport (`overflow`, `minHeight`), a `backgroundColor` on a palette region, or
116
+ native `dl` grid tracks — **provided every value is a `--uxl-*` variable**. Inline
117
+ `style` is not a licence to hardcode: no hex, no px, no font-size. The "no
118
+ `className` / `style`" rule is **A2UI-only**, where styling must come from adapter
119
+ props ([a2ui.md](a2ui.md)).
120
+
80
121
  ## Component tokens
81
122
 
82
123
  `--uxl-component-<component>-*` tokens style a specific component from inside that