@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.
- package/README.md +3 -5
- package/dist/index.js +106 -96
- package/index.json +106 -96
- package/package.json +1 -1
- package/skills/uxelle-components/ChoiceChip.md +1 -1
- package/skills/uxelle-components/ChoiceChipGroup.md +3 -3
- package/skills/uxelle-components/FilterChip.md +2 -2
- package/skills/uxelle-components/FilterChipGroup.md +1 -1
- package/skills/uxelle-components/Hero.md +2 -2
- package/skills/uxelle-components/Icon.md +4 -4
- package/skills/uxelle-components/Image.md +10 -3
- package/skills/uxelle-components/MultiSelect.md +3 -2
- package/skills/uxelle-components/NavigationSide.md +3 -3
- package/skills/uxelle-components/NavigationSideItem.md +4 -4
- package/skills/uxelle-components/NavigationSideSubItem.md +3 -3
- package/skills/uxelle-components/SKILL.md +9 -2
- package/skills/uxelle-components/Select.md +2 -1
- package/skills/uxelle-components/StatTile.md +13 -42
- package/skills/uxelle-components/Table.md +7 -4
- package/skills/uxelle-components/getting-started.md +85 -0
- package/skills/uxelle-design-harness/SKILL.md +44 -20
- package/skills/uxelle-design-harness/a2ui.md +31 -2
- package/skills/uxelle-design-harness/density.md +138 -0
- package/skills/uxelle-design-harness/how-to-accessibility.md +3 -1
- package/skills/uxelle-design-harness/how-to-color.md +6 -3
- package/skills/uxelle-design-harness/how-to-host.md +8 -21
- package/skills/uxelle-design-harness/how-to-page-layout.md +51 -6
- package/skills/uxelle-design-harness/principles.md +10 -6
- package/skills/uxelle-design-harness/recipe-app-chrome.md +20 -16
- package/skills/uxelle-design-harness/recipe-card-grid.md +14 -5
- package/skills/uxelle-design-harness/recipe-cta-band.md +16 -6
- package/skills/uxelle-design-harness/recipe-dashboard-overview.md +21 -7
- package/skills/uxelle-design-harness/recipe-data-table-page.md +30 -137
- package/skills/uxelle-design-harness/recipe-feature-section.md +9 -7
- package/skills/uxelle-design-harness/recipe-footer.md +7 -4
- package/skills/uxelle-design-harness/recipe-form-section.md +2 -2
- package/skills/uxelle-design-harness/recipe-hero.md +35 -15
- package/skills/uxelle-design-harness/recipe-landing-page.md +27 -4
- package/skills/uxelle-design-harness/recipe-logo-wall.md +11 -9
- package/skills/uxelle-design-harness/recipe-multi-step-flow.md +5 -3
- package/skills/uxelle-design-harness/recipe-page-header.md +14 -4
- package/skills/uxelle-design-harness/recipe-page-shell.md +2 -2
- package/skills/uxelle-design-harness/recipe-pricing.md +5 -2
- package/skills/uxelle-design-harness/recipe-query-bar.md +21 -5
- package/skills/uxelle-design-harness/recipe-record-detail.md +5 -2
- package/skills/uxelle-design-harness/recipe-settings-page.md +3 -3
- package/skills/uxelle-design-harness/recipe-stat-callouts.md +14 -7
- package/skills/uxelle-design-harness/recipe-states.md +9 -4
- package/skills/uxelle-design-harness/recipe-summary-list.md +5 -3
- package/skills/uxelle-design-harness/recipe-template.md +6 -0
- package/skills/uxelle-design-harness/recipe-testimonial.md +5 -3
- package/skills/uxelle-design-harness/spacing-steps.md +17 -8
- package/skills/uxelle-design-harness/tokens.md +45 -4
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Density: spacing and type by experience
|
|
2
|
+
|
|
3
|
+
The same structural role wants different space in different experiences. A dense
|
|
4
|
+
console packs sections together so more data fits on screen; a marketing page opens
|
|
5
|
+
them up so one idea lands per band. Both are correct — on the *same* grid, with the
|
|
6
|
+
*same* ramp.
|
|
7
|
+
|
|
8
|
+
Density is the lever. Pick a **mode** for the experience, then resolve each spacing
|
|
9
|
+
**role** and each **heading rank** through that mode. Roles are the vocabulary the
|
|
10
|
+
recipes speak in ("section gap", "field stack"); the mode decides which ramp step
|
|
11
|
+
each one lands on ([spacing-steps.md](spacing-steps.md)).
|
|
12
|
+
|
|
13
|
+
## 1. Pick the mode
|
|
14
|
+
|
|
15
|
+
One mode per experience, chosen once before you write any layout:
|
|
16
|
+
|
|
17
|
+
- **`compact`** — data-dense product surfaces where screen real estate is the
|
|
18
|
+
scarce resource: dashboards, data tables, consoles, admin and ops tooling,
|
|
19
|
+
monitoring, inbox/queue views.
|
|
20
|
+
- **`standard`** — task product surfaces where comprehension beats density:
|
|
21
|
+
forms, settings, record detail, wizards, generic page bodies. **This is the
|
|
22
|
+
default** when the brief is unclear.
|
|
23
|
+
- **`spacious`** — public marketing and editorial surfaces that sell an idea:
|
|
24
|
+
landing pages, heroes, pricing, closing CTAs, feature bands.
|
|
25
|
+
|
|
26
|
+
State the mode in a comment at the top of the surface so the choice is auditable:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// Density: compact (data-dense dashboard) — see density.md
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 2. Resolve the roles
|
|
33
|
+
|
|
34
|
+
| Role — what it separates | `compact` | `standard` | `spacious` |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| **Flush** — lines already separated by their line box | `0` | `0` | `0` |
|
|
37
|
+
| **Tight** — lockup internals, chip rows, label over value | `micro-1` | `micro-2` | `micro-3` |
|
|
38
|
+
| **Control** — related fields, same-row clusters, toolbars | `micro-3` | `small-4` | `small-6` |
|
|
39
|
+
| **Group** — subgroups within a section, bespoke section padding | `small-6` | `medium-8` | `medium-10` |
|
|
40
|
+
| **Section** — between page sections | `medium-9` | `medium-12` | `large-14` |
|
|
41
|
+
| **Band** — block padding on a full-bleed band | `medium-12` | `large-13` | `large-15` |
|
|
42
|
+
|
|
43
|
+
`large-16` is reserved for a deliberate editorial statement — a full-viewport hero
|
|
44
|
+
band — not the default `spacious` band. `small-5`, `medium-7`, and `medium-11` are
|
|
45
|
+
tuning room between modes; reach for them only to fix a specific rhythm problem.
|
|
46
|
+
|
|
47
|
+
Emit the resolved token, not the role name — a compact section gap is
|
|
48
|
+
`gap="var(--uxl-theme-layout-spacing-medium-9)"`. There is no `--uxl-*` density
|
|
49
|
+
variable and no app-owned alias layer; resolve at generation time.
|
|
50
|
+
|
|
51
|
+
`standard` is the harness baseline — it is the value every recipe used before
|
|
52
|
+
density existed, so a `standard` surface looks exactly as it always did.
|
|
53
|
+
|
|
54
|
+
## 3. Resolve the heading ranks
|
|
55
|
+
|
|
56
|
+
Rank is semantics; **type** is what makes rank visible. Map every heading through
|
|
57
|
+
the same mode so size and weight carry hierarchy — never color
|
|
58
|
+
([principles.md](principles.md)).
|
|
59
|
+
|
|
60
|
+
| Rank | `compact` | `standard` | `spacious` |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| `h1` — one per page | `Display Small` | `Display Small` | `Display Large` |
|
|
63
|
+
| `h2` — page section | `Display Extra Small` | `Display Extra Small` | `Display Medium` |
|
|
64
|
+
| `h3` — subsection | `Component Medium` | `Component Medium` | `Display Small` |
|
|
65
|
+
| Body copy | `Body Medium` | `Body Medium` | `Body Medium` |
|
|
66
|
+
|
|
67
|
+
**Consecutive ranks must never share a type.** The `Display` scale is `Large`
|
|
68
|
+
(64px) → `Medium` (45) → `Small` (32) → `Extra Small` (22), then `Component Medium`
|
|
69
|
+
(18) and `Body Medium` (16) on desktop. An `h1` and `h2` both set to
|
|
70
|
+
`Display Extra Small` render identically and flatten the page, which reads as one
|
|
71
|
+
undifferentiated block no matter how correct the markup is.
|
|
72
|
+
|
|
73
|
+
`compact` and `standard` share the heading ramp on purpose: density is a *spacing*
|
|
74
|
+
concern, while Display-led headlines are a *marketing* one. On a genuinely dense
|
|
75
|
+
console where vertical space is critical, `compact` may drop to `h1`
|
|
76
|
+
`Display Extra Small` / `h2` `Component Medium` — still one full stop apart.
|
|
77
|
+
|
|
78
|
+
**Step down one stop inside a bounded container, but never below
|
|
79
|
+
`Display Extra Small`.** A heading inside a `Card`, `ProductCard`, or tile takes the
|
|
80
|
+
type one stop below its rank's default, because the container's border and padding
|
|
81
|
+
already supply the separation the larger type would carry — a `spacious` feature
|
|
82
|
+
card's `h2` is `Display Small`, not `Display Medium`. `Display Extra Small` is the
|
|
83
|
+
floor for any heading: a product `h2` already sits there, so a product card heading
|
|
84
|
+
stays put rather than dropping to `Component Medium`, which would read as a label
|
|
85
|
+
instead of a heading.
|
|
86
|
+
|
|
87
|
+
Dense supporting text — table cells, metadata, KPI labels — may use `Body Small`
|
|
88
|
+
(14px) under `compact`. Never go below `Body Small` for reading copy.
|
|
89
|
+
|
|
90
|
+
## 4. Guardrails
|
|
91
|
+
|
|
92
|
+
**The grid never moves.** Page chrome — `--uxl-breakpoints-max-container-width`,
|
|
93
|
+
`-margin`, and `-gutter` — is **density-invariant**. Density modulates rhythm
|
|
94
|
+
*within* and *between* blocks, never the column cap, the page inset, or column
|
|
95
|
+
gaps. That is what keeps a compact page and a spacious page on one grid, and it is
|
|
96
|
+
the Swiss commitment in [principles.md](principles.md). Grid gaps stay
|
|
97
|
+
`--uxl-breakpoints-gutter` in every mode.
|
|
98
|
+
|
|
99
|
+
**Keep the gap roles at least ~1.5x apart.** A reader infers grouping by comparing
|
|
100
|
+
gaps, so **Tight → Control → Group → Section** must stay visibly distinct or the
|
|
101
|
+
page reads as undifferentiated mush. Every mode above satisfies this (`compact`
|
|
102
|
+
runs 4 → 10 → 16 → 24px on desktop). This is the failure mode of `compact`: tighten
|
|
103
|
+
the section gap to `medium-9` while leaving the group gap at `medium-8` and sections
|
|
104
|
+
stop being legible as sections. Tighten *the whole column* or none of it — never one
|
|
105
|
+
role in isolation.
|
|
106
|
+
|
|
107
|
+
**Band** is exempt: it is padding inset from a band edge, not a gap between
|
|
108
|
+
siblings, so it is not compared against the gap roles. A `spacious` band's `large-15`
|
|
109
|
+
padding sitting near the `large-14` section gap is correct, not a collision.
|
|
110
|
+
|
|
111
|
+
**`compact` has an accessibility floor.** Tightening must never bring touch targets
|
|
112
|
+
below 24 CSS px or let interactive rows collide. Catalog controls already meet the
|
|
113
|
+
floor — do not claw space back by shrinking hit areas
|
|
114
|
+
([how-to-accessibility.md](how-to-accessibility.md)).
|
|
115
|
+
|
|
116
|
+
**No breakpoint logic for density.** The ramp already remaps per breakpoint (a
|
|
117
|
+
`spacious` section gap is 72px on desktop and 48px on mobile on its own), so density
|
|
118
|
+
and responsiveness compose for free. Never add `matchMedia` or a second mode for
|
|
119
|
+
small screens ([how-to-page-layout.md](how-to-page-layout.md#responsiveness)).
|
|
120
|
+
|
|
121
|
+
**Do not mix modes in one experience.** A dense surface nested inside a spacious
|
|
122
|
+
page (a pricing comparison on a landing page, a table in a marketing section) keeps
|
|
123
|
+
its *own internals* compact, but the surrounding **section** rhythm stays the page's
|
|
124
|
+
mode. Density describes the experience, not each component.
|
|
125
|
+
|
|
126
|
+
## 5. Out of scope
|
|
127
|
+
|
|
128
|
+
Density does not reach into **component internals**. `Card` padding, `Table` row
|
|
129
|
+
height, `Button` insets, `Lockup` internals, and `Footer` band padding are
|
|
130
|
+
component-owned and already tuned — do not wrap a component in compensating padding
|
|
131
|
+
or override its spacing to hit a mode.
|
|
132
|
+
|
|
133
|
+
## A2UI
|
|
134
|
+
|
|
135
|
+
Chat surfaces are inherently narrow and have no full-bleed bands, so **`spacious`
|
|
136
|
+
never applies** and the `Band` role has no meaning. Use `standard` by default and
|
|
137
|
+
`compact` for data-heavy runtime output, resolving the same
|
|
138
|
+
`var(--uxl-theme-layout-spacing-*)` strings ([a2ui.md](a2ui.md)).
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
WCAG 2.x **Level AA** for pages and composed experiences. Prefer catalog components (`Button`, `Textfield`, `Textarea`, `Dialog`, …) so keyboard, focus rings, and field associations come for free. Contrast is theme-owned — use `--uxl-color-switcher-*`.
|
|
4
4
|
|
|
5
|
+
This file covers **composition**: landmarks, heading ranks, names, and live regions across a whole page. Component-level patterns (keyboard behavior, focus management, ARIA inside a single component) live in the workspace rule `.cursor/rules/accessibility.mdc`, and token/color relationships are owned by the theme layer. Read that rule when building or fixing a component rather than a page.
|
|
6
|
+
|
|
5
7
|
## Baseline
|
|
6
8
|
|
|
7
9
|
- One `h1`, then `h2`… with no skipped ranks (`Text as="h1"` / `as="h2"`).
|
|
@@ -24,7 +26,7 @@ WCAG 2.x **Level AA** for pages and composed experiences. Prefer catalog compone
|
|
|
24
26
|
- `aria-describedby` — extra help, not the name (`fieldDescription`, instructions).
|
|
25
27
|
- `aria-hidden="true"` — decorative / redundant graphics (default `Icon`).
|
|
26
28
|
- `aria-live="polite"` — status that appears without moving focus. `assertive` only for urgent interruptions.
|
|
27
|
-
- `aria-current="page"` — current item in nav (`NavLink`, or `activated` on `NavigationSideItem` / `NavigationSideSubItem` destinations — not accordion parents).
|
|
29
|
+
- `aria-current="page"` — current item in nav (`NavLink`, or `activated` on `NavigationSideItem` / `NavigationSideSubItem` destinations — not accordion parents). When the side rail is collapsed, an accordion parent of an activated nested row still shows the selected appearance so the current section remains visible on the icon-only rail.
|
|
28
30
|
- `aria-expanded` — only on a real disclosure control, not on every section.
|
|
29
31
|
- `aria-pressed` — toggle buttons (`IconButton` `activated`); `Switch` uses `checked` instead.
|
|
30
32
|
|
|
@@ -10,8 +10,10 @@ in [tokens.md](tokens.md)); choose which palette those roles resolve to with
|
|
|
10
10
|
`data-color-switcher="<palette>"` on a region remaps the `--uxl-color-switcher-*`
|
|
11
11
|
roles for that subtree; nested components inherit from the nearest ancestor.
|
|
12
12
|
|
|
13
|
-
- Set it on a **region** — a nav band, hero, section band, banner, or badge
|
|
14
|
-
|
|
13
|
+
- Set it on a **region** — a nav band, hero, section band, banner, or badge. The
|
|
14
|
+
**host** owns the one page-level default on `<html>`
|
|
15
|
+
(`data-color-switcher="default"`, set once during host setup —
|
|
16
|
+
[how-to-host.md](how-to-host.md)); recipes and page content never touch `<html>`.
|
|
15
17
|
- Some components take a `dataColorSwitcher` prop *or* the `data-color-switcher`
|
|
16
18
|
attribute (e.g. `LabelBadge`, `BannerAnnouncement`, `Button`, `DynamicAngle*`).
|
|
17
19
|
The attribute wins when both are set; for buttons rendered into another
|
|
@@ -72,7 +74,8 @@ To choose:
|
|
|
72
74
|
|
|
73
75
|
- Do apply a palette on the region, and style children with role tokens.
|
|
74
76
|
- Do keep status palettes for real status, paired with text or an icon.
|
|
75
|
-
- Don't set `data-color-switcher` on `<html>` or
|
|
77
|
+
- Don't re-set `data-color-switcher` on `<html>` from page content or a recipe
|
|
78
|
+
(host setup only), and don't invent palette names.
|
|
76
79
|
- Don't hardcode hex/rgb, and don't stack multiple loud brand bands in one view.
|
|
77
80
|
|
|
78
81
|
## A2UI
|
|
@@ -10,35 +10,22 @@ Agents consume **published packages**, not repo filesystem paths. Do not import
|
|
|
10
10
|
|
|
11
11
|
## Load CSS
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
Load component and theme CSS as in
|
|
14
|
+
[getting-started.md](../uxelle-components/getting-started.md). Do not set
|
|
15
|
+
`font-family` in app CSS — `Text` uses theme tokens.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
import "@uxelle/
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Until `@uxelle/components/styles.css` is the full bundle, also load extracted
|
|
22
|
-
component CSS:
|
|
23
|
-
|
|
24
|
-
```ts
|
|
25
|
-
import "@uxelle/components/index.css";
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Enterprise hosts swap the theme module (`@uxelle/themes/<theme-id>`) and the root
|
|
29
|
-
data attribute. Do not set `font-family` in app CSS — `Text` uses theme tokens.
|
|
30
|
-
Load the typeface the theme expects (open-source: Open Sans).
|
|
17
|
+
If generated UI uses Material Symbol names beyond GPF chrome (`home`, `mail`,
|
|
18
|
+
`campaign`, …), add `import "@uxelle/icons/register"` once at the app entry.
|
|
19
|
+
`@uxelle/a2ui` already registers the full catalog.
|
|
31
20
|
|
|
32
21
|
On `<html>`:
|
|
33
22
|
|
|
34
23
|
- Theme mode (host-owned): `data-open-source="light" | "dark"` (other themes use
|
|
35
24
|
`data-<theme-id>`).
|
|
36
|
-
- Color switcher default: `data-color-switcher="default"` on `<html
|
|
25
|
+
- Color switcher default: `data-color-switcher="default"` on `<html>` — this is the
|
|
26
|
+
one place `<html>` carries a palette, and only the host sets it. Apply other
|
|
37
27
|
palettes per region, not here ([how-to-color.md](how-to-color.md)).
|
|
38
28
|
|
|
39
|
-
Optional: `UXelleThemeProvider` from `@uxelle/themes/react` with
|
|
40
|
-
`theme="open-source"` keeps those attributes in sync.
|
|
41
|
-
|
|
42
29
|
## Skip link
|
|
43
30
|
|
|
44
31
|
First focusable node in the DOM, targeting `id="main-content"` on `<main>`:
|
|
@@ -11,6 +11,24 @@ Page chrome comes from theme variables (do not invent `uxl-breakpoint-*` singula
|
|
|
11
11
|
|
|
12
12
|
Stacks inside the column use `--uxl-theme-layout-spacing-*` ([spacing-steps.md](spacing-steps.md)).
|
|
13
13
|
|
|
14
|
+
## Who owns the content column
|
|
15
|
+
|
|
16
|
+
**The experience owns it — chrome does not.** [recipe-app-chrome.md](recipe-app-chrome.md)
|
|
17
|
+
renders the skip link, `Navigation`, and a scrolling `<main>`, and stops there.
|
|
18
|
+
`<main>` is a scrollport with no padding and no cap. The experience recipe
|
|
19
|
+
(dashboard, data-table page, settings, page shell) renders the one capped column
|
|
20
|
+
inside it.
|
|
21
|
+
|
|
22
|
+
The column carries `maxWidth`, `mh="auto"`, `p`, and `gap`, and all four are
|
|
23
|
+
experience-level decisions: the `gap` is the density **Section** step
|
|
24
|
+
([density.md](density.md)) and the cap may be the `600px` reading measure instead of
|
|
25
|
+
the container token. Chrome cannot know either, which is why it does not emit them.
|
|
26
|
+
|
|
27
|
+
So exactly one element in the tree sets `maxWidth="var(--uxl-breakpoints-max-container-width)"`
|
|
28
|
+
plus `p="var(--uxl-breakpoints-margin)"`. If you find yourself writing that pair
|
|
29
|
+
twice — once from chrome and once from the experience — the page is capped and
|
|
30
|
+
padded twice; delete the chrome one.
|
|
31
|
+
|
|
14
32
|
## Content column
|
|
15
33
|
|
|
16
34
|
One capped, centered column carries the page:
|
|
@@ -27,9 +45,12 @@ One capped, centered column carries the page:
|
|
|
27
45
|
>
|
|
28
46
|
```
|
|
29
47
|
|
|
30
|
-
`mh="auto"` centers the column; `
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
`mh="auto"` centers the column; the `gap` is the **Section** step, shown here at its
|
|
49
|
+
`standard` value — a `compact` dashboard uses `medium-9` and a `spacious` landing
|
|
50
|
+
page `large-14` ([density.md](density.md)). Cap with the token, not a literal
|
|
51
|
+
(`1120px` / `1600px`), and keep page internals off the theme-internal
|
|
52
|
+
`--uxl-theme-layout-*-gutter` / `*-margin`. The cap, the margin, and the gutter are
|
|
53
|
+
density-invariant — only the `gap` moves.
|
|
33
54
|
|
|
34
55
|
If the host already renders `<main>` (the skip-link target), keep this column a
|
|
35
56
|
`div`; otherwise set `as="main"`.
|
|
@@ -38,6 +59,23 @@ If the host already renders `<main>` (the skip-link target), keep this column a
|
|
|
38
59
|
prose section inside a wider page) caps at the **reading measure** rather than the
|
|
39
60
|
container token — see [Reading measure](#reading-measure).
|
|
40
61
|
|
|
62
|
+
## Intrinsic sizes in rem
|
|
63
|
+
|
|
64
|
+
The no-px rule targets **breakpoints** and **theme values** — things the theme
|
|
65
|
+
already owns and remaps. It does not forbid an intrinsic size that expresses
|
|
66
|
+
"how small may this box get before it should reflow", because no theme variable
|
|
67
|
+
carries that. Use `rem` (never `px`) for:
|
|
68
|
+
|
|
69
|
+
- **`minmax()` minimums** on `auto-fit` grids — the smallest comfortable cell.
|
|
70
|
+
`16rem` for a text card, `18rem` for a media/CTA tile, `12rem`-`14rem` for a
|
|
71
|
+
metric tile.
|
|
72
|
+
- **A bounded search field** — `width="16rem"` with `flexShrink={0}`.
|
|
73
|
+
|
|
74
|
+
These are not breakpoints and must never be used as one: no media query, no
|
|
75
|
+
`matchMedia`, and no `maxWidth` on the page column. Anything the theme *does* own —
|
|
76
|
+
the column cap, page inset, gutter, spacing, type, color — always comes from a
|
|
77
|
+
variable ([tokens.md](tokens.md)).
|
|
78
|
+
|
|
41
79
|
## Reading measure
|
|
42
80
|
|
|
43
81
|
Text you read in a single column — a form, an FAQ, a paragraph block — is easiest
|
|
@@ -156,8 +194,9 @@ Bands: `tablet` (from `--uxl-theme-layout-tablet-screen-width-min`), `desktop`,
|
|
|
156
194
|
|
|
157
195
|
## Keep controls with what they operate
|
|
158
196
|
|
|
159
|
-
Group a control with the surface it drives;
|
|
160
|
-
*sections*, not between a control and its target
|
|
197
|
+
Group a control with the surface it drives; the **Section** step is the gap between
|
|
198
|
+
*sections*, not between a control and its target — a control sits with its target at
|
|
199
|
+
the **Control** step in every density mode ([density.md](density.md)).
|
|
161
200
|
|
|
162
201
|
- **Page navigation** (`Navigation` / `NavLink`) changes routes — don't repeat
|
|
163
202
|
those destinations as in-page tabs.
|
|
@@ -170,7 +209,7 @@ Group a control with the surface it drives; `medium-12` is the gap between
|
|
|
170
209
|
To stack when tight, switch the header `flexDirection` to `column` below tablet —
|
|
171
210
|
not `flexWrap="wrap"`.
|
|
172
211
|
- **Related form fields** (first/last name, city/state/ZIP): `display="grid"` with
|
|
173
|
-
`fr` tracks and
|
|
212
|
+
`fr` tracks and the **Control** gap; collapse to one column when narrow. See
|
|
174
213
|
[recipe-form-section.md](recipe-form-section.md).
|
|
175
214
|
|
|
176
215
|
## Action clusters
|
|
@@ -196,6 +235,12 @@ Delete/Upload, banner CTAs with two+ text buttons.
|
|
|
196
235
|
(Filter/Export beside search), icon-only clusters. Do not use the tablet switch to
|
|
197
236
|
restyle card grids — those reflow intrinsically ([recipe-card-grid.md](recipe-card-grid.md)).
|
|
198
237
|
|
|
238
|
+
**One button** needs no `direction` at all. `ButtonGroup` already defaults to
|
|
239
|
+
`direction="row"` / `fullWidth={false}`, and a single child renders identically in
|
|
240
|
+
either direction, so pass neither — a lone `direction="row"` reads as a deliberate
|
|
241
|
+
override of the switch above and invites a false review flag. Add a second text
|
|
242
|
+
button and the cluster takes the switch like any other.
|
|
243
|
+
|
|
199
244
|
## A2UI
|
|
200
245
|
|
|
201
246
|
Chat adapters allow only `var(--uxl-theme-layout-spacing-*)` (or `0`) — no
|
|
@@ -17,10 +17,13 @@ expressive, but within the *same* grid, hierarchy, and limited palette.
|
|
|
17
17
|
- **Restraint / minimalism.** Prefer fewer elements. Few type roles, few
|
|
18
18
|
accents, one dominant brand band per view. If a divider, box, or color is not
|
|
19
19
|
doing a job, remove it. Whitespace is the default separator.
|
|
20
|
-
- **Whitespace and rhythm.** Space is structural
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
- **Whitespace and rhythm.** Space is structural, and how much of it is *the*
|
|
21
|
+
expression of an experience's character. Use the ramp deliberately by **role** —
|
|
22
|
+
Tight stacks, Control clusters, Group padding, Section gaps, Band breathing —
|
|
23
|
+
and resolve those roles through one **density mode**: `compact` where data
|
|
24
|
+
volume is the point, `spacious` where a single idea must land. The grid stays
|
|
25
|
+
fixed while the rhythm changes. See [spacing-steps.md](spacing-steps.md) and
|
|
26
|
+
[density.md](density.md).
|
|
24
27
|
- **Clarity and legibility.** Objective, plain language. Render copy with `Text`
|
|
25
28
|
so type, line-height, and contrast come from the theme; never set font-size or
|
|
26
29
|
hex in app CSS.
|
|
@@ -63,10 +66,11 @@ The whole flow is assembled in [recipe-landing-page.md](recipe-landing-page.md).
|
|
|
63
66
|
| | Product | Marketing |
|
|
64
67
|
|---|---|---|
|
|
65
68
|
| Palette | `default` / `default-subtle` + status | + brand bands for emphasis |
|
|
66
|
-
| Density |
|
|
69
|
+
| Density mode | `compact` (data-dense) or `standard` (task) | `spacious` |
|
|
67
70
|
| Type | Restrained; Body-led | Expressive; Display-led headlines |
|
|
68
71
|
| Motion / accents | Minimal | `DynamicAngle*` accents, still restrained |
|
|
69
72
|
| Goal | Get the task done | Build trust and convert |
|
|
70
73
|
|
|
71
74
|
Same grid, same tokens, same components. The difference is emphasis, not a
|
|
72
|
-
different system
|
|
75
|
+
different system — density shifts which ramp step each role lands on, never the
|
|
76
|
+
column cap, page inset, or gutter ([density.md](density.md)).
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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`)
|
|
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={
|
|
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.
|
|
39
|
-
|
|
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`)
|
|
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`.
|
|
104
|
-
`
|
|
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
|
|
20
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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-
|
|
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-
|
|
76
|
+
gap="var(--uxl-theme-layout-spacing-medium-10)"
|
|
67
77
|
width="100%"
|
|
68
78
|
maxWidth="var(--uxl-breakpoints-max-container-width)"
|
|
69
79
|
>
|
|
@@ -32,18 +32,26 @@ sections
|
|
|
32
32
|
|
|
33
33
|
KPI and card grids reflow with `auto-fit` / `minmax` (no JS); the header stacks and
|
|
34
34
|
its actions go `fullWidth` below tablet; the primary `Table` scrolls horizontally
|
|
35
|
-
when narrow.
|
|
35
|
+
when narrow. StatTile labels ellipsize on one line so a KPI row stays scannable.
|
|
36
|
+
See [how-to-page-layout.md](how-to-page-layout.md#responsiveness).
|
|
36
37
|
|
|
37
38
|
## Spacing
|
|
38
39
|
|
|
39
|
-
`
|
|
40
|
-
|
|
40
|
+
**Density: `compact`** — a dashboard's job is to show more at a glance, so the
|
|
41
|
+
column tightens ([density.md](density.md)). **Section** gap between page sections
|
|
42
|
+
(header / KPIs / primary / supporting) is `medium-9`; subgroups inside a section
|
|
43
|
+
use **Group** (`small-6`); section intros use **Tight** (`micro-1`).
|
|
44
|
+
`--uxl-breakpoints-gutter` stays the grid gap in every mode
|
|
41
45
|
([spacing-steps.md](spacing-steps.md)).
|
|
42
46
|
|
|
47
|
+
Step up to `standard` when the overview is a light landing page with two or three
|
|
48
|
+
KPIs rather than a dense monitoring surface — but pick one mode and hold it.
|
|
49
|
+
|
|
43
50
|
## A11y
|
|
44
51
|
|
|
45
52
|
One `main`, one `h1`. Name each region (KPIs `aria-label="Key metrics"`, the primary
|
|
46
|
-
surface by its heading). KPI deltas pair color with a sign or word.
|
|
53
|
+
surface by its heading). KPI deltas pair color with a sign or word. Truncated KPI
|
|
54
|
+
labels keep the full name on the group; hover or focus reveals it. Loading and empty
|
|
47
55
|
for the primary surface follow [states](recipe-states.md). See
|
|
48
56
|
[how-to-accessibility.md](how-to-accessibility.md).
|
|
49
57
|
|
|
@@ -55,6 +63,7 @@ alerts, never as decoration ([how-to-color.md](how-to-color.md)).
|
|
|
55
63
|
## React
|
|
56
64
|
|
|
57
65
|
```tsx
|
|
66
|
+
// Density: compact (data-dense dashboard) — see density.md
|
|
58
67
|
<Layout
|
|
59
68
|
display="flex"
|
|
60
69
|
flexDirection="column"
|
|
@@ -62,7 +71,7 @@ alerts, never as decoration ([how-to-color.md](how-to-color.md)).
|
|
|
62
71
|
maxWidth="var(--uxl-breakpoints-max-container-width)"
|
|
63
72
|
mh="auto"
|
|
64
73
|
p="var(--uxl-breakpoints-margin)"
|
|
65
|
-
gap="var(--uxl-theme-layout-spacing-medium-
|
|
74
|
+
gap="var(--uxl-theme-layout-spacing-medium-9)"
|
|
66
75
|
>
|
|
67
76
|
{/* Page header — recipe-page-header.md */}
|
|
68
77
|
<DashboardHeader />
|
|
@@ -90,18 +99,23 @@ alerts, never as decoration ([how-to-color.md](how-to-color.md)).
|
|
|
90
99
|
<LabelBadge label="+4" dataColorSwitcher="success" emphasis="low" leadingIcon leadingIconName="trending_up" />
|
|
91
100
|
}
|
|
92
101
|
/>
|
|
102
|
+
{/*
|
|
103
|
+
Palette tracks whether the change is *good*, not whether the number rose.
|
|
104
|
+
Fewer open tickets is an improvement, so this delta is `success` while
|
|
105
|
+
trending down — say so in the label rather than relying on the arrow.
|
|
106
|
+
*/}
|
|
93
107
|
<StatTile
|
|
94
108
|
label="Open tickets"
|
|
95
109
|
value="18"
|
|
96
110
|
trailingSlot
|
|
97
111
|
trailingSlotContent={
|
|
98
|
-
<LabelBadge label="
|
|
112
|
+
<LabelBadge label="6 fewer" dataColorSwitcher="success" emphasis="low" leadingIcon leadingIconName="trending_down" />
|
|
99
113
|
}
|
|
100
114
|
/>
|
|
101
115
|
</Layout>
|
|
102
116
|
|
|
103
117
|
{/* Primary surface — recipe-data-table-page.md (with states) */}
|
|
104
|
-
<Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-
|
|
118
|
+
<Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-3)">
|
|
105
119
|
<Text type="Display Extra Small" as="h2" width={false}>Recent activity</Text>
|
|
106
120
|
{isLoading ? <ActivitySkeleton /> : rows.length ? <ActivityTable rows={rows} /> : <ActivityEmpty />}
|
|
107
121
|
</Layout>
|