@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
@@ -11,7 +11,8 @@ Composes [`Table`](../uxelle-components/Table.md), [`TabsCompact`](../uxelle-com
11
11
  - Not a `DataTablePage` / `TablePage` component.
12
12
  - Do not invent a toolbar component. Two bands, two jobs — do not mix them:
13
13
  - **Page actions** (add, invite, import, delete, re-upload) sit with the title, in a `ButtonGroup`.
14
- - **Query controls** (filter, facets, search, export) sit on the table, in `Table` `topSlot`.
14
+ - **Query controls** (filter, facets, search, view-scoped export) sit on the table, in `Table` `topSlot`.
15
+ - **Export splits by scope — pick one.** "Export view" (what the filters leave on screen) rides the query bar; "Export all" (the whole dataset) is a page action beside the title ([recipe-page-header.md](recipe-page-header.md)). Never both on one page. This records page defaults to **Export view**.
15
16
  - **Data tabs are not page navigation.** `Navigation` / `NavLink` change pages (Referrals vs Earnings). `TabsCompact` that switch which rows are in **this** table (referrals vs connections, active vs invited) live in `Table` `topSlot`, immediately above the query bar and grid. Do not park them under the page title.
16
17
  - Do not put a `Banner` (or any other page section) between data tabs and the grid.
17
18
  - `ButtonGroup` only renders `Button` children — do not nest `Sheet` or `Textfield` in it.
@@ -20,7 +21,7 @@ Composes [`Table`](../uxelle-components/Table.md), [`TabsCompact`](../uxelle-com
20
21
  - Do not put table search in `Navigation`.
21
22
  - `Lockup` and `Textfield` are `width: 100%` of the parent. Do not put them in a `flexWrap="wrap"` row with actions — they take the line and stack siblings underneath. Keep `flexWrap="nowrap"` (Layout default). Do **not** wrap `Lockup` in another `Layout` (`flexGrow` / `flexBasis={0}`) to sit beside a `ButtonGroup`. Do not put `minWidth` on the actions to invent a wrap threshold. Size a search field (`width="16rem"`, `flexShrink={0}`). Actions: `Layout` `flexShrink={0}` around the `ButtonGroup` only. Stack the header with `flexDirection="column"` below tablet.
22
23
  - Table has no expandable / nested-row API. v1 is **flat rows**.
23
- - A2UI: no `getUxelleRecipe("data-table-page")` yet. Compose `A2uiTable*` / `A2uiSheet` / `A2uiTabGroup` / `A2uiTextfield`. `Lockup` has no adapter — stack `A2uiText`. Layout-spacing only (no breakpoint chrome).
24
+ - A2UI: no `getUxelleRecipe("data-table-page")` yet. Compose `A2uiTable*` / `A2uiSheet` / `A2uiTabGroup` / `A2uiTextfield` / `A2uiChoiceChip` / `A2uiFilterChipGroup`. `Lockup` has no adapter — stack `A2uiText`. Layout-spacing only (no breakpoint chrome).
24
25
 
25
26
  ## Regions
26
27
 
@@ -30,13 +31,13 @@ page column → **header** (title left, page actions right) → optional `Banner
30
31
  ┌ Title + optional subtitle [Delete] [Upload] ┐
31
32
  ├ optional Banner ┤
32
33
  ├ Table.topSlot: Tabs (this dataset / that dataset) ┤
33
- ├ [Filter] [facet] [Search……] [Export] ┤
34
+ ├ [Filter] [facet] [Search……] [Export view] ┤
34
35
  ├ [FilterChip] [FilterChip] ┤
35
36
  ├ Table.tableSlot: grid, or empty copy ┤
36
37
  └ Table.bottomSlot: Pagination ┘
37
38
  ```
38
39
 
39
- Page `gap` (`medium-12`) is between **page sections** (header vs table). Inside `topSlot`, use `small-4` so tabs sit on the table, not in a previous section.
40
+ Page `gap` (the **Section** step — `medium-9` under this recipe's `compact` mode) is between **page sections** (header vs table). Inside `topSlot`, use the **Control** step (`micro-3`) so tabs sit on the table, not in a previous section.
40
41
 
41
42
  ## What goes where
42
43
 
@@ -49,13 +50,13 @@ Page `gap` (`medium-12`) is between **page sections** (header vs table). Inside
49
50
  **Query bar — narrow the current view.** Next in `topSlot`, one row, `justifyContent="space-between"`:
50
51
 
51
52
  - **Left — constrain:** Filter `Button` → `Sheet`. Name the trigger after a single dimension (`Status`, `Role`); use `Filter` when the sheet has several groups. Optional `ChoiceChip` for a sticky facet. Applied values: `FilterChipGroup` **under** this row.
52
- - **Right — find:** search and utilities **on one line**. Wrapper around `Textfield` with a bounded `width` (e.g. `16rem`) and `flexShrink={0}`. `Export all` / Refresh as `emphasis="low"` `Button` siblings, cluster `flexWrap="nowrap"`. Not inside the Filter `ButtonGroup`.
53
+ - **Right — find:** search and utilities **on one line**. Wrapper around `Textfield` with a bounded `width` (e.g. `16rem`) and `flexShrink={0}`. `Export view` / Refresh as `emphasis="low"` `Button` siblings, cluster `flexWrap="nowrap"`. Not inside the Filter `ButtonGroup`. Default is `Export view` here. If the page needs the whole dataset instead, put `Export all` beside the title and omit this control — never both.
53
54
 
54
55
  **Footer — page the result.** `Pagination` in `bottomSlot`.
55
56
 
56
57
  ## Spacing
57
58
 
58
- Page column: [how-to-page-layout.md](how-to-page-layout.md). Header and query bar: `small-4` gap, `space-between`. `topSlot` stack (tabs → query bar → chips): `small-4`. Do not wrap `Table` in extra padding. Placement rules: [how-to-page-layout.md](how-to-page-layout.md#keep-controls-with-what-they-operate).
59
+ **Density: `compact`** — a records page exists to show rows ([density.md](density.md)). Page column: [how-to-page-layout.md](how-to-page-layout.md). Header and query bar use the **Control** step (`micro-3`) with `space-between`; the `topSlot` stack (tabs → query bar → chips) uses the same **Control** step; the **Section** gap between page sections is `medium-9`. Do not wrap `Table` in extra padding — row height is component-owned and does not shift with the mode, so never claw back space by shrinking hit areas. Placement rules: [how-to-page-layout.md](how-to-page-layout.md#keep-controls-with-what-they-operate).
59
60
 
60
61
  ## A11y
61
62
 
@@ -69,6 +70,9 @@ Set `alignment` on **both** `TableHeaderCell` and `TableCell`:
69
70
  - Money, counts, numeric → `right`
70
71
  - Category / visibility / status (`LabelBadge`) → `center`
71
72
  - A column that is only a CTA (`Button` / `IconButton` / `Link`) → `center`
73
+ - Selection / checkbox: `center`, and set `flex: 0` with `maxWidth` (e.g. 72) on that column track so leftover `fr` space does not stretch it on a wide viewport. `flex: 0` without a pixel `minWidth` or `maxWidth` sizes the track to content. `maxWidth` on a growing (`flex > 0`) column is a resize ceiling only.
74
+
75
+ 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 (native tooltip shows the full title). Do not pass `truncation` on body `Text` unless a dense single-line column is required.
72
76
 
73
77
  Identity cells: stack `Text type="Body Small Alt"` (name) and `Text type="Condensed"` (email) with `gap={0}`. Line boxes already separate the lines — do not add `micro-2` (or any layout-spacing) between them. Do not invent an Avatar component.
74
78
 
@@ -81,62 +85,27 @@ Keep `Table`, data tabs, and the query bar in `topSlot`. Put a `role="status"` s
81
85
  ## Responsive
82
86
 
83
87
  - **Header** stacks below tablet: switch the header `Layout` `flexDirection` to `column` and let the `ButtonGroup` go `direction="column"` `fullWidth` (`useBreakpointUp("tablet")`, [how-to-page-layout.md](how-to-page-layout.md#action-clusters)).
84
- - **Query bar** stays a row of controls; when very tight, the search field can take the full line above the filter/export cluster. Applied `FilterChipGroup` wraps.
85
- - **Table** scrolls horizontally when narrower than its columns — there is no stacked-row API. Keep the query bar and tabs above it.
88
+ - **Query bar** stays a row of controls; when very tight, the search field can take the full line above the filter/export cluster. Applied `FilterChipGroup` wraps whole chips; a too-wide chip ellipsizes so dismiss stays visible.
89
+ - **Table** scrolls horizontally when narrower than its columns — there is no stacked-row API. Keep the query bar and tabs above it. Before any column is resized, header titles stay unwrapped and the grid’s min width is the sum of those titles (plus column mins). After a resize, every column is a pixel track at the widths on screen (siblings do not reflow to fill the container). Header titles in a squeezed track ellipsize. Call `useResetTableColumnWidths()` to restore every column to its template.
86
90
  - **Pagination** swaps to [`PaginationSimple`](../uxelle-components/PaginationSimple.md) where the full control is too wide.
87
91
 
88
92
  ## React
89
93
 
90
- Import from `@uxelle/components`. Data tabs and the query bar both go in `topSlot` — tabs first. `tabletUp` comes from `useBreakpointUp("tablet")` ([how-to-page-layout.md](how-to-page-layout.md#action-clusters)).
94
+ Import from `@uxelle/components`. Data tabs and the query bar both go in `topSlot` — tabs first.
95
+
96
+ `RecordsHeader` is [recipe-page-header.md](recipe-page-header.md) as-is, with two deltas for this page: the `h1` is `Display Small` and the row gap is the `compact` **Control** step (`micro-3`). Do not add `Export all` to that header when the query bar already has `Export view`. `TableQueryBar` is [recipe-query-bar.md](recipe-query-bar.md), with its gaps at `micro-3` and its export labelled `Export view`. Build both from their own recipes rather than copying them here — the rules that differ are in **Regions** above.
91
97
 
92
98
  ```tsx
93
- <Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-small-4)">
94
- <Layout
95
- display="flex"
96
- flexDirection={tabletUp ? "row" : "column"}
97
- flexWrap="nowrap"
98
- justifyContent="space-between"
99
- alignItems={tabletUp ? "flex-end" : "stretch"}
100
- gap="var(--uxl-theme-layout-spacing-small-4)"
101
- >
102
- <Lockup
103
- topSlot={false}
104
- title={
105
- <Text type="Display Extra Small" as="h1" width={false}>
106
- Referrals
107
- </Text>
108
- }
109
- body={
110
- <Text type="Body Medium" width={false}>
111
- Connections, matches, and potential earnings in one register.
112
- </Text>
113
- }
114
- />
115
- <Layout flexShrink={0}>
116
- <ButtonGroup
117
- direction={tabletUp ? "row" : "column"}
118
- fullWidth={!tabletUp}
119
- aria-label="Referral actions"
120
- >
121
- <Button emphasis="medium">
122
- <Text type="Button" width>
123
- Delete
124
- </Text>
125
- </Button>
126
- <Button emphasis="high">
127
- <Text type="Button" width>
128
- Upload
129
- </Text>
130
- </Button>
131
- </ButtonGroup>
132
- </Layout>
133
- </Layout>
99
+ // Density: compact (records page — rows are the point) — see density.md
100
+ <Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-3)">
101
+ {/* Page header — recipe-page-header.md (h1 + collection actions; no Export all) */}
102
+ <RecordsHeader />
134
103
 
135
104
  {/* optional Banner — here, not inside Table */}
136
105
 
137
106
  <Table
138
107
  topSlot={
139
- <Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-small-4)">
108
+ <Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-3)">
140
109
  <TabsCompact
141
110
  amount={2}
142
111
  tabLabels={["Referrals (12)", "Connections (6)"]}
@@ -167,85 +136,6 @@ Import from `@uxelle/components`. Data tabs and the query bar both go in `topSlo
167
136
  </Layout>
168
137
  ```
169
138
 
170
- Query bar (filters left; search and export **nowrap** on the right):
171
-
172
- ```tsx
173
- function TableQueryBar() {
174
- return (
175
- <Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-small-4)">
176
- <Layout
177
- display="flex"
178
- flexDirection="row"
179
- flexWrap="nowrap"
180
- justifyContent="space-between"
181
- alignItems="center"
182
- gap="var(--uxl-theme-layout-spacing-small-4)"
183
- aria-label="Table filters and search"
184
- >
185
- <Layout
186
- display="flex"
187
- flexDirection="row"
188
- flexWrap="nowrap"
189
- alignItems="center"
190
- gap="var(--uxl-theme-layout-spacing-small-4)"
191
- >
192
- <Sheet
193
- direction="Right"
194
- title="Filters"
195
- closeLabel="Close filters"
196
- trigger={
197
- <Button emphasis="medium" leadingIcon leadingIconName="filter_list">
198
- <Text type="Button" width>
199
- Status
200
- </Text>
201
- </Button>
202
- }
203
- >
204
- <CheckboxGroup name="status" label labelText="Status">
205
- <Checkbox value="open">
206
- <Text type="Body Medium">Open</Text>
207
- </Checkbox>
208
- <Checkbox value="matched">
209
- <Text type="Body Medium">Matched</Text>
210
- </Checkbox>
211
- </CheckboxGroup>
212
- </Sheet>
213
- <ChoiceChip label="Top match" onChange={() => {}} />
214
- </Layout>
215
- <Layout
216
- display="flex"
217
- flexDirection="row"
218
- flexWrap="nowrap"
219
- alignItems="center"
220
- gap="var(--uxl-theme-layout-spacing-small-4)"
221
- flexShrink={0}
222
- >
223
- <Layout width="16rem" flexShrink={0}>
224
- <Textfield
225
- label={false}
226
- aria-label="Search by name"
227
- name="table-search"
228
- autoComplete="off"
229
- placeholder="Search by name"
230
- leadingIcon
231
- leadingIconName="search"
232
- />
233
- </Layout>
234
- <Layout flexShrink={0}>
235
- <Button emphasis="low">
236
- <Text type="Button" width>
237
- Export all
238
- </Text>
239
- </Button>
240
- </Layout>
241
- </Layout>
242
- </Layout>
243
- {/* Optional: FilterChipGroup of applied filters */}
244
- </Layout>
245
- );
246
- }
247
- ```
248
-
249
139
  Grid cells (alignment on header and body):
250
140
 
251
141
  ```tsx
@@ -266,7 +156,10 @@ Grid cells (alignment on header and body):
266
156
  </TableCell>
267
157
  ```
268
158
 
269
- Confirm / upload `Dialog` (page actions, not on the table):
159
+ Confirm / upload `Dialog` (page actions, not on the table). `tabletUp` is the shared
160
+ `useBreakpointUp("tablet")` helper — copy it from
161
+ [how-to-page-layout.md](how-to-page-layout.md#3-token-driven-js-narrow-fallback),
162
+ do not reimplement it or inline a px literal:
270
163
 
271
164
  ```tsx
272
165
  <Dialog
@@ -296,11 +189,11 @@ Confirm / upload `Dialog` (page actions, not on the table):
296
189
  </Text>
297
190
  }
298
191
  bottomSlotContent={
299
- <ButtonGroup
300
- direction={tabletUp ? "row" : "column"}
301
- fullWidth={!tabletUp}
302
- aria-label="Delete confirmation"
303
- >
192
+ <ButtonGroup
193
+ direction={tabletUp ? "row" : "column"}
194
+ fullWidth={!tabletUp}
195
+ aria-label="Delete confirmation"
196
+ >
304
197
  <Button emphasis="low" size="medium" onClick={() => setDeleteOpen(false)}>
305
198
  <Text type="Button" width>
306
199
  Cancel
@@ -320,4 +213,4 @@ Upload dialogs put `FileUpload` `fullWidth` in `centerSlotContent`. Title `Text`
320
213
 
321
214
  ## A2UI
322
215
 
323
- Same region order and layout-spacing strings. Bind row data and search/filter state. Runtime: no `getUxelleRecipe("data-table-page")` yet — compose `A2uiTable*` / `A2uiSheet` / `A2uiTabGroup` / `A2uiTextfield` ([a2ui.md](a2ui.md)). `Lockup` has no adapter — stack `A2uiText`. `A2uiText` in `A2uiDialog` slots uses `"width": false`.
216
+ Same region order and layout-spacing strings. Bind row data and search/filter state. Runtime: no `getUxelleRecipe("data-table-page")` yet — compose `A2uiTable*` / `A2uiSheet` / `A2uiTabGroup` / `A2uiTextfield` / `A2uiChoiceChip` / `A2uiFilterChipGroup` ([a2ui.md](a2ui.md)). `Lockup` has no adapter — stack `A2uiText`. `A2uiText` in `A2uiDialog` slots uses `"width": false`. Chip labels stay on the adapter (`label`); do not nest `A2uiText` inside chips. Exclusive `A2uiChoiceChip` selection is one checked value in host state; use `A2uiRadioGroup` or `A2uiSegmentedControl` for radio semantics.
@@ -35,9 +35,10 @@ Two columns from tablet up, single column (media over copy) below — use
35
35
 
36
36
  ## Spacing
37
37
 
38
- `--uxl-breakpoints-gutter` between the columns; `medium-12` between stacked feature
39
- sections; `large-13`+ block padding if the section is its own band
40
- ([spacing-steps.md](spacing-steps.md)).
38
+ `--uxl-breakpoints-gutter` between the columns in every mode. **Section** step
39
+ between stacked feature sections and the **Band** step if the section is its own
40
+ band — `large-14` and `large-15` on the `spacious` marketing page where these live
41
+ ([density.md](density.md), [spacing-steps.md](spacing-steps.md)).
41
42
 
42
43
  ## A11y
43
44
 
@@ -61,7 +62,7 @@ const tabletUp = useBreakpointUp("tablet");
61
62
  >
62
63
  <Lockup
63
64
  overline={<Text type="Overline" width={false}>Automation</Text>}
64
- title={<Text type="Display Small" as="h2" width={false}>Ship without the busywork</Text>}
65
+ title={<Text type="Display Medium" as="h2" width={false}>Ship without the busywork</Text>}
65
66
  body={<Text type="Body Medium" width={false}>Rules handle the repetitive steps so your team ships.</Text>}
66
67
  />
67
68
  <Image src="/features/automation.png" alt="Automation rules editor" aspectRatio="4:3" />
@@ -74,7 +75,7 @@ alternates sides.
74
75
  Switched variant (capabilities behind a control):
75
76
 
76
77
  ```tsx
77
- <Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-medium-8)">
78
+ <Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-medium-10)">
78
79
  <SegmentedControl
79
80
  labelText="Capabilities"
80
81
  value={feature}
@@ -92,5 +93,6 @@ Switched variant (capabilities behind a control):
92
93
  ## A2UI
93
94
 
94
95
  No `getUxelleRecipe("feature-section")` and no `gridTemplateColumns` — stack
95
- `A2uiText` copy then `A2uiImage` in one column; the switched variant uses
96
- `A2uiSegmentedControl` or `A2uiTabGroup` ([a2ui.md](a2ui.md)).
96
+ `A2uiText` copy then `A2uiImage` (`aspectRatio` to crop; omit `width` so it fills
97
+ the column) in one column; the switched variant uses `A2uiSegmentedControl` or
98
+ `A2uiTabGroup` ([a2ui.md](a2ui.md)).
@@ -107,10 +107,13 @@ leads. The legal bar wraps its links. See
107
107
 
108
108
  ## Spacing
109
109
 
110
- `--uxl-breakpoints-gutter` between columns; `micro-2` within a link list; `small-4`
111
- inside the brand block and the legal bar. Band padding is component-owned — a single
112
- remaining band is compact (`small-4`), two bands use the large step; do not wrap
113
- `Footer` in extra `p` / `gap`.
110
+ `--uxl-breakpoints-gutter` between columns; the **Tight** step within a link list;
111
+ the **Control** step inside the brand block and the legal bar (`micro-2` / `small-4`
112
+ under `standard`) — [density.md](density.md).
113
+
114
+ Band padding is **component-owned and density-invariant**: a single remaining band
115
+ is tight (`small-4`), two bands use the large step. Do not wrap `Footer` in extra
116
+ `p` / `gap` to hit a density mode.
114
117
 
115
118
  ## A11y
116
119
 
@@ -23,9 +23,9 @@ Any catalog field: [`Textfield`](../uxelle-components/Textfield.md), [`Textarea`
23
23
 
24
24
  ## Spacing
25
25
 
26
- `gap="var(--uxl-theme-layout-spacing-medium-8)"` between fields (or field **rows**). Header stack `var(--uxl-theme-layout-spacing-micro-2)`. Actions in `ButtonGroup` (not ad-hoc `Layout` for two buttons) — row from tablet up, column below tablet ([how-to-page-layout.md](how-to-page-layout.md#action-clusters)). Parts of one value on one row: `small-4` inside the grid.
26
+ **Density: `standard`** — a form is read and filled, so it stays legible; a form embedded in a `compact` console may take that mode, but never tighten a public marketing form ([density.md](density.md)). The **Group** step goes between fields (or field **rows**) — `gap="var(--uxl-theme-layout-spacing-medium-8)"` under `standard`. Header stack uses **Tight** (`var(--uxl-theme-layout-spacing-micro-2)`). Parts of one value on one row use **Control** (`small-4`) inside the grid. Actions in `ButtonGroup` (not ad-hoc `Layout` for two buttons) — row from tablet up, column below tablet ([how-to-page-layout.md](how-to-page-layout.md#action-clusters)).
27
27
 
28
- **Separate the actions from the fields.** The submit / action cluster is a decision point, not one more field — give it `medium-12` from the last field (the page-section gap), not the `medium-8` field gap. At the field gap the primary button reads as grouped with the control above it (e.g. a submit sitting right under the last `Select`). Group the heading + fields in one stack and make the actions a sibling at `medium-12` (see the React example).
28
+ **Separate the actions from the fields.** The submit / action cluster is a decision point, not one more field — give it the **Section** step from the last field (`medium-12` under `standard`), not the **Group** field gap. At the field gap the primary button reads as grouped with the control above it (e.g. a submit sitting right under the last `Select`). Group the heading + fields in one stack and make the actions a sibling at the **Section** step (see the React example). This separation must survive every mode: under `compact` it is `medium-9` against a `small-6` field gap — still the required ~1.5x apart.
29
29
 
30
30
  ## Measure
31
31
 
@@ -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