@uxelle/skills 0.2.0-beta.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/LICENSE +674 -0
- package/README.md +58 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +586 -0
- package/index.json +573 -0
- package/package.json +57 -0
- package/skills/uxelle-components/Accordion.md +51 -0
- package/skills/uxelle-components/AccordionGroup.md +39 -0
- package/skills/uxelle-components/Banner.md +73 -0
- package/skills/uxelle-components/BannerAnnouncement.md +63 -0
- package/skills/uxelle-components/Breadcrumb.md +38 -0
- package/skills/uxelle-components/Breadcrumbs.md +32 -0
- package/skills/uxelle-components/Bubble.md +34 -0
- package/skills/uxelle-components/Button.md +54 -0
- package/skills/uxelle-components/ButtonGroup.md +43 -0
- package/skills/uxelle-components/Card.md +35 -0
- package/skills/uxelle-components/Checkbox.md +57 -0
- package/skills/uxelle-components/CheckboxGroup.md +47 -0
- package/skills/uxelle-components/ChoiceChip.md +44 -0
- package/skills/uxelle-components/ChoiceChipGroup.md +50 -0
- package/skills/uxelle-components/DatePicker.md +59 -0
- package/skills/uxelle-components/DatePickerRange.md +73 -0
- package/skills/uxelle-components/Dialog.md +83 -0
- package/skills/uxelle-components/Divider.md +37 -0
- package/skills/uxelle-components/DynamicAngleLine.md +36 -0
- package/skills/uxelle-components/DynamicAngleLineCombo.md +30 -0
- package/skills/uxelle-components/DynamicAngleShape.md +32 -0
- package/skills/uxelle-components/DynamicAngleShapeSurface.md +35 -0
- package/skills/uxelle-components/DynamicAngleTexture.md +29 -0
- package/skills/uxelle-components/DynamicAngleTextureText.md +35 -0
- package/skills/uxelle-components/FieldMessage.md +31 -0
- package/skills/uxelle-components/FileUpload.md +53 -0
- package/skills/uxelle-components/FilterChip.md +35 -0
- package/skills/uxelle-components/FilterChipGroup.md +42 -0
- package/skills/uxelle-components/Footer.md +34 -0
- package/skills/uxelle-components/Hero.md +61 -0
- package/skills/uxelle-components/Icon.md +43 -0
- package/skills/uxelle-components/IconButton.md +44 -0
- package/skills/uxelle-components/Image.md +40 -0
- package/skills/uxelle-components/Label.md +35 -0
- package/skills/uxelle-components/LabelBadge.md +58 -0
- package/skills/uxelle-components/LanguageSelector.md +38 -0
- package/skills/uxelle-components/LanguageSelectorButton.md +34 -0
- package/skills/uxelle-components/Layout.md +81 -0
- package/skills/uxelle-components/LinearProgress.md +53 -0
- package/skills/uxelle-components/Link.md +52 -0
- package/skills/uxelle-components/List.md +35 -0
- package/skills/uxelle-components/ListControls.md +100 -0
- package/skills/uxelle-components/ListItem.md +69 -0
- package/skills/uxelle-components/Lockup.md +50 -0
- package/skills/uxelle-components/Logo.md +39 -0
- package/skills/uxelle-components/Menu.md +48 -0
- package/skills/uxelle-components/MultiSelect.md +54 -0
- package/skills/uxelle-components/NavLink.md +44 -0
- package/skills/uxelle-components/Navigation.md +61 -0
- package/skills/uxelle-components/NavigationSide.md +51 -0
- package/skills/uxelle-components/NavigationSideGroup.md +36 -0
- package/skills/uxelle-components/NavigationSideItem.md +56 -0
- package/skills/uxelle-components/NavigationSideSubItem.md +39 -0
- package/skills/uxelle-components/NotificationBadge.md +62 -0
- package/skills/uxelle-components/Pagination.md +49 -0
- package/skills/uxelle-components/PaginationComplex.md +51 -0
- package/skills/uxelle-components/PaginationSimple.md +44 -0
- package/skills/uxelle-components/ProductCard.md +46 -0
- package/skills/uxelle-components/Quantity.md +45 -0
- package/skills/uxelle-components/Radio.md +55 -0
- package/skills/uxelle-components/RadioGroup.md +49 -0
- package/skills/uxelle-components/SKILL.md +242 -0
- package/skills/uxelle-components/Scrim.md +28 -0
- package/skills/uxelle-components/SegmentedControl.md +118 -0
- package/skills/uxelle-components/Select.md +55 -0
- package/skills/uxelle-components/Sheet.md +31 -0
- package/skills/uxelle-components/Skeleton.md +35 -0
- package/skills/uxelle-components/Spinner.md +33 -0
- package/skills/uxelle-components/StatTile.md +150 -0
- package/skills/uxelle-components/Stepper.md +73 -0
- package/skills/uxelle-components/Switch.md +41 -0
- package/skills/uxelle-components/Tab.md +41 -0
- package/skills/uxelle-components/Table.md +55 -0
- package/skills/uxelle-components/TabsCompact.md +37 -0
- package/skills/uxelle-components/TabsStretch.md +35 -0
- package/skills/uxelle-components/Text.md +56 -0
- package/skills/uxelle-components/Textarea.md +77 -0
- package/skills/uxelle-components/Textfield.md +62 -0
- package/skills/uxelle-components/Tooltip.md +33 -0
- package/skills/uxelle-design-harness/SKILL.md +149 -0
- package/skills/uxelle-design-harness/a2ui.md +73 -0
- package/skills/uxelle-design-harness/how-to-accessibility.md +31 -0
- package/skills/uxelle-design-harness/how-to-color.md +82 -0
- package/skills/uxelle-design-harness/how-to-host.md +108 -0
- package/skills/uxelle-design-harness/how-to-page-layout.md +203 -0
- package/skills/uxelle-design-harness/principles.md +72 -0
- package/skills/uxelle-design-harness/recipe-app-chrome.md +168 -0
- package/skills/uxelle-design-harness/recipe-card-grid.md +104 -0
- package/skills/uxelle-design-harness/recipe-cta-band.md +91 -0
- package/skills/uxelle-design-harness/recipe-dashboard-overview.md +118 -0
- package/skills/uxelle-design-harness/recipe-data-table-page.md +323 -0
- package/skills/uxelle-design-harness/recipe-feature-section.md +96 -0
- package/skills/uxelle-design-harness/recipe-footer.md +156 -0
- package/skills/uxelle-design-harness/recipe-form-section.md +163 -0
- package/skills/uxelle-design-harness/recipe-hero.md +89 -0
- package/skills/uxelle-design-harness/recipe-landing-page.md +94 -0
- package/skills/uxelle-design-harness/recipe-logo-wall.md +80 -0
- package/skills/uxelle-design-harness/recipe-multi-step-flow.md +108 -0
- package/skills/uxelle-design-harness/recipe-page-header.md +102 -0
- package/skills/uxelle-design-harness/recipe-page-shell.md +82 -0
- package/skills/uxelle-design-harness/recipe-pricing.md +83 -0
- package/skills/uxelle-design-harness/recipe-query-bar.md +108 -0
- package/skills/uxelle-design-harness/recipe-record-detail.md +96 -0
- package/skills/uxelle-design-harness/recipe-settings-page.md +170 -0
- package/skills/uxelle-design-harness/recipe-stat-callouts.md +111 -0
- package/skills/uxelle-design-harness/recipe-states.md +119 -0
- package/skills/uxelle-design-harness/recipe-summary-list.md +88 -0
- package/skills/uxelle-design-harness/recipe-template.md +62 -0
- package/skills/uxelle-design-harness/recipe-testimonial.md +73 -0
- package/skills/uxelle-design-harness/spacing-steps.md +57 -0
- package/skills/uxelle-design-harness/tokens.md +101 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# How to: page layout and responsiveness
|
|
2
|
+
|
|
3
|
+
The home for laying out a React **page body** and making it responsive. (A2UI
|
|
4
|
+
chat surfaces are inherently narrow — see [a2ui.md](a2ui.md).)
|
|
5
|
+
|
|
6
|
+
Page chrome comes from theme variables (do not invent `uxl-breakpoint-*` singular):
|
|
7
|
+
|
|
8
|
+
- `--uxl-breakpoints-max-container-width` — cap the body column so large viewports keep side space
|
|
9
|
+
- `--uxl-breakpoints-margin` — padding **around** the body
|
|
10
|
+
- `--uxl-breakpoints-gutter` — gap **between** columns (`fr` grids, card rows)
|
|
11
|
+
|
|
12
|
+
Stacks inside the column use `--uxl-theme-layout-spacing-*` ([spacing-steps.md](spacing-steps.md)).
|
|
13
|
+
|
|
14
|
+
## Content column
|
|
15
|
+
|
|
16
|
+
One capped, centered column carries the page:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<Layout
|
|
20
|
+
display="flex"
|
|
21
|
+
flexDirection="column"
|
|
22
|
+
width="100%"
|
|
23
|
+
maxWidth="var(--uxl-breakpoints-max-container-width)"
|
|
24
|
+
mh="auto"
|
|
25
|
+
p="var(--uxl-breakpoints-margin)"
|
|
26
|
+
gap="var(--uxl-theme-layout-spacing-medium-12)"
|
|
27
|
+
>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`mh="auto"` centers the column; `medium-12` is the gap between page sections. Cap
|
|
31
|
+
with the token, not a literal (`1120px` / `1600px`), and keep page internals off
|
|
32
|
+
the theme-internal `--uxl-theme-layout-*-gutter` / `*-margin`.
|
|
33
|
+
|
|
34
|
+
If the host already renders `<main>` (the skip-link target), keep this column a
|
|
35
|
+
`div`; otherwise set `as="main"`.
|
|
36
|
+
|
|
37
|
+
**One-column reading content** (settings, a form-only view, or a form / FAQ /
|
|
38
|
+
prose section inside a wider page) caps at the **reading measure** rather than the
|
|
39
|
+
container token — see [Reading measure](#reading-measure).
|
|
40
|
+
|
|
41
|
+
## Reading measure
|
|
42
|
+
|
|
43
|
+
Text you read in a single column — a form, an FAQ, a paragraph block — is easiest
|
|
44
|
+
to scan at roughly `maxWidth="600px"`. Treat that as one shared measure, applied
|
|
45
|
+
the same way everywhere it appears:
|
|
46
|
+
|
|
47
|
+
- **Cap the whole section, not the inner control.** Put `width="100%"`,
|
|
48
|
+
`maxWidth="600px"`, `mh="auto"` on the section wrapper so its heading, body,
|
|
49
|
+
fields, or list share one left edge and one measure. Capping only the form (or
|
|
50
|
+
only the accordion) leaves the heading full-width and the block stranded left of
|
|
51
|
+
center.
|
|
52
|
+
- **Center it.** A block capped narrower than its column must set `mh="auto"`, or
|
|
53
|
+
it reads as left-aligned and unbalanced against the wider content around it.
|
|
54
|
+
- **Keep the measure consistent across siblings.** When a page stacks reading
|
|
55
|
+
sections — e.g. an FAQ above a demo form — give them the *same* `600px` measure
|
|
56
|
+
and centering so they line up as one column instead of each finding its own
|
|
57
|
+
width.
|
|
58
|
+
- **A whole one-column page** (account settings, a form-only view) caps its page
|
|
59
|
+
column at `600px` the same way — keep the margin and centering
|
|
60
|
+
([recipe-settings-page.md](recipe-settings-page.md)).
|
|
61
|
+
|
|
62
|
+
Do not apply `600px` to tables, card grids, or mixed / multi-column layouts —
|
|
63
|
+
those keep `--uxl-breakpoints-max-container-width`.
|
|
64
|
+
|
|
65
|
+
## Columns and cards
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
<Layout display="grid" gridTemplateColumns="1fr 1fr" gap="var(--uxl-breakpoints-gutter)">
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Put gutter on column gaps, margin on the outer padding — not the other way round.
|
|
72
|
+
|
|
73
|
+
## Responsiveness
|
|
74
|
+
|
|
75
|
+
Design mobile-first and let layouts reflow up. Reach for methods in this order.
|
|
76
|
+
|
|
77
|
+
### 1. Intrinsic reflow (primary method)
|
|
78
|
+
|
|
79
|
+
Prefer fluid CSS that adapts with no breakpoint values and no JS:
|
|
80
|
+
|
|
81
|
+
- **Grids that reflow by width** — `repeat(auto-fit, minmax(MIN, 1fr))` so the
|
|
82
|
+
column count follows available space:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
<Layout
|
|
86
|
+
display="grid"
|
|
87
|
+
gridTemplateColumns="repeat(auto-fit, minmax(16rem, 1fr))"
|
|
88
|
+
gap="var(--uxl-breakpoints-gutter)"
|
|
89
|
+
>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- **Clusters that wrap** — `flexWrap="wrap"` for chip rows and toolbars that may
|
|
93
|
+
need a second line.
|
|
94
|
+
- **Fluid measures** — `minmax()` / `clamp()` for column widths.
|
|
95
|
+
- **The chrome tokens** already remap per breakpoint, so the capped column,
|
|
96
|
+
margin, and gutter tighten on small screens for free.
|
|
97
|
+
|
|
98
|
+
This covers page columns, card/metric/feature grids, and most cluster wrapping.
|
|
99
|
+
|
|
100
|
+
### 2. Component-native responsiveness
|
|
101
|
+
|
|
102
|
+
Prefer components that adapt themselves (`Navigation`, `Sheet`, `Hero`
|
|
103
|
+
`contentDirection`) over app-level logic. Confirm a component's built-in behavior
|
|
104
|
+
in its doc before adding anything.
|
|
105
|
+
|
|
106
|
+
### 3. Token-driven JS (narrow fallback)
|
|
107
|
+
|
|
108
|
+
CSS media queries cannot read `var()`, so when a switch is bound to a component
|
|
109
|
+
**prop** and cannot be done with flow — e.g. `ButtonGroup` `direction` row<->column,
|
|
110
|
+
or swapping nav for a drawer — read the theme breakpoint bound and track it. Never
|
|
111
|
+
write the px literal:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { useEffect, useState } from "react";
|
|
115
|
+
|
|
116
|
+
// Tracks a theme breakpoint bound. The px comes from the theme, so it stays
|
|
117
|
+
// correct across platforms/themes — do not hardcode it.
|
|
118
|
+
function useBreakpointUp(
|
|
119
|
+
band: "tablet" | "desktop" | "desktop-plus" = "tablet",
|
|
120
|
+
): boolean {
|
|
121
|
+
const [up, setUp] = useState(true); // desktop-first default avoids a mobile flash on hydrate
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (typeof window === "undefined") return;
|
|
124
|
+
const min = getComputedStyle(document.documentElement)
|
|
125
|
+
.getPropertyValue(`--uxl-theme-layout-${band}-screen-width-min`)
|
|
126
|
+
.trim();
|
|
127
|
+
if (!min) return;
|
|
128
|
+
const media = window.matchMedia(`(min-width: ${min})`);
|
|
129
|
+
const sync = () => setUp(media.matches);
|
|
130
|
+
sync();
|
|
131
|
+
media.addEventListener("change", sync);
|
|
132
|
+
return () => media.removeEventListener("change", sync);
|
|
133
|
+
}, [band]);
|
|
134
|
+
return up;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const tabletUp = useBreakpointUp("tablet");
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Bands: `tablet` (from `--uxl-theme-layout-tablet-screen-width-min`), `desktop`,
|
|
141
|
+
`desktop-plus`. Use this only where intrinsic reflow cannot express the change.
|
|
142
|
+
|
|
143
|
+
### Reflow patterns at a glance
|
|
144
|
+
|
|
145
|
+
- **Grids**: 1 -> 2 -> 3(-4)-up via `auto-fit` / `minmax`; use the JS helper only
|
|
146
|
+
when a component needs an explicit count.
|
|
147
|
+
- **Header / query clusters**: title over actions, search full-width with
|
|
148
|
+
filters/export below, when narrow; one row when wide (see below).
|
|
149
|
+
- **Navigation**: collapse to an `IconButton` menu opening a `Sheet` / `Menu`
|
|
150
|
+
drawer when narrow; verify `Navigation`'s built-in behavior first.
|
|
151
|
+
- **Tables**: `Table` scrolls horizontally when narrow (no stacked-row API); use
|
|
152
|
+
`PaginationSimple` where full `Pagination` is too wide.
|
|
153
|
+
- **Hero / media**: `Hero` `contentDirection` handles narrow stacking; related-field
|
|
154
|
+
grids and summary lists collapse to one column.
|
|
155
|
+
- **Touch targets**: catalog controls meet 24x24 CSS px — do not shrink hit areas.
|
|
156
|
+
|
|
157
|
+
## Keep controls with what they operate
|
|
158
|
+
|
|
159
|
+
Group a control with the surface it drives; `medium-12` is the gap between
|
|
160
|
+
*sections*, not between a control and its target.
|
|
161
|
+
|
|
162
|
+
- **Page navigation** (`Navigation` / `NavLink`) changes routes — don't repeat
|
|
163
|
+
those destinations as in-page tabs.
|
|
164
|
+
- **Data navigation** (tabs that swap a table's rows) sits on that table, in
|
|
165
|
+
`Table` `topSlot` first — not under the page title. See [recipe-data-table-page.md](recipe-data-table-page.md).
|
|
166
|
+
- **One-row clusters** (title + page actions, search + export): keep
|
|
167
|
+
`flexWrap="nowrap"` (Layout default). `Lockup` and `Textfield` are `width: 100%`,
|
|
168
|
+
so let the `Lockup` be the flex sibling that shrinks and put `flexShrink={0}` on
|
|
169
|
+
the action cluster. Bound a search field with `width="16rem"` `flexShrink={0}`.
|
|
170
|
+
To stack when tight, switch the header `flexDirection` to `column` below tablet —
|
|
171
|
+
not `flexWrap="wrap"`.
|
|
172
|
+
- **Related form fields** (first/last name, city/state/ZIP): `display="grid"` with
|
|
173
|
+
`fr` tracks and `small-4` gap; collapse to one column when narrow. See
|
|
174
|
+
[recipe-form-section.md](recipe-form-section.md).
|
|
175
|
+
|
|
176
|
+
## Action clusters
|
|
177
|
+
|
|
178
|
+
Two or more text `Button`s in a `ButtonGroup` sit in a **row from tablet up** and
|
|
179
|
+
a **column below tablet**, using the `direction` prop (with `fullWidth` when
|
|
180
|
+
stacked). Do not add app CSS `flex-direction` or invented breakpoints.
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
const tabletUp = useBreakpointUp("tablet");
|
|
184
|
+
|
|
185
|
+
<ButtonGroup
|
|
186
|
+
direction={tabletUp ? "row" : "column"}
|
|
187
|
+
fullWidth={!tabletUp}
|
|
188
|
+
aria-label="Form actions"
|
|
189
|
+
>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Switch** (row/column): form Save/Cancel, dialog confirm/cancel, page
|
|
193
|
+
Delete/Upload, banner CTAs with two+ text buttons.
|
|
194
|
+
|
|
195
|
+
**Stay row** at every width: `Navigation` trailing, a table query bar
|
|
196
|
+
(Filter/Export beside search), icon-only clusters. Do not use the tablet switch to
|
|
197
|
+
restyle card grids — those reflow intrinsically ([recipe-card-grid.md](recipe-card-grid.md)).
|
|
198
|
+
|
|
199
|
+
## A2UI
|
|
200
|
+
|
|
201
|
+
Chat adapters allow only `var(--uxl-theme-layout-spacing-*)` (or `0`) — no
|
|
202
|
+
breakpoint chrome tokens and no `matchMedia`. Default to a single column; for
|
|
203
|
+
action clusters use `"direction": "column"`. See [a2ui.md](a2ui.md).
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Design principles
|
|
2
|
+
|
|
3
|
+
International Typographic (Swiss) principles, each mapped to a concrete uxElle
|
|
4
|
+
mechanism. These apply to every experience. They are **context-aware**: product
|
|
5
|
+
surfaces are maximally calm and dense-but-legible; marketing surfaces are
|
|
6
|
+
expressive, but within the *same* grid, hierarchy, and limited palette.
|
|
7
|
+
|
|
8
|
+
## Principles -> mechanism
|
|
9
|
+
|
|
10
|
+
- **Grid and alignment.** Lay content on one grid: a capped, centered column
|
|
11
|
+
(`--uxl-breakpoints-max-container-width`, `mh="auto"`, `--uxl-breakpoints-margin`)
|
|
12
|
+
and `fr` tracks with `--uxl-breakpoints-gutter`. Everything aligns to it.
|
|
13
|
+
See [how-to-page-layout.md](how-to-page-layout.md).
|
|
14
|
+
- **Hierarchy.** Lead with one clear focal point per view. Exactly one `h1`,
|
|
15
|
+
unbroken heading ranks, and a small set of `Text` type roles (Display for
|
|
16
|
+
headlines, Body for copy). Let size and weight — not color — carry rank.
|
|
17
|
+
- **Restraint / minimalism.** Prefer fewer elements. Few type roles, few
|
|
18
|
+
accents, one dominant brand band per view. If a divider, box, or color is not
|
|
19
|
+
doing a job, remove it. Whitespace is the default separator.
|
|
20
|
+
- **Whitespace and rhythm.** Space is structural. Use the ramp deliberately:
|
|
21
|
+
tight stacks `micro`, related controls `small-4`, card/section padding
|
|
22
|
+
`medium-8`, page-section gaps `medium-12`, hero breathing `large-13`+.
|
|
23
|
+
See [spacing-steps.md](spacing-steps.md).
|
|
24
|
+
- **Clarity and legibility.** Objective, plain language. Render copy with `Text`
|
|
25
|
+
so type, line-height, and contrast come from the theme; never set font-size or
|
|
26
|
+
hex in app CSS.
|
|
27
|
+
- **Consistency.** Compose catalog components and `--uxl-*` tokens, not bespoke
|
|
28
|
+
markup. The same job looks the same everywhere.
|
|
29
|
+
- **Feedback and state.** Design the empty, loading, and error states, not only
|
|
30
|
+
the happy path. Use `Banner` / status palettes for messages and
|
|
31
|
+
`Skeleton` / `Spinner` for pending work. See [recipe-states.md](recipe-states.md).
|
|
32
|
+
- **Responsiveness.** Mobile-first. Build within the breakpoint system with
|
|
33
|
+
intrinsic CSS (flex-wrap, `minmax`, `auto-fit`) so layouts reflow without px
|
|
34
|
+
literals. See [how-to-page-layout.md](how-to-page-layout.md).
|
|
35
|
+
- **Color discipline.** Product surfaces stay on `default` / `default-subtle`
|
|
36
|
+
plus semantic status; marketing surfaces add brand bands for emphasis. Never
|
|
37
|
+
hardcode color; status is never signaled by color alone. See
|
|
38
|
+
[how-to-color.md](how-to-color.md).
|
|
39
|
+
- **Objectivity, content-first.** Let real content lead. On marketing surfaces
|
|
40
|
+
that means concrete proof — metrics, customer/integration logos, testimonials —
|
|
41
|
+
over decoration.
|
|
42
|
+
|
|
43
|
+
## Marketing composition (distilled)
|
|
44
|
+
|
|
45
|
+
A dependable rhythm for a public page, each section mapped to a piece. Keep one
|
|
46
|
+
brand accent, generous section spacing, and a strong grid throughout.
|
|
47
|
+
|
|
48
|
+
1. **Hero** — value proposition + primary/secondary action + product visual
|
|
49
|
+
([recipe-hero.md](recipe-hero.md)).
|
|
50
|
+
2. **Trust bar** — customer or integration logos ([recipe-logo-wall.md](recipe-logo-wall.md)).
|
|
51
|
+
3. **Feature sections** — alternating media + copy, optionally tab-switched
|
|
52
|
+
([recipe-feature-section.md](recipe-feature-section.md)).
|
|
53
|
+
4. **Stat tiles** — a few large figures with labels ([recipe-stat-callouts.md](recipe-stat-callouts.md)).
|
|
54
|
+
5. **Testimonials** — quote + attribution ([recipe-testimonial.md](recipe-testimonial.md)).
|
|
55
|
+
6. **Pricing** — comparable tiers with one recommended ([recipe-pricing.md](recipe-pricing.md)).
|
|
56
|
+
7. **Closing CTA** — a single decisive action ([recipe-cta-band.md](recipe-cta-band.md)).
|
|
57
|
+
8. **Footer** — categorized links and legal ([recipe-footer.md](recipe-footer.md)).
|
|
58
|
+
|
|
59
|
+
The whole flow is assembled in [recipe-landing-page.md](recipe-landing-page.md).
|
|
60
|
+
|
|
61
|
+
## Product vs marketing
|
|
62
|
+
|
|
63
|
+
| | Product | Marketing |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| Palette | `default` / `default-subtle` + status | + brand bands for emphasis |
|
|
66
|
+
| Density | Compact, information-dense | Airy, one idea per band |
|
|
67
|
+
| Type | Restrained; Body-led | Expressive; Display-led headlines |
|
|
68
|
+
| Motion / accents | Minimal | `DynamicAngle*` accents, still restrained |
|
|
69
|
+
| Goal | Get the task done | Build trust and convert |
|
|
70
|
+
|
|
71
|
+
Same grid, same tokens, same components. The difference is emphasis, not a
|
|
72
|
+
different system.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Recipe: app chrome
|
|
2
|
+
|
|
3
|
+
## When
|
|
4
|
+
|
|
5
|
+
React app frame: skip link, [`Navigation`](../uxelle-components/Navigation.md), scrolling `<main>`, optional [`Footer`](../uxelle-components/Footer.md). Serves both product and marketing shells.
|
|
6
|
+
|
|
7
|
+
## When not
|
|
8
|
+
|
|
9
|
+
- Not an `AppChrome` / `PageShell` component.
|
|
10
|
+
- Theme CSS and `data-*`: [how-to-host.md](how-to-host.md).
|
|
11
|
+
- 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
|
+
|
|
13
|
+
## Regions
|
|
14
|
+
|
|
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>`**
|
|
16
|
+
|
|
17
|
+
## Spacing
|
|
18
|
+
|
|
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.
|
|
20
|
+
|
|
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`).
|
|
22
|
+
|
|
23
|
+
## A11y
|
|
24
|
+
|
|
25
|
+
[how-to-accessibility.md](how-to-accessibility.md) and skip-link CSS in [how-to-host.md](how-to-host.md). One `main`. `aria-current="page"` on the current `NavLink` or `NavigationSideItem` / `NavigationSideSubItem` (`activated`). Decorative `Logo` / `Icon` beside a visible product name: `aria-hidden`.
|
|
26
|
+
|
|
27
|
+
`NavLink` in `Navigation` is **header** page navigation. `NavigationSide` is the side-rail **chrome** (`<aside>`, or a `<header>` when `mobile`). `NavigationSideGroup` is the **side** navigation landmark for the experience (a named `<nav>` of destination rows) and belongs in `NavigationSide` `centerSlotContent`. Do not wrap `NavigationSide` or `NavigationSideGroup` in another `nav`. Pass `aria-label` when more than one group is on the page. Accordion parents are not destinations — put section landing pages on `NavigationSideSubItem` children. Tabs that only change which rows appear in a table are **data** navigation — put them on the table ([recipe-data-table-page.md](recipe-data-table-page.md)), not in `centerSlot` and not as a second nav under the page title.
|
|
28
|
+
|
|
29
|
+
## Side rail
|
|
30
|
+
|
|
31
|
+
Compose `NavigationSide` with `centerSlotContent` of `NavigationSideGroup` + `NavigationSideItem` (optional `accordion` with `NavigationSideSubItem` children). Set `expanded={false}` on `NavigationSide` for the icon-only rail (groups inherit collapsed layout). SSR hosts must pass `mobile={true}` or `mobile={false}` so server and client markup match. Omit `mobile` only in client-only surfaces to follow the theme mobile range. Do not hard-code viewport widths. Trailing slot content on destination rows must be non-interactive. When the mobile layout is shown, the menu control opens a right `Sheet` with the same `centerSlotContent` and `bottomSlotContent` as the rail (labeled rows). Use `menuOpen` / `onMenuOpenChange` to close it after navigation.
|
|
32
|
+
|
|
33
|
+
## Footer
|
|
34
|
+
|
|
35
|
+
Optional — omit `Footer` when the page has none; place it **inside** `<main>` after the page column so it scrolls with the page. Pass **only the bands this experience needs**: `null` hides a band (same pattern as Navigation `bottomSlot={null}`), real nodes fill one; never leave a slot off in generated UI (omitted `undefined` fills catalog demo content). Do not wrap `Footer` in extra `p` / `gap` — band padding is catalog-owned (single band `small-4`, two bands the large step).
|
|
36
|
+
|
|
37
|
+
Footer content architecture — the brand block, categorized link columns, the legal/utility bar, and how to lay each band out as a tidy cluster — lives in [recipe-footer.md](recipe-footer.md).
|
|
38
|
+
|
|
39
|
+
## React
|
|
40
|
+
|
|
41
|
+
Import from `@uxelle/components`. Put skip-link CSS in the host stylesheet.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
<Layout
|
|
45
|
+
display="flex"
|
|
46
|
+
flexDirection="column"
|
|
47
|
+
height="100%"
|
|
48
|
+
style={{
|
|
49
|
+
backgroundColor: "var(--uxl-color-switcher-background)",
|
|
50
|
+
overflow: "hidden",
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<a className="uxl-skip-link" href="#main-content">
|
|
54
|
+
Skip to main content
|
|
55
|
+
</a>
|
|
56
|
+
<Layout flexShrink={0}>
|
|
57
|
+
<Navigation
|
|
58
|
+
bottomSlot={null}
|
|
59
|
+
leadingSlot={
|
|
60
|
+
<Text type="Condensed Alt" truncation width={false}>
|
|
61
|
+
Product name
|
|
62
|
+
</Text>
|
|
63
|
+
}
|
|
64
|
+
centerSlot={
|
|
65
|
+
<>
|
|
66
|
+
<NavLink label="Home" href="/" aria-current="page" />
|
|
67
|
+
<NavLink label="Settings" href="/settings" />
|
|
68
|
+
</>
|
|
69
|
+
}
|
|
70
|
+
trailingSlot={
|
|
71
|
+
<IconButton emphasis="low" size="small" iconName="search" aria-label="Search" />
|
|
72
|
+
}
|
|
73
|
+
/>
|
|
74
|
+
</Layout>
|
|
75
|
+
<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>
|
|
87
|
+
<Footer
|
|
88
|
+
topMainContent={null}
|
|
89
|
+
bottomMainContent={
|
|
90
|
+
<Text type="Body Small" width={false}>
|
|
91
|
+
© Organization
|
|
92
|
+
</Text>
|
|
93
|
+
}
|
|
94
|
+
/>
|
|
95
|
+
</main>
|
|
96
|
+
</Layout>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Responsive
|
|
100
|
+
|
|
101
|
+
- **Primary nav collapses on mobile.** Below tablet, swap the center [`NavLink`](../uxelle-components/NavLink.md) set for an [`IconButton`](../uxelle-components/IconButton.md) (`iconName="menu"`, `aria-label="Menu"`) in `trailingSlot` that opens a [`Sheet`](../uxelle-components/Sheet.md) (or [`Menu`](../uxelle-components/Menu.md)) holding the same destinations as a `List` of `NavLink`s. Confirm `Navigation`'s built-in behavior first and only add the drawer for what it does not handle; switch with `useBreakpointUp("tablet")` ([how-to-page-layout.md](how-to-page-layout.md#responsiveness)).
|
|
102
|
+
- **Trailing utilities stay a row** at every width (search, language, Log in / Register) — [how-to-page-layout.md](how-to-page-layout.md#action-clusters).
|
|
103
|
+
- **Footer** columns wrap intrinsically ([recipe-footer.md](recipe-footer.md)); band padding is catalog-owned. **Content column** reflows through the chrome tokens with nothing to set per breakpoint.
|
|
104
|
+
|
|
105
|
+
## A2UI
|
|
106
|
+
|
|
107
|
+
When-not for this recipe. Host owns chrome (skip link, viewport shell, html `data-*`). Catalog adapters: `A2uiNavigation`, `A2uiFooter`, `A2uiBannerAnnouncement` if the surface needs them — no `getUxelleRecipe("app-chrome")`. For the side rail only, `getUxelleRecipe("navigation-side")` from `@uxelle/a2ui`: trailing badges are `A2uiLabelBadge` with `"emphasis": "high"`; the account row is `A2uiList` + `A2uiListItem`. See [a2ui.md](a2ui.md).
|
|
108
|
+
|
|
109
|
+
## Help and marketing chrome
|
|
110
|
+
|
|
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.
|
|
112
|
+
|
|
113
|
+
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
|
+
|
|
115
|
+
`Breadcrumbs` belong in the **page column**, not in `Navigation`. Related articles are an in-page `Link` list under the article, not nav or crumbs.
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<Navigation
|
|
119
|
+
bottomSlot={null}
|
|
120
|
+
leadingSlot={
|
|
121
|
+
<a href="/" aria-label="Home">
|
|
122
|
+
<Logo name="generic" width={40} interactive aria-hidden />
|
|
123
|
+
</a>
|
|
124
|
+
}
|
|
125
|
+
centerSlot={false}
|
|
126
|
+
trailingSlot={
|
|
127
|
+
<>
|
|
128
|
+
<LanguageSelector value="EN" />
|
|
129
|
+
<Button emphasis="low" size="small">
|
|
130
|
+
<Text type="Button" width>
|
|
131
|
+
Log in
|
|
132
|
+
</Text>
|
|
133
|
+
</Button>
|
|
134
|
+
<Button emphasis="high" size="small">
|
|
135
|
+
<Text type="Button" width>
|
|
136
|
+
Register
|
|
137
|
+
</Text>
|
|
138
|
+
</Button>
|
|
139
|
+
</>
|
|
140
|
+
}
|
|
141
|
+
/>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
In the page column, before the `h1`:
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<Breadcrumbs>
|
|
148
|
+
<Breadcrumb label="Help home" href="/" />
|
|
149
|
+
<Breadcrumb label="Sending money" href="/sending-money" />
|
|
150
|
+
<Breadcrumb label="Can I send exact amounts?" />
|
|
151
|
+
</Breadcrumbs>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Last `Breadcrumb` is the current page (`currentPage` or no `href`). Ancestor crumbs pass `href`.
|
|
155
|
+
|
|
156
|
+
After the article body, related links stay in the page column:
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
<Layout as="section" display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-small-4)">
|
|
160
|
+
<Text type="Display Extra Small" as="h2" width={false}>
|
|
161
|
+
Related articles
|
|
162
|
+
</Text>
|
|
163
|
+
<Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-2)">
|
|
164
|
+
<Link href="/help/batch-payments">Sending exact amounts — Batch payments</Link>
|
|
165
|
+
<Link href="/help/guaranteed-rate">What if I miss my guaranteed rate?</Link>
|
|
166
|
+
</Layout>
|
|
167
|
+
</Layout>
|
|
168
|
+
```
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Recipe: card grid
|
|
2
|
+
|
|
3
|
+
A shared **piece**: a row of equal cells that reflows by width. Used for feature /
|
|
4
|
+
benefit grids, case-study or product tiles, and dashboard groupings.
|
|
5
|
+
|
|
6
|
+
Composes [`Card`](../uxelle-components/Card.md) (single content surface) or
|
|
7
|
+
[`ProductCard`](../uxelle-components/ProductCard.md) (fixed top / center / bottom
|
|
8
|
+
regions), plus [`Text`](../uxelle-components/Text.md),
|
|
9
|
+
[`LabelBadge`](../uxelle-components/LabelBadge.md), and
|
|
10
|
+
[`Image`](../uxelle-components/Image.md).
|
|
11
|
+
|
|
12
|
+
## When
|
|
13
|
+
|
|
14
|
+
Three-ish comparable cells in a row on a React page — features, benefits, case
|
|
15
|
+
studies, product tiles, or dashboard cards.
|
|
16
|
+
|
|
17
|
+
## When not
|
|
18
|
+
|
|
19
|
+
- Not a `CardGrid` component — it is a `Layout` grid of cards.
|
|
20
|
+
- Use `ProductCard` only when the tile needs fixed top / center / bottom regions;
|
|
21
|
+
otherwise `Card`.
|
|
22
|
+
- Do not use `--uxl-breakpoints-margin` as the gap (that token is page inset) — use
|
|
23
|
+
`--uxl-breakpoints-gutter`.
|
|
24
|
+
|
|
25
|
+
## Regions
|
|
26
|
+
|
|
27
|
+
page column -> grid (`auto-fit` / `minmax` tracks, `gap` = gutter) -> `Card` /
|
|
28
|
+
`ProductCard` cells
|
|
29
|
+
|
|
30
|
+
## Responsive
|
|
31
|
+
|
|
32
|
+
Reflows intrinsically — no JS, no breakpoint literals. `repeat(auto-fit,
|
|
33
|
+
minmax(MIN, 1fr))` drops from 3-up to 2-up to 1-up as width shrinks; pick `MIN`
|
|
34
|
+
(e.g. `16rem`) for the smallest comfortable card. Cards fill the row at every width.
|
|
35
|
+
|
|
36
|
+
## Spacing
|
|
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).
|
|
40
|
+
|
|
41
|
+
## A11y
|
|
42
|
+
|
|
43
|
+
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`
|
|
45
|
+
CTA names itself; if the whole card is the link, the heading text is the name. See
|
|
46
|
+
[how-to-accessibility.md](how-to-accessibility.md).
|
|
47
|
+
|
|
48
|
+
## React
|
|
49
|
+
|
|
50
|
+
Feature / benefit grid (`Card`):
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<Layout
|
|
54
|
+
display="grid"
|
|
55
|
+
gridTemplateColumns="repeat(auto-fit, minmax(16rem, 1fr))"
|
|
56
|
+
gap="var(--uxl-breakpoints-gutter)"
|
|
57
|
+
aria-label="Highlights"
|
|
58
|
+
>
|
|
59
|
+
<Card
|
|
60
|
+
cardContentSlot={
|
|
61
|
+
<Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-small-4)">
|
|
62
|
+
<Icon iconName="bolt" variant="sharpUnfilled" aria-hidden />
|
|
63
|
+
<Text type="Display Extra Small" as="h2" width={false}>Fast by default</Text>
|
|
64
|
+
<Text type="Body Medium" width={false}>Ship in minutes with sensible presets.</Text>
|
|
65
|
+
</Layout>
|
|
66
|
+
}
|
|
67
|
+
/>
|
|
68
|
+
{/* repeat Card cells */}
|
|
69
|
+
</Layout>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Case-study / product tiles (`ProductCard`, media + CTA):
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
<Layout
|
|
76
|
+
display="grid"
|
|
77
|
+
gridTemplateColumns="repeat(auto-fit, minmax(18rem, 1fr))"
|
|
78
|
+
gap="var(--uxl-breakpoints-gutter)"
|
|
79
|
+
aria-label="Customer stories"
|
|
80
|
+
>
|
|
81
|
+
<ProductCard
|
|
82
|
+
topSlotContent={
|
|
83
|
+
<>
|
|
84
|
+
<LabelBadge label="Case study" emphasis="low" />
|
|
85
|
+
<Text type="Display Extra Small" as="h2" width={false}>40% faster close</Text>
|
|
86
|
+
<Text type="Body Medium" width={false}>How Northwind cut cycle time.</Text>
|
|
87
|
+
</>
|
|
88
|
+
}
|
|
89
|
+
centerSlotContent={<Image src="/case/northwind.jpg" alt="" aspectRatio="16:9" />}
|
|
90
|
+
bottomSlotContent={
|
|
91
|
+
<Button fullWidth emphasis="medium">
|
|
92
|
+
<Text type="Button" width>Read story</Text>
|
|
93
|
+
</Button>
|
|
94
|
+
}
|
|
95
|
+
/>
|
|
96
|
+
{/* repeat ProductCard cells */}
|
|
97
|
+
</Layout>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## A2UI
|
|
101
|
+
|
|
102
|
+
`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)).
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Recipe: CTA band
|
|
2
|
+
|
|
3
|
+
A marketing **piece**: a full-width brand band near the end of a page with one
|
|
4
|
+
decisive action. The closing counterpart to the [hero](recipe-hero.md).
|
|
5
|
+
|
|
6
|
+
Composes [`Layout`](../uxelle-components/Layout.md) (the band),
|
|
7
|
+
[`Lockup`](../uxelle-components/Lockup.md),
|
|
8
|
+
[`ButtonGroup`](../uxelle-components/ButtonGroup.md), and optionally a
|
|
9
|
+
[`DynamicAngle*`](../uxelle-components/DynamicAngleShapeSurface.md) accent.
|
|
10
|
+
|
|
11
|
+
## When
|
|
12
|
+
|
|
13
|
+
A single, decisive conversion moment: "Start free," "Talk to sales."
|
|
14
|
+
|
|
15
|
+
## When not
|
|
16
|
+
|
|
17
|
+
- One primary action (an optional secondary is fine) — not a menu of links.
|
|
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.
|
|
21
|
+
|
|
22
|
+
## Regions
|
|
23
|
+
|
|
24
|
+
full-bleed `section` (`data-color-switcher`) -> capped inner column -> `Lockup`
|
|
25
|
+
(`h2` + body) + `ButtonGroup`, optional decorative `DynamicAngle*`
|
|
26
|
+
|
|
27
|
+
## Responsive
|
|
28
|
+
|
|
29
|
+
The band is full-bleed; the inner column caps with
|
|
30
|
+
`--uxl-breakpoints-max-container-width` and centers. Actions are a row on wide
|
|
31
|
+
viewports and stack `fullWidth` below tablet (`useBreakpointUp("tablet")`). See
|
|
32
|
+
[how-to-page-layout.md](how-to-page-layout.md#action-clusters).
|
|
33
|
+
|
|
34
|
+
## Spacing
|
|
35
|
+
|
|
36
|
+
`large-13`+ block padding for the band; `medium-8` between the lockup and actions
|
|
37
|
+
([spacing-steps.md](spacing-steps.md)).
|
|
38
|
+
|
|
39
|
+
## A11y
|
|
40
|
+
|
|
41
|
+
Section `h2`; name the region with it. A `DynamicAngle*` accent is decorative
|
|
42
|
+
(`aria-hidden`) and is muted under `prefers-reduced-motion`. Style all text with role
|
|
43
|
+
tokens so contrast holds on the palette. See [how-to-accessibility.md](how-to-accessibility.md).
|
|
44
|
+
|
|
45
|
+
## Color
|
|
46
|
+
|
|
47
|
+
Set `data-color-switcher` on the **section** and style with `--uxl-color-switcher-*`
|
|
48
|
+
role tokens — never hardcode hex. Pick the brand palette by the look you want
|
|
49
|
+
([how-to-color.md](how-to-color.md)).
|
|
50
|
+
|
|
51
|
+
## React
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<Layout
|
|
55
|
+
as="section"
|
|
56
|
+
data-color-switcher="brand-1"
|
|
57
|
+
display="flex"
|
|
58
|
+
justifyContent="center"
|
|
59
|
+
p="var(--uxl-theme-layout-spacing-large-13)"
|
|
60
|
+
style={{ backgroundColor: "var(--uxl-color-switcher-background)" }}
|
|
61
|
+
>
|
|
62
|
+
<Layout
|
|
63
|
+
display="flex"
|
|
64
|
+
flexDirection="column"
|
|
65
|
+
alignItems="center"
|
|
66
|
+
gap="var(--uxl-theme-layout-spacing-medium-8)"
|
|
67
|
+
width="100%"
|
|
68
|
+
maxWidth="var(--uxl-breakpoints-max-container-width)"
|
|
69
|
+
>
|
|
70
|
+
<Lockup
|
|
71
|
+
topSlot={false}
|
|
72
|
+
title={<Text type="Display Medium" as="h2" width={false}>Ready to ship faster?</Text>}
|
|
73
|
+
body={<Text type="Body Medium" width={false}>Start free — no card required.</Text>}
|
|
74
|
+
/>
|
|
75
|
+
<ButtonGroup
|
|
76
|
+
direction={tabletUp ? "row" : "column"}
|
|
77
|
+
fullWidth={!tabletUp}
|
|
78
|
+
aria-label="Get started"
|
|
79
|
+
>
|
|
80
|
+
<Button emphasis="high"><Text type="Button" width>Start free</Text></Button>
|
|
81
|
+
<Button emphasis="medium"><Text type="Button" width>Talk to sales</Text></Button>
|
|
82
|
+
</ButtonGroup>
|
|
83
|
+
</Layout>
|
|
84
|
+
</Layout>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## A2UI
|
|
88
|
+
|
|
89
|
+
No `getUxelleRecipe("cta-band")` — wrap an `A2uiLayout` (with `data-color-switcher`)
|
|
90
|
+
around stacked `A2uiText` and an `A2uiButtonGroup` (`"direction": "column"`). See
|
|
91
|
+
[a2ui.md](a2ui.md).
|