@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.
- package/README.md +3 -5
- package/dist/index.js +98 -88
- package/index.json +98 -88
- 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/Image.md +10 -3
- package/skills/uxelle-components/Logo.md +1 -1
- package/skills/uxelle-components/MultiSelect.md +3 -2
- package/skills/uxelle-components/SKILL.md +3 -1
- package/skills/uxelle-components/Select.md +2 -1
- package/skills/uxelle-components/Table.md +7 -4
- package/skills/uxelle-components/getting-started.md +54 -0
- package/skills/uxelle-design-harness/SKILL.md +44 -20
- package/skills/uxelle-design-harness/a2ui.md +22 -2
- package/skills/uxelle-design-harness/density.md +138 -0
- package/skills/uxelle-design-harness/how-to-accessibility.md +2 -0
- package/skills/uxelle-design-harness/how-to-color.md +6 -3
- package/skills/uxelle-design-harness/how-to-host.md +5 -22
- 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 +17 -5
- 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 +8 -3
- 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,54 @@
|
|
|
1
|
+
<!-- Generated from README.md (getting-started region). Do not edit. -->
|
|
2
|
+
|
|
3
|
+
# Getting started
|
|
4
|
+
|
|
5
|
+
New app or host without uxElle packages and CSS? Read this file first, then set up page chrome in [how-to-host.md](../uxelle-design-harness/how-to-host.md). Component APIs live in [SKILL.md](SKILL.md). Optional A2UI runtime: [a2ui.md](../uxelle-design-harness/a2ui.md). Do not preload every component file.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
Peer dependencies: React 18 or 19.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @uxelle/components @uxelle/themes
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import "@uxelle/components/styles.css";
|
|
17
|
+
import "@uxelle/components/index.css";
|
|
18
|
+
import "@uxelle/themes/themes/open-source/open-source.css";
|
|
19
|
+
|
|
20
|
+
import { Button } from "@uxelle/components";
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
return <Button emphasis="high">Get Started</Button>;
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`styles.css` loads Material Symbols and the global baseline. `index.css` ships the bundled component rules. Load both stylesheets in the host app — the package JS does not import CSS automatically.
|
|
28
|
+
|
|
29
|
+
### Next.js / SSR
|
|
30
|
+
|
|
31
|
+
- Import the three stylesheets in `app/layout.tsx` (or a top-level provider). JS side effects will not style App Router.
|
|
32
|
+
- Published `@uxelle/components` JS is prefixed with `"use client"`. Importing any component places that file in the client graph.
|
|
33
|
+
- Pass `mobile={true}` or `mobile={false}` on `NavigationSide` so server and client markup match. Omit `mobile` only in client-only surfaces.
|
|
34
|
+
|
|
35
|
+
Optional: for an agent-driven UI surface, also install `@uxelle/a2ui`.
|
|
36
|
+
|
|
37
|
+
## Themes
|
|
38
|
+
|
|
39
|
+
Theme CSS is published as `@uxelle/themes` and generated from design tokens. Apply a theme by importing one CSS file. Set `data-open-source="light"` or `data-open-source="dark"` on `<html>`, plus `data-color-switcher="default"`. Optional: wrap the app with `UXelleThemeProvider` so those attributes stay in sync.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import "@uxelle/themes/themes/open-source/open-source.css";
|
|
43
|
+
import { UXelleThemeProvider } from "@uxelle/themes/react";
|
|
44
|
+
|
|
45
|
+
function AppShell({ children }: { children: React.ReactNode }) {
|
|
46
|
+
return (
|
|
47
|
+
<UXelleThemeProvider theme="open-source" darkMode={false} colorSwitcher="default">
|
|
48
|
+
{children}
|
|
49
|
+
</UXelleThemeProvider>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Open Source uses **Open Sans** (self-hosted in the theme package). Do not set `font-family` in app CSS — `Text` uses theme tokens.
|
|
@@ -37,29 +37,38 @@ palette. The full mapping of principles to uxElle mechanisms lives in
|
|
|
37
37
|
1. Load this `SKILL.md`. The foundations below always apply.
|
|
38
38
|
2. Read the variable foundation once: [tokens.md](tokens.md) and
|
|
39
39
|
[spacing-steps.md](spacing-steps.md).
|
|
40
|
-
3.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
4.
|
|
40
|
+
3. Pick the **density mode** for the experience — `compact` (data-dense product),
|
|
41
|
+
`standard` (task product, the default), `spacious` (marketing). One mode per
|
|
42
|
+
experience, chosen before any layout: [density.md](density.md).
|
|
43
|
+
4. Creating a **new** app/host? Install and load CSS from
|
|
44
|
+
[getting-started.md](../uxelle-components/getting-started.md), then the
|
|
45
|
+
shell in [how-to-host.md](how-to-host.md). Building a page body in an
|
|
46
|
+
existing host? [how-to-page-layout.md](how-to-page-layout.md) (layout
|
|
47
|
+
**and** responsiveness), [how-to-color.md](how-to-color.md), and
|
|
48
|
+
[how-to-accessibility.md](how-to-accessibility.md).
|
|
49
|
+
5. Prefer an existing component. Link its doc at point of use, e.g.
|
|
44
50
|
`Table` -> `../uxelle-components/Table.md`. Import from `@uxelle/components`.
|
|
45
|
-
|
|
51
|
+
6. Otherwise follow a recipe (see the index). If nothing fits, compose `Layout` +
|
|
46
52
|
`Text` + primitives on the grid.
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
7. Building a runtime surface? Read [a2ui.md](a2ui.md) and each recipe's **A2UI** note.
|
|
54
|
+
8. Before you finish, run the quality checklist below.
|
|
49
55
|
|
|
50
56
|
## Decision tree
|
|
51
57
|
|
|
52
|
-
- **New app/host** (
|
|
53
|
-
|
|
58
|
+
- **New app/host** (packages not installed, or CSS / `data-*` not loaded) ->
|
|
59
|
+
[getting-started.md](../uxelle-components/getting-started.md), then
|
|
60
|
+
[how-to-host.md](how-to-host.md) for skip link, `main`, and column chrome.
|
|
61
|
+
Composing inside an existing host -> skip both; do not set theme `data-*` on `<html>`.
|
|
54
62
|
- **Product experience** (signed-in app):
|
|
55
63
|
app frame -> [recipe-app-chrome.md](recipe-app-chrome.md);
|
|
56
|
-
landing/overview -> [recipe-dashboard-overview.md](recipe-dashboard-overview.md);
|
|
57
|
-
records list -> [recipe-data-table-page.md](recipe-data-table-page.md);
|
|
64
|
+
landing/overview -> [recipe-dashboard-overview.md](recipe-dashboard-overview.md) (`compact`);
|
|
65
|
+
records list -> [recipe-data-table-page.md](recipe-data-table-page.md) (`compact`);
|
|
58
66
|
one record -> [recipe-record-detail.md](recipe-record-detail.md);
|
|
59
67
|
settings -> [recipe-settings-page.md](recipe-settings-page.md);
|
|
60
68
|
wizard/checkout -> [recipe-multi-step-flow.md](recipe-multi-step-flow.md);
|
|
61
69
|
generic body -> [recipe-page-shell.md](recipe-page-shell.md).
|
|
62
|
-
|
|
70
|
+
Unmarked product recipes default to `standard` ([density.md](density.md)).
|
|
71
|
+
- **Marketing experience** (public, `spacious`): whole page -> [recipe-landing-page.md](recipe-landing-page.md),
|
|
63
72
|
composed from the marketing pieces below.
|
|
64
73
|
- **A piece** (part of an experience): page intro -> [recipe-page-header.md](recipe-page-header.md);
|
|
65
74
|
filter/search toolbar -> [recipe-query-bar.md](recipe-query-bar.md);
|
|
@@ -79,6 +88,11 @@ palette. The full mapping of principles to uxElle mechanisms lives in
|
|
|
79
88
|
padding / margin. Page chrome (inset, column cap, `fr` gaps) uses
|
|
80
89
|
`--uxl-breakpoints-margin` / `-max-container-width` / `-gutter`. See
|
|
81
90
|
[spacing-steps.md](spacing-steps.md) and [how-to-page-layout.md](how-to-page-layout.md).
|
|
91
|
+
- **Density**: choose the step by **role** (Flush / Tight / Control / Group /
|
|
92
|
+
Section / Band) and the heading type by **rank**, both resolved through one
|
|
93
|
+
**mode** for the whole experience — `compact` | `standard` | `spacious`. Page
|
|
94
|
+
chrome and component internals are density-invariant. Never mix modes or tighten
|
|
95
|
+
one role alone. See [density.md](density.md).
|
|
82
96
|
- **Color**: style via `--uxl-color-switcher-*` roles; apply a palette with
|
|
83
97
|
`data-color-switcher` on a **region** (never invent switcher names). See
|
|
84
98
|
[how-to-color.md](how-to-color.md).
|
|
@@ -86,10 +100,12 @@ palette. The full mapping of principles to uxElle mechanisms lives in
|
|
|
86
100
|
never use `--uxl-component-*` in app/recipe CSS (those belong inside components).
|
|
87
101
|
- **No hardcoded values**: no hex, no font-family, no breakpoint px. Read
|
|
88
102
|
breakpoint bounds from theme variables when JS is unavoidable
|
|
89
|
-
([how-to-page-layout.md](how-to-page-layout.md)).
|
|
90
|
-
content (a form/settings page, or a form / FAQ / prose section on a wider
|
|
91
|
-
may cap at the `maxWidth="600px"` **reading measure
|
|
92
|
-
`mh="auto"` ([how-to-page-layout.md](how-to-page-layout.md#reading-measure))
|
|
103
|
+
([how-to-page-layout.md](how-to-page-layout.md)). Two exceptions: one-column
|
|
104
|
+
reading content (a form/settings page, or a form / FAQ / prose section on a wider
|
|
105
|
+
page) may cap at the `maxWidth="600px"` **reading measure**, always centered with
|
|
106
|
+
`mh="auto"` ([how-to-page-layout.md](how-to-page-layout.md#reading-measure)); and
|
|
107
|
+
**intrinsic sizes** — `minmax()` mins and a bounded search field — use `rem`
|
|
108
|
+
([how-to-page-layout.md](how-to-page-layout.md#intrinsic-sizes-in-rem)).
|
|
93
109
|
- **Components first**: prefer a catalog component over bespoke markup; link its
|
|
94
110
|
doc at point of use (`../uxelle-components/<Name>.md`); do not open package source.
|
|
95
111
|
- **Recipes are patterns, not exports**: never add `PageShell` / `AppChrome` /
|
|
@@ -104,14 +120,21 @@ Every generated screen must pass this. It is the definition of done.
|
|
|
104
120
|
|
|
105
121
|
- **Grid & rhythm**: content sits on the grid; one capped content column via
|
|
106
122
|
`--uxl-breakpoints-max-container-width` / `-gutter` / `-margin`; section spacing
|
|
107
|
-
from the ramp
|
|
123
|
+
from the ramp at the experience's **Section** step. One-column reading sections
|
|
108
124
|
(form / FAQ / prose) share the `600px` reading measure and are centered
|
|
109
125
|
(`mh="auto"`); a decision cluster (form actions) is separated from its fields by
|
|
110
|
-
|
|
126
|
+
the **Section** step, not the field gap.
|
|
127
|
+
- **Density**: one mode declared for the surface and applied to every role; the gap
|
|
128
|
+
roles (Tight -> Control -> Group -> Section) stay ~1.5x apart; page chrome and
|
|
129
|
+
component internals unchanged; no breakpoint logic added for density
|
|
130
|
+
([density.md](density.md)).
|
|
111
131
|
- **Tokens only**: spacing from `--uxl-theme-layout-spacing-*`; color from
|
|
112
132
|
`--uxl-color-switcher-*`; no hardcoded hex or px, no invented `--uxl-*`, no
|
|
113
133
|
`--uxl-component-*` in app CSS, no `var(--x, fallback)`.
|
|
114
|
-
- **Hierarchy**: exactly one `h1`; heading ranks unbroken;
|
|
134
|
+
- **Hierarchy**: exactly one `h1`; heading ranks unbroken; each rank resolved to a
|
|
135
|
+
**distinct** `Text` type through the density mode — consecutive ranks never share
|
|
136
|
+
a type (an `h1` and `h2` both on `Display Extra Small` flatten the page), and
|
|
137
|
+
headings inside a `Card` step down one stop ([density.md](density.md)).
|
|
115
138
|
- **Responsive**: reflows mobile -> up with no px literals; clusters stack, `Table`
|
|
116
139
|
scrolls, nav collapses; touch targets stay >= 24 CSS px.
|
|
117
140
|
- **Color discipline**: at most one dominant brand band per view; status palettes
|
|
@@ -126,9 +149,10 @@ Every generated screen must pass this. It is the definition of done.
|
|
|
126
149
|
|
|
127
150
|
**Foundations** (always apply): [principles.md](principles.md) ·
|
|
128
151
|
[tokens.md](tokens.md) · [spacing-steps.md](spacing-steps.md) ·
|
|
152
|
+
[density.md](density.md) ·
|
|
129
153
|
[how-to-page-layout.md](how-to-page-layout.md) · [how-to-color.md](how-to-color.md) ·
|
|
130
154
|
[how-to-accessibility.md](how-to-accessibility.md) · [how-to-host.md](how-to-host.md) ·
|
|
131
|
-
[a2ui.md](a2ui.md)
|
|
155
|
+
[getting-started](../uxelle-components/getting-started.md) · [a2ui.md](a2ui.md)
|
|
132
156
|
|
|
133
157
|
**Experiences** — product: [app-chrome](recipe-app-chrome.md) ·
|
|
134
158
|
[page-shell](recipe-page-shell.md) · [dashboard-overview](recipe-dashboard-overview.md) ·
|
|
@@ -19,6 +19,10 @@ use the catalog schema the host provides.
|
|
|
19
19
|
- **No `className` / `style`.** Styling comes from the theme and adapter props.
|
|
20
20
|
- **Layout-spacing tokens only.** `A2uiLayout` accepts `0` or
|
|
21
21
|
`var(--uxl-theme-layout-spacing-*)`; never emit page-chrome breakpoint tokens.
|
|
22
|
+
- **`standard` or `compact` density only.** Chat surfaces are narrow and have no
|
|
23
|
+
full-bleed bands, so `spacious` never applies and the **Band** role has no
|
|
24
|
+
meaning. Use `standard` by default, `compact` for data-heavy runtime output
|
|
25
|
+
([density.md](density.md)).
|
|
22
26
|
- **No responsive JS.** There is no `matchMedia`. Default to a single, narrow
|
|
23
27
|
column (chat is mobile-like) and stack action clusters (`"direction": "column"`).
|
|
24
28
|
- **Host owns theme.** Never set theme `data-*` or a skip link from adapters; the
|
|
@@ -45,12 +49,27 @@ recipe JSON into the customer app.
|
|
|
45
49
|
Most catalog components map 1:1 to an `A2ui<Name>` adapter (e.g. `Button` ->
|
|
46
50
|
`A2uiButton`, `Textfield` -> `A2uiTextfield`, `Textarea` -> `A2uiTextarea`,
|
|
47
51
|
`Table` -> `A2uiTable`, `Sheet` -> `A2uiSheet`, `Hero` -> `A2uiHero`,
|
|
48
|
-
`Stepper` -> `A2uiStepper` + `A2uiStepperItem`,
|
|
52
|
+
`Image` -> `A2uiImage`, `Stepper` -> `A2uiStepper` + `A2uiStepperItem`,
|
|
53
|
+
`ListControls` -> `A2uiListControls`,
|
|
49
54
|
`Banner` -> `A2uiBanner`, `LabelBadge` -> `A2uiLabelBadge`, `StatTile` -> `A2uiStatTile`,
|
|
55
|
+
`ChoiceChip` -> `A2uiChoiceChip`, `ChoiceChipGroup` -> `A2uiChoiceChipGroup`,
|
|
56
|
+
`FilterChip` -> `A2uiFilterChip`, `FilterChipGroup` -> `A2uiFilterChipGroup`,
|
|
50
57
|
`DynamicAngle*` -> `A2uiDynamicAngle*`). `Table` has the full family (`A2uiTableGrid`,
|
|
51
58
|
`A2uiTableHead`, `A2uiTableBody`, `A2uiTableRow`, `A2uiTableCell`,
|
|
52
59
|
`A2uiTableHeaderCell`, `A2uiTableColumnSizingProvider`).
|
|
53
60
|
|
|
61
|
+
`A2uiImage` box size (`width`, `height`, `minWidth`, `maxWidth`, `minHeight`,
|
|
62
|
+
`maxHeight`) is `number|string` — numbers are pixels, strings pass through as CSS
|
|
63
|
+
(including `var(--uxl-…)`). `aspectRatio` crops and computes the missing axis.
|
|
64
|
+
Omit size and `aspectRatio` on an `A2uiImage` nested in `A2uiHero`.
|
|
65
|
+
|
|
66
|
+
`A2uiChoiceChip` / `A2uiFilterChip` take a `label` string. Do not nest `A2uiText`
|
|
67
|
+
inside a chip. Choice-chip labels use Condensed when unchecked and Condensed Alt
|
|
68
|
+
when checked; filter-chip labels use Condensed. `A2uiFilterChipGroup` wraps whole
|
|
69
|
+
chips; a chip wider than its container ellipsizes so dismiss stays visible.
|
|
70
|
+
Exclusive `A2uiChoiceChip` selection is one checked value in host state (toggles,
|
|
71
|
+
not radios). Use `A2uiRadioGroup` or `A2uiSegmentedControl` for radio semantics.
|
|
72
|
+
|
|
54
73
|
Known gaps — substitute rather than invent:
|
|
55
74
|
|
|
56
75
|
| React | A2UI |
|
|
@@ -66,7 +85,8 @@ If a design needs a gap component as its backbone, say so in the recipe's
|
|
|
66
85
|
|
|
67
86
|
## Translating a React recipe
|
|
68
87
|
|
|
69
|
-
1. Keep the same region order
|
|
88
|
+
1. Keep the same region order. Keep the layout-spacing values too, unless the React
|
|
89
|
+
surface was `spacious` — then re-resolve its roles at `standard`.
|
|
70
90
|
2. Swap each component for its adapter; expand `Lockup` to stacked `A2uiText`.
|
|
71
91
|
3. Replace `useBreakpointUp` switches with a single narrow column.
|
|
72
92
|
4. Replace React state with host bindings.
|
|
@@ -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"`).
|
|
@@ -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,18 @@ Agents consume **published packages**, not repo filesystem paths. Do not import
|
|
|
10
10
|
|
|
11
11
|
## Load CSS
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```ts
|
|
17
|
-
import "@uxelle/themes/open-source";
|
|
18
|
-
import "@uxelle/components/styles.css";
|
|
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).
|
|
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.
|
|
31
16
|
|
|
32
17
|
On `<html>`:
|
|
33
18
|
|
|
34
19
|
- Theme mode (host-owned): `data-open-source="light" | "dark"` (other themes use
|
|
35
20
|
`data-<theme-id>`).
|
|
36
|
-
- Color switcher default: `data-color-switcher="default"` on `<html
|
|
21
|
+
- Color switcher default: `data-color-switcher="default"` on `<html>` — this is the
|
|
22
|
+
one place `<html>` carries a palette, and only the host sets it. Apply other
|
|
37
23
|
palettes per region, not here ([how-to-color.md](how-to-color.md)).
|
|
38
24
|
|
|
39
|
-
Optional: `UXelleThemeProvider` from `@uxelle/themes/react` with
|
|
40
|
-
`theme="open-source"` keeps those attributes in sync.
|
|
41
|
-
|
|
42
25
|
## Skip link
|
|
43
26
|
|
|
44
27
|
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)).
|