@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,62 @@
|
|
|
1
|
+
# Textfield
|
|
2
|
+
|
|
3
|
+
Import: `import { Textfield } from "@uxelle/components";`
|
|
4
|
+
|
|
5
|
+
Single-line text input with design tokens, optional `Label`, icon slots, and `FieldMessage`.
|
|
6
|
+
|
|
7
|
+
## Props
|
|
8
|
+
|
|
9
|
+
<!-- prettier-ignore-start -->
|
|
10
|
+
|
|
11
|
+
| Prop | Type | Default | Description |
|
|
12
|
+
|------|------|---------|-------------|
|
|
13
|
+
| label | `boolean` | `true` | When false, the label row is not rendered; pass `aria-label` so the input keeps an accessible name. |
|
|
14
|
+
| labelText | `string` | `"Label"` | Visible label text, rendered as Body Medium Alt inside `Label`; ignored when `labelSlot` is set, and an empty string hides the label row. |
|
|
15
|
+
| labelSlot | `ReactNode` | `—` | Replaces `labelText` with custom content inside `Label`, which keeps `htmlFor` and the required asterisk. Pass `id` on `Textfield` for a stable label association. |
|
|
16
|
+
| fieldMessage | `boolean` | `false` | Renders a `FieldMessage` row below the input; `validation` set to `success` or `error` shows the row as well, so use this flag for neutral helper copy. |
|
|
17
|
+
| fieldMessageContent | `string` | `""` | Content for the `FieldMessage` row; when omitted, the row falls back to built-in copy for the current `validation` state. |
|
|
18
|
+
| validation | `"none" \| "success" \| "error"` | `"none"` | Validation state: `success` and `error` restyle the field message and force the row; `error` also sets `aria-invalid` and links the message via `aria-errormessage`. |
|
|
19
|
+
| leadingIcon | `boolean` | `false` | When true, renders the built-in leading `Icon` from `leadingIconName`; `leadingSlot` is the identical alias and `leading` overrides both. |
|
|
20
|
+
| leadingSlot | `boolean` | `false` | Alias of `leadingIcon`: either flag turns the leading region on, matching the `ListItem` slot naming. |
|
|
21
|
+
| leadingIconName | `string` | `"add"` | Material Symbol rendered in the leading region; used only when `leadingIcon` (or `leadingSlot`) is true and `leading` is unset. An empty string falls back to `add`. |
|
|
22
|
+
| leadingIconVariant | `"sharpFilled" \| "sharpUnfilled"` | `—` | Icon style for the built-in leading icon — `sharpUnfilled` (outlined) or `sharpFilled` (solid); ignored when `leading` supplies its own icon. |
|
|
23
|
+
| leading | `ReactNode` | `—` | Custom node rendered in the leading region instead of the built-in icon, with no need for `leadingIcon`. Buttons and links here keep their own click target. |
|
|
24
|
+
| trailingIcon | `boolean` | `false` | When true, renders the built-in trailing `Icon` from `trailingIconName`; `trailingSlot` is the identical alias, while `trailing` and `passwordVisibilityToggle` take the region over. |
|
|
25
|
+
| trailingSlot | `boolean` | `false` | Alias of `trailingIcon`: either flag turns the trailing region on, matching the `ListItem` slot naming. |
|
|
26
|
+
| trailingIconName | `string` | `"visibility_off"` | Material Symbol rendered in the trailing region; an empty string falls back to `visibility_off`. Used only when `trailingIcon` (or `trailingSlot`) is true and neither `trailing` nor `passwordVisibilityToggle` owns the region. |
|
|
27
|
+
| trailingIconVariant | `"sharpFilled" \| "sharpUnfilled"` | `—` | Icon style — `sharpUnfilled` (outlined) or `sharpFilled` (solid) — for the built-in trailing icon and for the `passwordVisibilityToggle` button icon. |
|
|
28
|
+
| trailing | `ReactNode` | `—` | Custom node rendered in the trailing region, taking precedence over `passwordVisibilityToggle` and the built-in trailing icon. Use it for actions such as a clear or search `IconButton`. |
|
|
29
|
+
| passwordVisibilityToggle | `boolean` | `false` | Adds a trailing `IconButton` that swaps the input between `password` and `text`, overriding `type` while it is on. Ignored when `trailing` is set; pair it with `autoComplete` (`current-password` or `new-password`). |
|
|
30
|
+
| showPasswordAriaLabel | `string` | `"Show password"` | `aria-label` on the password toggle while the value is masked; localize it together with `hidePasswordAriaLabel`. |
|
|
31
|
+
| hidePasswordAriaLabel | `string` | `"Hide password"` | `aria-label` on the password toggle while the value is visible; localize it together with `showPasswordAriaLabel`. |
|
|
32
|
+
| placeholder | `string` | `""` | Hint text shown inside the input while it is empty; it disappears on typing, so it is not a substitute for `labelText` or `aria-label`. |
|
|
33
|
+
| disabled | `boolean` | `false` | Blocks typing, removes the input from the tab order, and applies disabled styling; disabled inputs are not submitted with the form. |
|
|
34
|
+
| readOnly | `boolean` | `false` | Read-only state (token background, no hover; focus uses the focus stroke) |
|
|
35
|
+
|
|
36
|
+
<!-- prettier-ignore-end -->
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<Textfield labelText="Email" placeholder="e.g. name@example.com" leadingIcon fieldMessage />
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
<Textfield
|
|
46
|
+
labelText="Password"
|
|
47
|
+
type="password"
|
|
48
|
+
passwordVisibilityToggle
|
|
49
|
+
autoComplete="current-password"
|
|
50
|
+
name="password"
|
|
51
|
+
/>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## Notes
|
|
56
|
+
|
|
57
|
+
- Associates label and field message with the input for accessibility.
|
|
58
|
+
- **Simple API** — `labelText`, `leadingIcon` / `trailingIcon`, and icon names.
|
|
59
|
+
- **Composable API** — `labelSlot`, `leading`, and `trailing` (same idea as `ListItem` slots). Use `leadingIcon` / `trailingIcon` or the equivalent `leadingSlot` / `trailingSlot` flags to show the side regions.
|
|
60
|
+
- **Password visibility** — `passwordVisibilityToggle` renders an accessible trailing toggle (unless `trailing` is set).
|
|
61
|
+
- **Multi-line** — Use `Textarea` for comments, notes, bios, and other multi-line copy. `Textfield` is single-line only.
|
|
62
|
+
- **Accessibility** — Pass `id` for stable `label` association; use `autoComplete` for known field types; pass `aria-label` when `label={false}`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
|
|
3
|
+
Import: `import { Tooltip } from "@uxelle/components";`
|
|
4
|
+
|
|
5
|
+
Hover/focus supplementary disclosure (`role="tooltip"`).
|
|
6
|
+
|
|
7
|
+
## Props
|
|
8
|
+
|
|
9
|
+
<!-- prettier-ignore-start -->
|
|
10
|
+
|
|
11
|
+
| Prop | Type | Default | Description |
|
|
12
|
+
|------|------|---------|-------------|
|
|
13
|
+
| children (required) | `ReactElement` | `—` | The trigger the tooltip attaches to and describes — usually a button, link, or icon. Must be a single React element (not a fragment or bare string): it is cloned to receive `aria-describedby` pointing at the panel while open, and hovering or focusing it is what reveals the panel. |
|
|
14
|
+
| content (required) | `ReactNode` | `—` | Supplementary text rendered inside the floating panel. Keep it short and non-interactive — the panel is `role="tooltip"` and cannot be tabbed into. |
|
|
15
|
+
| direction | `"Top Right" \| "Top Center" \| "Top Left" \| "Right Top" \| "Right Center" \| "Right Bottom" \| "Bottom Right" \| "Bottom Center" \| "Bottom Left" \| "Left Bottom" \| "Left Center" \| "Left Top"` | `"Top Right"` | Preferred panel position: the first word is the trigger edge the panel sits on, the second is its alignment along that edge. Flips to the opposite edge when the viewport lacks room. |
|
|
16
|
+
| open | `boolean` | `false` | Controlled visibility of the panel. Pass it with `onOpenChange`; omit it to let hover, focus, and Escape drive visibility internally. |
|
|
17
|
+
| onOpenChange | `(open: boolean) => void` | `—` | Called with the requested visibility whenever hover, focus, blur, or Escape asks to change it. Required when `open` is passed, since the component then keeps no visibility state of its own. |
|
|
18
|
+
| tooltipId | `string` | `""` | Id put on the panel and referenced by the trigger's `aria-describedby` while open. Leave empty to auto-generate a unique id. |
|
|
19
|
+
| tooltipLabel | `string` | `""` | Sets `aria-label` on the panel, replacing `content` as its accessible name when the visible text alone reads poorly. Leave empty to let `content` name the panel. |
|
|
20
|
+
|
|
21
|
+
<!-- prettier-ignore-end -->
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
<Tooltip content={/* ReactNode */}>
|
|
27
|
+
{/* ReactElement */}
|
|
28
|
+
</Tooltip>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Notes
|
|
32
|
+
|
|
33
|
+
- Use non-interactive `content` and theme tokens for color/contrast. Omit `open` for hover/focus-driven visibility. Pass `open` (with `onOpenChange`) to control visibility, including design/docs previews.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uxelle-design-harness
|
|
3
|
+
description: >-
|
|
4
|
+
The single home for building on-brand, enterprise uxElle experiences: Swiss
|
|
5
|
+
design principles, the --uxl-* variable foundation (layout spacing, page
|
|
6
|
+
chrome, color-switcher palettes, radius, elevation, type), first-class mobile
|
|
7
|
+
responsiveness, WCAG AA, and composition recipes for product and marketing
|
|
8
|
+
surfaces (app chrome, dashboards, data tables, record detail, settings,
|
|
9
|
+
multi-step flows, states, landing pages, heroes, pricing, CTAs) across React
|
|
10
|
+
(@uxelle/components) and A2UI (@uxelle/a2ui). Use when generating pages, apps,
|
|
11
|
+
forms, layouts, dashboards, marketing/landing pages, greenfield React, or A2UI
|
|
12
|
+
surfaces, or when writing CSS / inline styles that need --uxl-* tokens.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# uxElle design harness
|
|
16
|
+
|
|
17
|
+
The home for generating on-brand uxElle experiences. It carries the design
|
|
18
|
+
**principles**, the **variable foundation** (tokens), the **layout and
|
|
19
|
+
responsiveness** rules, and the **recipes** that compose components into whole
|
|
20
|
+
experiences and reusable pieces.
|
|
21
|
+
|
|
22
|
+
One approach, two projections: **React** (`@uxelle/components`) for IDE agents
|
|
23
|
+
and **A2UI** (`getUxelleRecipe` from `@uxelle/a2ui`) for runtime hosts. Write the
|
|
24
|
+
design once; [a2ui.md](a2ui.md) maps it to the runtime track.
|
|
25
|
+
|
|
26
|
+
## Philosophy
|
|
27
|
+
|
|
28
|
+
Design like the International Typographic (Swiss) school: a clear grid, strong
|
|
29
|
+
typographic hierarchy, generous whitespace, restraint, and objective, content-first
|
|
30
|
+
layouts. Restraint is **context-aware** — product surfaces are maximally calm;
|
|
31
|
+
marketing surfaces are expressive within the *same* grid, hierarchy, and limited
|
|
32
|
+
palette. The full mapping of principles to uxElle mechanisms lives in
|
|
33
|
+
[principles.md](principles.md).
|
|
34
|
+
|
|
35
|
+
## Use the harness
|
|
36
|
+
|
|
37
|
+
1. Load this `SKILL.md`. The foundations below always apply.
|
|
38
|
+
2. Read the variable foundation once: [tokens.md](tokens.md) and
|
|
39
|
+
[spacing-steps.md](spacing-steps.md).
|
|
40
|
+
3. Creating an app shell? [how-to-host.md](how-to-host.md). Building a page body?
|
|
41
|
+
[how-to-page-layout.md](how-to-page-layout.md) (layout **and** responsiveness),
|
|
42
|
+
[how-to-color.md](how-to-color.md), and [how-to-accessibility.md](how-to-accessibility.md).
|
|
43
|
+
4. Prefer an existing component. Link its doc at point of use, e.g.
|
|
44
|
+
`Table` -> `../uxelle-components/Table.md`. Import from `@uxelle/components`.
|
|
45
|
+
5. Otherwise follow a recipe (see the index). If nothing fits, compose `Layout` +
|
|
46
|
+
`Text` + primitives on the grid.
|
|
47
|
+
6. Building a runtime surface? Read [a2ui.md](a2ui.md) and each recipe's **A2UI** note.
|
|
48
|
+
7. Before you finish, run the quality checklist below.
|
|
49
|
+
|
|
50
|
+
## Decision tree
|
|
51
|
+
|
|
52
|
+
- **New app/host** (loads theme CSS, `data-*`, skip link, `main`) -> [how-to-host.md](how-to-host.md).
|
|
53
|
+
Composing inside an existing host -> skip host setup; do not set theme `data-*` on `<html>`.
|
|
54
|
+
- **Product experience** (signed-in app):
|
|
55
|
+
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);
|
|
58
|
+
one record -> [recipe-record-detail.md](recipe-record-detail.md);
|
|
59
|
+
settings -> [recipe-settings-page.md](recipe-settings-page.md);
|
|
60
|
+
wizard/checkout -> [recipe-multi-step-flow.md](recipe-multi-step-flow.md);
|
|
61
|
+
generic body -> [recipe-page-shell.md](recipe-page-shell.md).
|
|
62
|
+
- **Marketing experience** (public): whole page -> [recipe-landing-page.md](recipe-landing-page.md),
|
|
63
|
+
composed from the marketing pieces below.
|
|
64
|
+
- **A piece** (part of an experience): page intro -> [recipe-page-header.md](recipe-page-header.md);
|
|
65
|
+
filter/search toolbar -> [recipe-query-bar.md](recipe-query-bar.md);
|
|
66
|
+
form block -> [recipe-form-section.md](recipe-form-section.md);
|
|
67
|
+
key/value summary -> [recipe-summary-list.md](recipe-summary-list.md);
|
|
68
|
+
card row -> [recipe-card-grid.md](recipe-card-grid.md);
|
|
69
|
+
metrics -> [recipe-stat-callouts.md](recipe-stat-callouts.md);
|
|
70
|
+
empty/loading/error -> [recipe-states.md](recipe-states.md);
|
|
71
|
+
page footer -> [recipe-footer.md](recipe-footer.md);
|
|
72
|
+
hero -> [recipe-hero.md](recipe-hero.md); feature -> [recipe-feature-section.md](recipe-feature-section.md);
|
|
73
|
+
trust bar -> [recipe-logo-wall.md](recipe-logo-wall.md); testimonial -> [recipe-testimonial.md](recipe-testimonial.md);
|
|
74
|
+
pricing -> [recipe-pricing.md](recipe-pricing.md); closing CTA -> [recipe-cta-band.md](recipe-cta-band.md).
|
|
75
|
+
|
|
76
|
+
## Core rules
|
|
77
|
+
|
|
78
|
+
- **Spacing**: stacks use `0` or `var(--uxl-theme-layout-spacing-*)` on `gap` /
|
|
79
|
+
padding / margin. Page chrome (inset, column cap, `fr` gaps) uses
|
|
80
|
+
`--uxl-breakpoints-margin` / `-max-container-width` / `-gutter`. See
|
|
81
|
+
[spacing-steps.md](spacing-steps.md) and [how-to-page-layout.md](how-to-page-layout.md).
|
|
82
|
+
- **Color**: style via `--uxl-color-switcher-*` roles; apply a palette with
|
|
83
|
+
`data-color-switcher` on a **region** (never invent switcher names). See
|
|
84
|
+
[how-to-color.md](how-to-color.md).
|
|
85
|
+
- **No fabrication**: never invent `--uxl-*` names, never `var(--token, fallback)`,
|
|
86
|
+
never use `--uxl-component-*` in app/recipe CSS (those belong inside components).
|
|
87
|
+
- **No hardcoded values**: no hex, no font-family, no breakpoint px. Read
|
|
88
|
+
breakpoint bounds from theme variables when JS is unavoidable
|
|
89
|
+
([how-to-page-layout.md](how-to-page-layout.md)). Exception: one-column reading
|
|
90
|
+
content (a form/settings page, or a form / FAQ / prose section on a wider page)
|
|
91
|
+
may cap at the `maxWidth="600px"` **reading measure** — always centered with
|
|
92
|
+
`mh="auto"` ([how-to-page-layout.md](how-to-page-layout.md#reading-measure)).
|
|
93
|
+
- **Components first**: prefer a catalog component over bespoke markup; link its
|
|
94
|
+
doc at point of use (`../uxelle-components/<Name>.md`); do not open package source.
|
|
95
|
+
- **Recipes are patterns, not exports**: never add `PageShell` / `AppChrome` /
|
|
96
|
+
`FormSection` to `@uxelle/components`, and never author new recipes in the
|
|
97
|
+
customer app — contribute them back to uxElle ([recipe-template.md](recipe-template.md)).
|
|
98
|
+
- **A2UI**: catalog adapters only — no `className` / `style`, layout-spacing only,
|
|
99
|
+
`import { getUxelleRecipe } from "@uxelle/a2ui"`. See [a2ui.md](a2ui.md).
|
|
100
|
+
|
|
101
|
+
## Quality checklist (run before you finish)
|
|
102
|
+
|
|
103
|
+
Every generated screen must pass this. It is the definition of done.
|
|
104
|
+
|
|
105
|
+
- **Grid & rhythm**: content sits on the grid; one capped content column via
|
|
106
|
+
`--uxl-breakpoints-max-container-width` / `-gutter` / `-margin`; section spacing
|
|
107
|
+
from the ramp (page sections use `medium-12`). One-column reading sections
|
|
108
|
+
(form / FAQ / prose) share the `600px` reading measure and are centered
|
|
109
|
+
(`mh="auto"`); a decision cluster (form actions) is separated from its fields by
|
|
110
|
+
`medium-12`, not the field gap.
|
|
111
|
+
- **Tokens only**: spacing from `--uxl-theme-layout-spacing-*`; color from
|
|
112
|
+
`--uxl-color-switcher-*`; no hardcoded hex or px, no invented `--uxl-*`, no
|
|
113
|
+
`--uxl-component-*` in app CSS, no `var(--x, fallback)`.
|
|
114
|
+
- **Hierarchy**: exactly one `h1`; heading ranks unbroken; few `Text` type roles.
|
|
115
|
+
- **Responsive**: reflows mobile -> up with no px literals; clusters stack, `Table`
|
|
116
|
+
scrolls, nav collapses; touch targets stay >= 24 CSS px.
|
|
117
|
+
- **Color discipline**: at most one dominant brand band per view; status palettes
|
|
118
|
+
only for semantic status, never color-alone (paired with text or icon).
|
|
119
|
+
- **Accessibility**: skip link + one `main`; landmarks named; focus visible; every
|
|
120
|
+
control has an accessible name; async regions announced.
|
|
121
|
+
- **Components first**: composed existing components (linked at point of use) over
|
|
122
|
+
bespoke markup; no recipe exported as a component; package source not opened.
|
|
123
|
+
- **A2UI (if used)**: catalog adapters only; no `className` / `style`; layout-spacing only.
|
|
124
|
+
|
|
125
|
+
## Index
|
|
126
|
+
|
|
127
|
+
**Foundations** (always apply): [principles.md](principles.md) ·
|
|
128
|
+
[tokens.md](tokens.md) · [spacing-steps.md](spacing-steps.md) ·
|
|
129
|
+
[how-to-page-layout.md](how-to-page-layout.md) · [how-to-color.md](how-to-color.md) ·
|
|
130
|
+
[how-to-accessibility.md](how-to-accessibility.md) · [how-to-host.md](how-to-host.md) ·
|
|
131
|
+
[a2ui.md](a2ui.md)
|
|
132
|
+
|
|
133
|
+
**Experiences** — product: [app-chrome](recipe-app-chrome.md) ·
|
|
134
|
+
[page-shell](recipe-page-shell.md) · [dashboard-overview](recipe-dashboard-overview.md) ·
|
|
135
|
+
[data-table-page](recipe-data-table-page.md) · [record-detail](recipe-record-detail.md) ·
|
|
136
|
+
[settings-page](recipe-settings-page.md) · [multi-step-flow](recipe-multi-step-flow.md).
|
|
137
|
+
Marketing: [landing-page](recipe-landing-page.md).
|
|
138
|
+
|
|
139
|
+
**Pieces** — shared: [page-header](recipe-page-header.md) ·
|
|
140
|
+
[form-section](recipe-form-section.md) · [query-bar](recipe-query-bar.md) ·
|
|
141
|
+
[card-grid](recipe-card-grid.md) · [summary-list](recipe-summary-list.md) ·
|
|
142
|
+
[states](recipe-states.md) · [stat-callouts](recipe-stat-callouts.md) ·
|
|
143
|
+
[logo-wall](recipe-logo-wall.md) · [footer](recipe-footer.md). Marketing: [hero](recipe-hero.md) ·
|
|
144
|
+
[feature-section](recipe-feature-section.md) · [testimonial](recipe-testimonial.md) ·
|
|
145
|
+
[pricing](recipe-pricing.md) · [cta-band](recipe-cta-band.md).
|
|
146
|
+
|
|
147
|
+
**Component APIs**: link each at point of use — [uxelle-components](../uxelle-components/SKILL.md).
|
|
148
|
+
|
|
149
|
+
**Contributing** a recipe: [recipe-template.md](recipe-template.md).
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# A2UI: the runtime translation layer
|
|
2
|
+
|
|
3
|
+
A2UI is the runtime-host projection of the same designs. Instead of React JSX, a
|
|
4
|
+
host renders uxElle components from catalog **adapters** driven by JSON messages.
|
|
5
|
+
Design the experience once with the principles, tokens, and recipes here; this file
|
|
6
|
+
maps it to A2UI. Each recipe also carries a short **A2UI** note with its specifics.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { getUxelleRecipe, uxelleCatalogSchema } from "@uxelle/a2ui";
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Hosts inject `uxelleCatalogSchema` (and optionally a recipe's messages) into the
|
|
13
|
+
agent prompt and render the result. Do not open package source to "discover" props —
|
|
14
|
+
use the catalog schema the host provides.
|
|
15
|
+
|
|
16
|
+
## Hard constraints
|
|
17
|
+
|
|
18
|
+
- **Catalog adapters only.** No raw HTML, no custom components.
|
|
19
|
+
- **No `className` / `style`.** Styling comes from the theme and adapter props.
|
|
20
|
+
- **Layout-spacing tokens only.** `A2uiLayout` accepts `0` or
|
|
21
|
+
`var(--uxl-theme-layout-spacing-*)`; never emit page-chrome breakpoint tokens.
|
|
22
|
+
- **No responsive JS.** There is no `matchMedia`. Default to a single, narrow
|
|
23
|
+
column (chat is mobile-like) and stack action clusters (`"direction": "column"`).
|
|
24
|
+
- **Host owns theme.** Never set theme `data-*` or a skip link from adapters; the
|
|
25
|
+
host wraps content in its theme scope.
|
|
26
|
+
- **Bind, don't hold state.** Wire values through the host data model (bindings),
|
|
27
|
+
not React state.
|
|
28
|
+
|
|
29
|
+
## Prebuilt recipes
|
|
30
|
+
|
|
31
|
+
Registered runtime recipes today:
|
|
32
|
+
|
|
33
|
+
- `getUxelleRecipe("page-shell")`
|
|
34
|
+
- `getUxelleRecipe("form-section")`
|
|
35
|
+
- `getUxelleRecipe("navigation-side")` — side rail; Overview trailing
|
|
36
|
+
`A2uiLabelBadge` uses `"emphasis": "high"`; account row is `A2uiList` +
|
|
37
|
+
`A2uiListItem` (`label` plus optional `bottomText`)
|
|
38
|
+
|
|
39
|
+
Every other recipe in this harness has **no `getUxelleRecipe` yet** — compose it
|
|
40
|
+
from adapters following the recipe's regions and its A2UI note. Do not duplicate
|
|
41
|
+
recipe JSON into the customer app.
|
|
42
|
+
|
|
43
|
+
## Adapter map
|
|
44
|
+
|
|
45
|
+
Most catalog components map 1:1 to an `A2ui<Name>` adapter (e.g. `Button` ->
|
|
46
|
+
`A2uiButton`, `Textfield` -> `A2uiTextfield`, `Textarea` -> `A2uiTextarea`,
|
|
47
|
+
`Table` -> `A2uiTable`, `Sheet` -> `A2uiSheet`, `Hero` -> `A2uiHero`,
|
|
48
|
+
`Stepper` -> `A2uiStepper` + `A2uiStepperItem`, `ListControls` -> `A2uiListControls`,
|
|
49
|
+
`Banner` -> `A2uiBanner`, `LabelBadge` -> `A2uiLabelBadge`, `StatTile` -> `A2uiStatTile`,
|
|
50
|
+
`DynamicAngle*` -> `A2uiDynamicAngle*`). `Table` has the full family (`A2uiTableGrid`,
|
|
51
|
+
`A2uiTableHead`, `A2uiTableBody`, `A2uiTableRow`, `A2uiTableCell`,
|
|
52
|
+
`A2uiTableHeaderCell`, `A2uiTableColumnSizingProvider`).
|
|
53
|
+
|
|
54
|
+
Known gaps — substitute rather than invent:
|
|
55
|
+
|
|
56
|
+
| React | A2UI |
|
|
57
|
+
|-------|------|
|
|
58
|
+
| `Lockup` | no adapter — stack `A2uiText` (eyebrow / title / body) |
|
|
59
|
+
| `TabsCompact` / `TabsStretch` | `A2uiTabGroup` + `A2uiTab` |
|
|
60
|
+
| `Pagination` / `PaginationSimple` / `PaginationComplex` | no adapter yet — omit or use a bound prev/next with `A2uiButton` |
|
|
61
|
+
| `SkeletonGroup` | no adapter — repeat `A2uiSkeleton` |
|
|
62
|
+
| `Quantity` | no adapter — use `A2uiTextfield` (numeric) |
|
|
63
|
+
|
|
64
|
+
If a design needs a gap component as its backbone, say so in the recipe's
|
|
65
|
+
**when-not** and pick the closest supported composition.
|
|
66
|
+
|
|
67
|
+
## Translating a React recipe
|
|
68
|
+
|
|
69
|
+
1. Keep the same region order and layout-spacing values.
|
|
70
|
+
2. Swap each component for its adapter; expand `Lockup` to stacked `A2uiText`.
|
|
71
|
+
3. Replace `useBreakpointUp` switches with a single narrow column.
|
|
72
|
+
4. Replace React state with host bindings.
|
|
73
|
+
5. Drop skip link and theme `data-*` (host-owned).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# How to: accessibility (generated UI)
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
## Baseline
|
|
6
|
+
|
|
7
|
+
- One `h1`, then `h2`… with no skipped ranks (`Text as="h1"` / `as="h2"`).
|
|
8
|
+
- One `main`. If nav/chrome exists, put a skip link first in the DOM (`href="#main-content"`). CSS: [how-to-host.md](how-to-host.md).
|
|
9
|
+
- Landmarks: `Layout as="main"|"nav"|"section"`. Name a region with a visible heading (`aria-labelledby`) or `aria-label` when there is no heading. `NavigationSide` is chrome (`<aside>`, or `<header>` when `mobile`) — not a `nav`. When `mobile`, the menu opens a right `Sheet` that already contains the same `NavigationSideGroup` landmarks. `NavigationSideGroup` is already a named `<nav>` — do not wrap it in another `nav`. Pass `aria-label` when more than one group is on the page. SSR hosts must pass `mobile` on `NavigationSide` (`true` or `false`); omitting it hydrates the rail on the server and can swap to the top bar on the client.
|
|
10
|
+
- Native controls: `Button`, `Link`, catalog fields — not clickable `div`s.
|
|
11
|
+
- Visible label first. `aria-label` only when there is no visible name (icon-only `IconButton`, unlabeled `ButtonGroup`).
|
|
12
|
+
- Decorative `Icon` stays default-hidden. Meaningful icons: `role="img"` + `aria-label`.
|
|
13
|
+
- Color is not the only status signal (pair with text or icon).
|
|
14
|
+
- Keyboard: no custom traps (`Dialog` / `Sheet` already trap). DOM order = tab order. No positive `tabindex`. Do not remove or restyle catalog `:focus-visible` rings.
|
|
15
|
+
- Pointer targets: use catalog controls (they meet 24×24 CSS px). Do not shrink hit areas with custom CSS.
|
|
16
|
+
- Decorative motion (`DynamicAngle*`, transitions) is muted under `prefers-reduced-motion: reduce`. Do not add app animation that ignores it.
|
|
17
|
+
- Injected status: `role="status"` or `aria-live="polite"`. Field errors: `FieldMessage` `Danger` (catalog fields wire `aria-invalid` / `aria-errormessage`).
|
|
18
|
+
- `ButtonGroup` / `RadioGroup` / `CheckboxGroup`: visible legend or `aria-label`.
|
|
19
|
+
|
|
20
|
+
## ARIA helpers
|
|
21
|
+
|
|
22
|
+
- `aria-label` — accessible name when no visible text (keep short; do not duplicate visible copy).
|
|
23
|
+
- `aria-labelledby` — id(s) of a visible heading/label that name this region or control.
|
|
24
|
+
- `aria-describedby` — extra help, not the name (`fieldDescription`, instructions).
|
|
25
|
+
- `aria-hidden="true"` — decorative / redundant graphics (default `Icon`).
|
|
26
|
+
- `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).
|
|
28
|
+
- `aria-expanded` — only on a real disclosure control, not on every section.
|
|
29
|
+
- `aria-pressed` — toggle buttons (`IconButton` `activated`); `Switch` uses `checked` instead.
|
|
30
|
+
|
|
31
|
+
Do not invent roles on `Layout` when `as` already maps (`main`, `nav`, `section`). Do not `aria-label` headings or buttons that already have visible text.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# How to: color and the color switcher
|
|
2
|
+
|
|
3
|
+
uxElle color is applied by **palette**, not by hardcoded values. Style every
|
|
4
|
+
themeable surface with the `--uxl-color-switcher-*` role tokens (the role table is
|
|
5
|
+
in [tokens.md](tokens.md)); choose which palette those roles resolve to with
|
|
6
|
+
`data-color-switcher`.
|
|
7
|
+
|
|
8
|
+
## Mechanism
|
|
9
|
+
|
|
10
|
+
`data-color-switcher="<palette>"` on a region remaps the `--uxl-color-switcher-*`
|
|
11
|
+
roles for that subtree; nested components inherit from the nearest ancestor.
|
|
12
|
+
|
|
13
|
+
- Set it on a **region** — a nav band, hero, section band, banner, or badge — not
|
|
14
|
+
on `<html>`. The host owns the page-level default (`default`).
|
|
15
|
+
- Some components take a `dataColorSwitcher` prop *or* the `data-color-switcher`
|
|
16
|
+
attribute (e.g. `LabelBadge`, `BannerAnnouncement`, `Button`, `DynamicAngle*`).
|
|
17
|
+
The attribute wins when both are set; for buttons rendered into another
|
|
18
|
+
component's slot, pass the prop so the palette reaches the DOM.
|
|
19
|
+
- Always style with role tokens inside the region. Never hardcode hex — the palette
|
|
20
|
+
guarantees text/headline contrast on its own surface.
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
// A brand band: one region, one palette, role tokens throughout.
|
|
24
|
+
<Layout
|
|
25
|
+
as="section"
|
|
26
|
+
data-color-switcher="brand-1"
|
|
27
|
+
p="var(--uxl-theme-layout-spacing-large-13)"
|
|
28
|
+
style={{
|
|
29
|
+
backgroundColor: "var(--uxl-color-switcher-background)",
|
|
30
|
+
color: "var(--uxl-color-switcher-text)",
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
{/* Text + Button inside inherit this palette */}
|
|
34
|
+
</Layout>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Palette catalog
|
|
38
|
+
|
|
39
|
+
- **`default`** — the page baseline: neutral surface and text. The default look
|
|
40
|
+
for product surfaces.
|
|
41
|
+
- **`default-subtle`** — a low-contrast neutral fill to separate a section, well,
|
|
42
|
+
or sidebar *without* introducing brand color (alternating sections, cards on a
|
|
43
|
+
neutral page).
|
|
44
|
+
- **`neutral`** — an internal subtle neutral used by some components (e.g. file
|
|
45
|
+
lists). Component-owned; you rarely set it by hand.
|
|
46
|
+
- **`success` / `warning` / `danger` / `info`** — semantic **status** only, on
|
|
47
|
+
`Banner`, `BannerAnnouncement`, `LabelBadge`, and inline feedback (`Textarea`
|
|
48
|
+
`softOverflow` uses `data-color-switcher="warning"` on the counter; pass
|
|
49
|
+
`softOverflowMessage` for a warning `FieldMessage`). Never
|
|
50
|
+
decorative, and never the only signal: pair the color with text or an icon.
|
|
51
|
+
- **`brand-1` / `brand-2` / `brand-3` / `brand-4`** — the expressive, high-impact
|
|
52
|
+
palettes. See below.
|
|
53
|
+
|
|
54
|
+
## Choosing a brand palette
|
|
55
|
+
|
|
56
|
+
`brand-1..4` are the high-impact set, meant for high-impact components — heroes,
|
|
57
|
+
CTA bands, and loud statements in general. They have **no priority order and no
|
|
58
|
+
fixed per-index meaning**: which hue each maps to is theme-owned and differs across
|
|
59
|
+
platforms and themes, so never assume "brand-1 = red / primary" or
|
|
60
|
+
"brand-2 = informational."
|
|
61
|
+
|
|
62
|
+
To choose:
|
|
63
|
+
|
|
64
|
+
- Reach for a brand palette when a region should make a statement (hero, CTA,
|
|
65
|
+
standout feature band); keep product task surfaces on `default` / `default-subtle`.
|
|
66
|
+
- Pick whichever palette **renders** the look you want, and confirm the rendered
|
|
67
|
+
output (e.g. in Storybook) rather than assuming a color.
|
|
68
|
+
- Use **one dominant brand band per view** and let `default` / `default-subtle`
|
|
69
|
+
carry the calmer regions, so the loud band stays loud.
|
|
70
|
+
|
|
71
|
+
## Do / don't
|
|
72
|
+
|
|
73
|
+
- Do apply a palette on the region, and style children with role tokens.
|
|
74
|
+
- Do keep status palettes for real status, paired with text or an icon.
|
|
75
|
+
- Don't set `data-color-switcher` on `<html>` or invent palette names.
|
|
76
|
+
- Don't hardcode hex/rgb, and don't stack multiple loud brand bands in one view.
|
|
77
|
+
|
|
78
|
+
## A2UI
|
|
79
|
+
|
|
80
|
+
Set the palette through the adapter where a prop exists (e.g. `A2uiBanner` status,
|
|
81
|
+
`A2uiLabelBadge`); otherwise wrap the region in one `A2uiLayout` with the switcher.
|
|
82
|
+
One palette per region, same rules as above.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# How to: host (React app entry)
|
|
2
|
+
|
|
3
|
+
Use this when **creating** a React app shell — the same shell serves product and
|
|
4
|
+
marketing surfaces. If the host (an MCP runtime or existing app) already loads
|
|
5
|
+
theme CSS, component CSS, and `data-*` on `<html>`, skip this file and compose
|
|
6
|
+
pages inside it.
|
|
7
|
+
|
|
8
|
+
Agents consume **published packages**, not repo filesystem paths. Do not import
|
|
9
|
+
`packages/themes/build/…` or invent Vite aliases to source.
|
|
10
|
+
|
|
11
|
+
## Load CSS
|
|
12
|
+
|
|
13
|
+
Theme CSS is a **placeholder import** — use the theme id the host documents.
|
|
14
|
+
Open-source example:
|
|
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).
|
|
31
|
+
|
|
32
|
+
On `<html>`:
|
|
33
|
+
|
|
34
|
+
- Theme mode (host-owned): `data-open-source="light" | "dark"` (other themes use
|
|
35
|
+
`data-<theme-id>`).
|
|
36
|
+
- Color switcher default: `data-color-switcher="default"` on `<html>`. Apply other
|
|
37
|
+
palettes per region, not here ([how-to-color.md](how-to-color.md)).
|
|
38
|
+
|
|
39
|
+
Optional: `UXelleThemeProvider` from `@uxelle/themes/react` with
|
|
40
|
+
`theme="open-source"` keeps those attributes in sync.
|
|
41
|
+
|
|
42
|
+
## Skip link
|
|
43
|
+
|
|
44
|
+
First focusable node in the DOM, targeting `id="main-content"` on `<main>`:
|
|
45
|
+
|
|
46
|
+
```css
|
|
47
|
+
.uxl-skip-link {
|
|
48
|
+
position: absolute;
|
|
49
|
+
z-index: 100;
|
|
50
|
+
inset-inline-start: var(--uxl-theme-layout-spacing-small-4);
|
|
51
|
+
inset-block-start: var(--uxl-theme-layout-spacing-small-4);
|
|
52
|
+
padding: var(--uxl-theme-layout-spacing-small-4);
|
|
53
|
+
background-color: var(--uxl-color-switcher-background);
|
|
54
|
+
color: var(--uxl-color-switcher-text);
|
|
55
|
+
text-decoration: underline;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.uxl-skip-link:not(:focus-visible) {
|
|
59
|
+
width: 1px;
|
|
60
|
+
height: 1px;
|
|
61
|
+
padding: 0;
|
|
62
|
+
margin: -1px;
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
clip: rect(0, 0, 0, 0);
|
|
65
|
+
white-space: nowrap;
|
|
66
|
+
border: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.uxl-skip-link:focus-visible {
|
|
70
|
+
outline: 2px solid var(--uxl-color-switcher-interactive-icon);
|
|
71
|
+
outline-offset: 2px;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## React entry
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<html data-open-source="light" data-color-switcher="default">
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<Layout
|
|
83
|
+
display="flex"
|
|
84
|
+
flexDirection="column"
|
|
85
|
+
height="100%"
|
|
86
|
+
style={{
|
|
87
|
+
backgroundColor: "var(--uxl-color-switcher-background)",
|
|
88
|
+
overflow: "hidden",
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<a className="uxl-skip-link" href="#main-content">
|
|
92
|
+
Skip to main content
|
|
93
|
+
</a>
|
|
94
|
+
{/* Navigation — recipe-app-chrome.md */}
|
|
95
|
+
<main id="main-content" tabIndex={-1} style={{ flexGrow: 1, minHeight: 0, overflow: "auto" }}>
|
|
96
|
+
{/* page column — how-to-page-layout.md */}
|
|
97
|
+
{/* Footer — inside this scrollport; recipe-app-chrome.md */}
|
|
98
|
+
</main>
|
|
99
|
+
</Layout>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Set `html, body, #root { height: 100%; margin: 0; }` so the column can fill the
|
|
103
|
+
viewport.
|
|
104
|
+
|
|
105
|
+
## A2UI hosts
|
|
106
|
+
|
|
107
|
+
A2UI runtimes load the same theme CSS and wrap content in `A2uiThemeScope`; they do
|
|
108
|
+
**not** emit skip-link CSS or theme `data-*` from adapters. See [a2ui.md](a2ui.md).
|