@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
@@ -7,18 +7,25 @@ React app frame: skip link, [`Navigation`](../uxelle-components/Navigation.md),
7
7
  ## When not
8
8
 
9
9
  - Not an `AppChrome` / `PageShell` component.
10
+ - **Chrome does not render the content column.** `<main>` here is only the
11
+ scrollport — the **experience** inside it owns the capped column
12
+ ([how-to-page-layout.md](how-to-page-layout.md#who-owns-the-content-column)). Do
13
+ not emit a `maxWidth` + `--uxl-breakpoints-margin` wrapper from this recipe *and*
14
+ from the experience recipe, or the page is padded and capped twice.
10
15
  - Theme CSS and `data-*`: [how-to-host.md](how-to-host.md).
11
16
  - A2UI chat surfaces: skip link, 100% viewport shell, and html `data-*` are host-owned. Do not emit this recipe as A2UI. Optional `A2uiNavigation` / `A2uiFooter` only when the host already has a surface.
12
17
 
13
18
  ## Regions
14
19
 
15
- skip link → `Navigation` (header) and/or `NavigationSide` (side rail wrapping `NavigationSideGroup` rows) → `<main id="main-content">` → content column ([how-to-page-layout.md](how-to-page-layout.md)) → optional `Footer` **inside that same `<main>`**
20
+ skip link → `Navigation` (header) and/or `NavigationSide` (side rail wrapping `NavigationSideGroup` rows) → `<main id="main-content">` → *the experience's* content column ([how-to-page-layout.md](how-to-page-layout.md#who-owns-the-content-column)) → optional `Footer` **inside that same `<main>`**
16
21
 
17
22
  ## Spacing
18
23
 
19
- Nav is a catalog component (its own tokens). Main is the scrollport (no page padding). The **page column inside main** uses breakpoint margin / max-container-width (one-column settings may use `600px` — [recipe-settings-page.md](recipe-settings-page.md)). When present, `Footer` sits after that column in the same scrollport.
24
+ Chrome is **density-invariant**: nav and footer are catalog components with their own tokens, and the margin / max-container-width on the experience's column never shift with the mode either. The experience picks the density mode and renders that column ([density.md](density.md)) — chrome does neither.
20
25
 
21
- Do not wrap `Footer` in extra `Layout` padding. Band padding is catalog-owned: two-band footers use `--uxl-breakpoints-padding-large-13`; a **single** band (legal-only, unused slot `null`) uses `--uxl-theme-layout-spacing-small-4` on the block axis. That is the related-controls step, not page-section (`medium-12`) or hero (`large-13`).
26
+ Nav is a catalog component (its own tokens). Main is the scrollport — **no page padding and no column cap here**; the experience's own column supplies breakpoint margin / max-container-width (one-column settings may use `600px` — [recipe-settings-page.md](recipe-settings-page.md)). When present, `Footer` sits after that column in the same scrollport.
27
+
28
+ Do not wrap `Footer` in extra `Layout` padding. Band padding is catalog-owned: two-band footers use `--uxl-breakpoints-padding-large-13`; a **single** band (legal-only, unused slot `null`) uses `--uxl-theme-layout-spacing-small-4` on the block axis. That is the **Control** step, not **Section** or **Band**.
22
29
 
23
30
  ## A11y
24
31
 
@@ -73,17 +80,14 @@ Import from `@uxelle/components`. Put skip-link CSS in the host stylesheet.
73
80
  />
74
81
  </Layout>
75
82
  <main id="main-content" tabIndex={-1} style={{ flexGrow: 1, minHeight: 0, overflow: "auto" }}>
76
- <Layout
77
- display="flex"
78
- flexDirection="column"
79
- width="100%"
80
- maxWidth="var(--uxl-breakpoints-max-container-width)"
81
- mh="auto"
82
- p="var(--uxl-breakpoints-margin)"
83
- gap="var(--uxl-theme-layout-spacing-medium-12)"
84
- >
85
- {/* page body — recipe-page-shell.md / recipe-form-section.md / recipe-data-table-page.md / recipe-settings-page.md */}
86
- </Layout>
83
+ {/*
84
+ The experience renders its own capped column here — chrome does not.
85
+ It owns maxWidth / mh / p / gap so it can set its density mode and, for a
86
+ one-column form or settings view, the 600px reading measure.
87
+ recipe-page-shell.md / recipe-dashboard-overview.md / recipe-data-table-page.md /
88
+ recipe-settings-page.md
89
+ */}
90
+ <PageBody />
87
91
  <Footer
88
92
  topMainContent={null}
89
93
  bottomMainContent={
@@ -108,7 +112,7 @@ When-not for this recipe. Host owns chrome (skip link, viewport shell, html `dat
108
112
 
109
113
  ## Help and marketing chrome
110
114
 
111
- Use the same `Navigation` when there are **no** primary app links (help center, marketing). Pass `bottomSlot={null}` to hide the secondary bar. Unused **primary** slots must not be omitted — omitted (`undefined`) or `null` on leading/center/trailing still fill catalog demo content (`??`). Pass `centerSlot={false}` when there is no center nav.
115
+ Use the same `Navigation` when there are **no** primary app links (help center, marketing). Pass `bottomSlot={null}` to hide the secondary bar. Unused **primary** slots must not be omitted — an omitted (`undefined`) `leadingSlot` / `centerSlot` / `trailingSlot` fills catalog demo content. Pass **`null`** to drop a slot entirely (`centerSlot={null}` when there is no center nav) — the same pattern as `bottomSlot={null}`. Do not pass `false`: it clears the content but still renders an empty slot wrapper in the flex row.
112
116
 
113
117
  Nav trailing (search, language, Log in / Register) stays a **row** at every width — [how-to-page-layout.md](how-to-page-layout.md#action-clusters). Do not stack those controls on mobile.
114
118
 
@@ -122,7 +126,7 @@ Nav trailing (search, language, Log in / Register) stays a **row** at every widt
122
126
  <Logo name="generic" width={40} interactive aria-hidden />
123
127
  </a>
124
128
  }
125
- centerSlot={false}
129
+ centerSlot={null}
126
130
  trailingSlot={
127
131
  <>
128
132
  <LanguageSelector value="EN" />
@@ -35,13 +35,20 @@ minmax(MIN, 1fr))` drops from 3-up to 2-up to 1-up as width shrinks; pick `MIN`
35
35
 
36
36
  ## Spacing
37
37
 
38
- `gap="var(--uxl-breakpoints-gutter)"`. Card inner padding is component-owned. The
39
- page column still uses [how-to-page-layout.md](how-to-page-layout.md).
38
+ `gap="var(--uxl-breakpoints-gutter)"`. Card inner padding is component-owned. Both
39
+ are **density-invariant** — a card grid looks the same in every mode, so do not
40
+ tighten the gutter for a `compact` dashboard or pad it out for a marketing page
41
+ ([density.md](density.md)). Stacks *inside* a card use the host's **Control** or
42
+ **Group** step. The page column still uses [how-to-page-layout.md](how-to-page-layout.md).
40
43
 
41
44
  ## A11y
42
45
 
43
46
  Name the group when the cards are a set (`aria-label` on the grid `Layout`).
44
- Headings inside cards continue page rank (`h2` under the page `h1`). A `ProductCard`
47
+ Headings inside cards continue page rank (`h2` under the page `h1`) but take the
48
+ type **one stop below** that rank's default, since the card already supplies the
49
+ separation — with `Display Extra Small` as the floor. A `spacious` card `h2` is
50
+ `Display Small`; a product card `h2` is already at the floor and stays
51
+ `Display Extra Small`, as shown ([density.md](density.md)). A `ProductCard`
45
52
  CTA names itself; if the whole card is the link, the heading text is the name. See
46
53
  [how-to-accessibility.md](how-to-accessibility.md).
47
54
 
@@ -100,5 +107,7 @@ Case-study / product tiles (`ProductCard`, media + CTA):
100
107
  ## A2UI
101
108
 
102
109
  `A2uiLayout` has no `gridTemplateColumns` — stack `A2uiCard` / `A2uiProductCard` in
103
- a column with `var(--uxl-theme-layout-spacing-*)` `gap`. No
104
- `getUxelleRecipe("card-grid")` ([a2ui.md](a2ui.md)).
110
+ a column with `var(--uxl-theme-layout-spacing-*)` `gap`. Media in
111
+ `A2uiProductCard` `centerSlotContent` is `A2uiImage` with `aspectRatio` (omit
112
+ `width` so it fills the card). No `getUxelleRecipe("card-grid")`
113
+ ([a2ui.md](a2ui.md)).
@@ -16,8 +16,10 @@ A single, decisive conversion moment: "Start free," "Talk to sales."
16
16
 
17
17
  - One primary action (an optional secondary is fine) — not a menu of links.
18
18
  - Section heading is `h2`, not another `h1`.
19
- - Keep it to one dominant brand band per page; if the hero is loud, this can be
20
- quieter, or vice versa.
19
+ - Keep it to one dominant brand band per page, and make that concrete rather than
20
+ vague: if the hero already carries a brand palette, set this band to
21
+ `default-subtle`. Only one of the two gets a `brand-*` palette
22
+ ([recipe-landing-page.md](recipe-landing-page.md)).
21
23
 
22
24
  ## Regions
23
25
 
@@ -33,8 +35,10 @@ viewports and stack `fullWidth` below tablet (`useBreakpointUp("tablet")`). See
33
35
 
34
36
  ## Spacing
35
37
 
36
- `large-13`+ block padding for the band; `medium-8` between the lockup and actions
37
- ([spacing-steps.md](spacing-steps.md)).
38
+ **Band** step for the band's block padding and **Group** between the lockup and the
39
+ actions — `large-15` and `medium-10` on a `spacious` marketing page, its usual home
40
+ ([density.md](density.md), [spacing-steps.md](spacing-steps.md)). A CTA closing a
41
+ `standard` product surface resolves to `large-13` / `medium-8`.
38
42
 
39
43
  ## A11y
40
44
 
@@ -50,20 +54,26 @@ role tokens — never hardcode hex. Pick the brand palette by the look you want
50
54
 
51
55
  ## React
52
56
 
57
+ `tabletUp` is the shared `useBreakpointUp("tablet")` helper — copy it from
58
+ [how-to-page-layout.md](how-to-page-layout.md#3-token-driven-js-narrow-fallback),
59
+ do not reimplement it or inline a px literal.
60
+
53
61
  ```tsx
62
+ const tabletUp = useBreakpointUp("tablet");
63
+
54
64
  <Layout
55
65
  as="section"
56
66
  data-color-switcher="brand-1"
57
67
  display="flex"
58
68
  justifyContent="center"
59
- p="var(--uxl-theme-layout-spacing-large-13)"
69
+ p="var(--uxl-theme-layout-spacing-large-15)"
60
70
  style={{ backgroundColor: "var(--uxl-color-switcher-background)" }}
61
71
  >
62
72
  <Layout
63
73
  display="flex"
64
74
  flexDirection="column"
65
75
  alignItems="center"
66
- gap="var(--uxl-theme-layout-spacing-medium-8)"
76
+ gap="var(--uxl-theme-layout-spacing-medium-10)"
67
77
  width="100%"
68
78
  maxWidth="var(--uxl-breakpoints-max-container-width)"
69
79
  >
@@ -36,10 +36,16 @@ when narrow. See [how-to-page-layout.md](how-to-page-layout.md#responsiveness).
36
36
 
37
37
  ## Spacing
38
38
 
39
- `medium-12` between page sections (header / KPIs / primary / supporting);
40
- `--uxl-breakpoints-gutter` inside the grids. Section intros are `micro-2`
39
+ **Density: `compact`** — a dashboard's job is to show more at a glance, so the
40
+ column tightens ([density.md](density.md)). **Section** gap between page sections
41
+ (header / KPIs / primary / supporting) is `medium-9`; subgroups inside a section
42
+ use **Group** (`small-6`); section intros use **Tight** (`micro-1`).
43
+ `--uxl-breakpoints-gutter` stays the grid gap in every mode
41
44
  ([spacing-steps.md](spacing-steps.md)).
42
45
 
46
+ Step up to `standard` when the overview is a light landing page with two or three
47
+ KPIs rather than a dense monitoring surface — but pick one mode and hold it.
48
+
43
49
  ## A11y
44
50
 
45
51
  One `main`, one `h1`. Name each region (KPIs `aria-label="Key metrics"`, the primary
@@ -55,6 +61,7 @@ alerts, never as decoration ([how-to-color.md](how-to-color.md)).
55
61
  ## React
56
62
 
57
63
  ```tsx
64
+ // Density: compact (data-dense dashboard) — see density.md
58
65
  <Layout
59
66
  display="flex"
60
67
  flexDirection="column"
@@ -62,7 +69,7 @@ alerts, never as decoration ([how-to-color.md](how-to-color.md)).
62
69
  maxWidth="var(--uxl-breakpoints-max-container-width)"
63
70
  mh="auto"
64
71
  p="var(--uxl-breakpoints-margin)"
65
- gap="var(--uxl-theme-layout-spacing-medium-12)"
72
+ gap="var(--uxl-theme-layout-spacing-medium-9)"
66
73
  >
67
74
  {/* Page header — recipe-page-header.md */}
68
75
  <DashboardHeader />
@@ -90,18 +97,23 @@ alerts, never as decoration ([how-to-color.md](how-to-color.md)).
90
97
  <LabelBadge label="+4" dataColorSwitcher="success" emphasis="low" leadingIcon leadingIconName="trending_up" />
91
98
  }
92
99
  />
100
+ {/*
101
+ Palette tracks whether the change is *good*, not whether the number rose.
102
+ Fewer open tickets is an improvement, so this delta is `success` while
103
+ trending down — say so in the label rather than relying on the arrow.
104
+ */}
93
105
  <StatTile
94
106
  label="Open tickets"
95
107
  value="18"
96
108
  trailingSlot
97
109
  trailingSlotContent={
98
- <LabelBadge label="-6" dataColorSwitcher="success" emphasis="low" leadingIcon leadingIconName="trending_down" />
110
+ <LabelBadge label="6 fewer" dataColorSwitcher="success" emphasis="low" leadingIcon leadingIconName="trending_down" />
99
111
  }
100
112
  />
101
113
  </Layout>
102
114
 
103
115
  {/* Primary surface — recipe-data-table-page.md (with states) */}
104
- <Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-small-4)">
116
+ <Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-3)">
105
117
  <Text type="Display Extra Small" as="h2" width={false}>Recent activity</Text>
106
118
  {isLoading ? <ActivitySkeleton /> : rows.length ? <ActivityTable rows={rows} /> : <ActivityEmpty />}
107
119
  </Layout>
@@ -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