@uxelle/skills 0.2.3 → 0.2.4

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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/manifest.generated.ts
2
2
  var manifest = {
3
3
  "schemaVersion": 1,
4
- "version": "0.2.3",
4
+ "version": "0.2.4",
5
5
  "skills": [
6
6
  {
7
7
  "name": "uxelle-components",
@@ -249,8 +249,8 @@ var manifest = {
249
249
  },
250
250
  {
251
251
  "path": "Navigation.md",
252
- "size": 2626,
253
- "hash": "sha256:ed38f63fc2728c5de857bcc891807c628103e30b7381e4b4b3e7576980b672e4"
252
+ "size": 3749,
253
+ "hash": "sha256:d51bf6f799deda23afae3b13a60e38d90b8a8069be1eedd05c5c6ec5d6d806ad"
254
254
  },
255
255
  {
256
256
  "path": "NavigationSide.md",
@@ -274,8 +274,8 @@ var manifest = {
274
274
  },
275
275
  {
276
276
  "path": "NavLink.md",
277
- "size": 2567,
278
- "hash": "sha256:46d020ef5c1a46874e8fb89df9af5c96b0a2cc9b778fe8d64ceaaa1522df8047"
277
+ "size": 2765,
278
+ "hash": "sha256:6d8d696c1066b536cb3a36a145f316968bef40879b265a8d5fac06fbeaf9d69b"
279
279
  },
280
280
  {
281
281
  "path": "NotificationBadge.md",
@@ -436,12 +436,12 @@ var manifest = {
436
436
  {
437
437
  "path": "how-to-host.md",
438
438
  "size": 2856,
439
- "hash": "sha256:05187f27fb791bc18c613354635d5d68a044454c166dfce0925116adca3a6cba"
439
+ "hash": "sha256:dab3dc61a74b069600d56940f4b2b5e9210f2899ab5688613fbbbfdc035c60ba"
440
440
  },
441
441
  {
442
442
  "path": "how-to-page-layout.md",
443
- "size": 10962,
444
- "hash": "sha256:b01626a0661950a5bbb9a7def38e82b6b3ce9ded32a328edd72aa5c256588a84"
443
+ "size": 11126,
444
+ "hash": "sha256:2d06c39afbe035eb13bace7c744f7d19a2130248eb574a8b11f2bf179f83fa67"
445
445
  },
446
446
  {
447
447
  "path": "principles.md",
@@ -450,8 +450,8 @@ var manifest = {
450
450
  },
451
451
  {
452
452
  "path": "recipe-app-chrome.md",
453
- "size": 10042,
454
- "hash": "sha256:225ebbfabfa65538c9275744c4cc6e5cc7798a36ab496643b41283efae3ea1a3"
453
+ "size": 12052,
454
+ "hash": "sha256:f4a1380400b4ea54a70dd8d77bb800d37fdf90240e1aaecd5aafad67730730bb"
455
455
  },
456
456
  {
457
457
  "path": "recipe-card-grid.md",
@@ -508,6 +508,11 @@ var manifest = {
508
508
  "size": 3876,
509
509
  "hash": "sha256:351069ddf1e5d085d9f1a89caa47c368874b5d15d4cb9a53e9e1691c3df44a14"
510
510
  },
511
+ {
512
+ "path": "recipe-navigation.md",
513
+ "size": 17612,
514
+ "hash": "sha256:37dde1f17bb34f12b9e53a930f336a7beafbb4ae9fc237bcff02a2b37018f922"
515
+ },
511
516
  {
512
517
  "path": "recipe-page-header.md",
513
518
  "size": 4161,
@@ -565,8 +570,8 @@ var manifest = {
565
570
  },
566
571
  {
567
572
  "path": "SKILL.md",
568
- "size": 10810,
569
- "hash": "sha256:096a85cb85d2fe413bc5f2abb241c45ea6e59dfc3b9547a00b639c7739fc014e"
573
+ "size": 11032,
574
+ "hash": "sha256:75b96a393abccffd6c6292a22bbaf88f402fc68edbcd9326f7d099ea0d575d48"
570
575
  },
571
576
  {
572
577
  "path": "spacing-steps.md",
package/index.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "skills": [
5
5
  {
6
6
  "name": "uxelle-components",
@@ -248,8 +248,8 @@
248
248
  },
249
249
  {
250
250
  "path": "Navigation.md",
251
- "size": 2626,
252
- "hash": "sha256:ed38f63fc2728c5de857bcc891807c628103e30b7381e4b4b3e7576980b672e4"
251
+ "size": 3749,
252
+ "hash": "sha256:d51bf6f799deda23afae3b13a60e38d90b8a8069be1eedd05c5c6ec5d6d806ad"
253
253
  },
254
254
  {
255
255
  "path": "NavigationSide.md",
@@ -273,8 +273,8 @@
273
273
  },
274
274
  {
275
275
  "path": "NavLink.md",
276
- "size": 2567,
277
- "hash": "sha256:46d020ef5c1a46874e8fb89df9af5c96b0a2cc9b778fe8d64ceaaa1522df8047"
276
+ "size": 2765,
277
+ "hash": "sha256:6d8d696c1066b536cb3a36a145f316968bef40879b265a8d5fac06fbeaf9d69b"
278
278
  },
279
279
  {
280
280
  "path": "NotificationBadge.md",
@@ -435,12 +435,12 @@
435
435
  {
436
436
  "path": "how-to-host.md",
437
437
  "size": 2856,
438
- "hash": "sha256:05187f27fb791bc18c613354635d5d68a044454c166dfce0925116adca3a6cba"
438
+ "hash": "sha256:dab3dc61a74b069600d56940f4b2b5e9210f2899ab5688613fbbbfdc035c60ba"
439
439
  },
440
440
  {
441
441
  "path": "how-to-page-layout.md",
442
- "size": 10962,
443
- "hash": "sha256:b01626a0661950a5bbb9a7def38e82b6b3ce9ded32a328edd72aa5c256588a84"
442
+ "size": 11126,
443
+ "hash": "sha256:2d06c39afbe035eb13bace7c744f7d19a2130248eb574a8b11f2bf179f83fa67"
444
444
  },
445
445
  {
446
446
  "path": "principles.md",
@@ -449,8 +449,8 @@
449
449
  },
450
450
  {
451
451
  "path": "recipe-app-chrome.md",
452
- "size": 10042,
453
- "hash": "sha256:225ebbfabfa65538c9275744c4cc6e5cc7798a36ab496643b41283efae3ea1a3"
452
+ "size": 12052,
453
+ "hash": "sha256:f4a1380400b4ea54a70dd8d77bb800d37fdf90240e1aaecd5aafad67730730bb"
454
454
  },
455
455
  {
456
456
  "path": "recipe-card-grid.md",
@@ -507,6 +507,11 @@
507
507
  "size": 3876,
508
508
  "hash": "sha256:351069ddf1e5d085d9f1a89caa47c368874b5d15d4cb9a53e9e1691c3df44a14"
509
509
  },
510
+ {
511
+ "path": "recipe-navigation.md",
512
+ "size": 17612,
513
+ "hash": "sha256:37dde1f17bb34f12b9e53a930f336a7beafbb4ae9fc237bcff02a2b37018f922"
514
+ },
510
515
  {
511
516
  "path": "recipe-page-header.md",
512
517
  "size": 4161,
@@ -564,8 +569,8 @@
564
569
  },
565
570
  {
566
571
  "path": "SKILL.md",
567
- "size": 10810,
568
- "hash": "sha256:096a85cb85d2fe413bc5f2abb241c45ea6e59dfc3b9547a00b639c7739fc014e"
572
+ "size": 11032,
573
+ "hash": "sha256:75b96a393abccffd6c6292a22bbaf88f402fc68edbcd9326f7d099ea0d575d48"
569
574
  },
570
575
  {
571
576
  "path": "spacing-steps.md",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxelle/skills",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "uxElle generate-UI agent skills: component API reference and design harness, as versioned markdown with a machine-readable manifest",
5
5
  "type": "module",
6
6
  "repository": {
@@ -17,7 +17,7 @@ Primary or secondary navigation item with Navigation Link typography.
17
17
  | trailingIconName | `string` | `"keyboard_arrow_down"` | Material Symbol name for the trailing icon. Ignored when `trailingIcon` is false, and overridden by the menu chevron when `withMenu` is true. |
18
18
  | iconVariant | `"sharpUnfilled" \| "sharpFilled"` | `"sharpUnfilled"` | Fill style shared by both icons: `sharpUnfilled` for the outline glyph, `sharpFilled` for the solid one. |
19
19
  | navigationLevel | `"Primary" \| "Secondary"` | `"Primary"` | Which `Navigation` band the link sits in, so its colors match that surface: `Primary` for the main bar, `Secondary` for the sub-navigation bar. |
20
- | withMenu | `boolean` | `false` | Wraps the link in a `Menu` that opens `children` as a dropdown panel. The trigger stays a button (so `href` is ignored) and always shows a trailing `keyboard_arrow_down` that rotates 180° when open. |
20
+ | withMenu | `boolean` | `false` | Wraps the link in a `Menu` (`pattern="menu"`, Tab dismisses) for overflow labeled More — not destination panels (`Menu` `pattern="disclosure"` with a `NavLink` trigger instead). The trigger stays a button (so `href` is ignored) and always shows a trailing `keyboard_arrow_down` that rotates 180° when open. |
21
21
  | href | `string` | `undefined` | Destination URL, which renders the link as an anchor instead of a button. Ignored when `withMenu` is true. |
22
22
  | children | `ReactNode` | `undefined` | Dropdown panel content — typically a `List` of `ListItem`s — shown when `withMenu` is true. Ignored otherwise; the visible text comes from `label`. |
23
23
  | className | `string` | `—` | |
@@ -40,5 +40,5 @@ Primary or secondary navigation item with Navigation Link typography.
40
40
  ## Notes
41
41
 
42
42
  - Renders as an anchor when `href` is set; otherwise a button.
43
- - Set `withMenu` and pass panel `children` for dropdown overflow items. `href` is ignored while `withMenu` is true so the trigger stays a button. A trailing `keyboard_arrow_down` icon is shown and rotates 180° when open.
43
+ - Set `withMenu` for overflow items labeled More. Destination panels use `Menu` `pattern="disclosure"` with a `NavLink` trigger (no `href`) so Tab walks links. `href` is ignored while `withMenu` is true so the trigger stays a button. A trailing `keyboard_arrow_down` icon is shown and rotates 180° when open.
44
44
  - Use `navigationLevel="Secondary"` in the secondary nav band so colors follow that surface.
@@ -10,10 +10,10 @@ Two-tier navigation bar for enterprise applications.
10
10
 
11
11
  | Prop | Type | Default | Description |
12
12
  |------|------|---------|-------------|
13
- | leadingSlot | `ReactNode \| null` | `undefined` | Start of the primary bar, typically a menu `IconButton`, a **Logo**, and a title lockup. Pass `null` to drop the region; omit to render the built-in placeholder branding. |
14
- | centerSlot | `ReactNode \| null` | `undefined` | Middle of the primary bar between the leading and trailing regions — typically a row of `NavLink`s. Pass `null` to drop the region; omit to render placeholder links. |
15
- | trailingSlot | `ReactNode \| null` | `undefined` | End of the primary bar, for utilities such as a search `IconButton`, a `LanguageSelector`, and a log-in `Button`. Pass `null` to drop the region; omit for the built-in utility set. |
16
- | bottomSlot | `ReactNode \| null` | `undefined` | Contents of the secondary bar below the primary bar, typically `NavLink`s with `navigationLevel="Secondary"`. Pass `null` to drop the secondary bar entirely; omit to render placeholder links. |
13
+ | leadingSlot | `ReactNode \| null` | `undefined` | Start of the primary bar. Slots are open — any node is valid. Generation default: a **Logo** home control plus a two-line lockup (`Text` Condensed Alt for the site name, optional Condensed for location or a short descriptor). The compact (below-desktop) menu `IconButton` does not belong here unless asked. Pass `null` to drop the region; omit to render the built-in placeholder branding. |
14
+ | centerSlot | `ReactNode \| null` | `undefined` | Middle of the primary bar. Slots are open. Generation default: `null` (destinations live in a trailing **Sheet**). Fill from desktop up with **Menu** `pattern="disclosure"` category triggers when the IA is two or more levels deep (`NavLink withMenu` is overflow More only). Pass `null` to leave the middle empty (blank space between leading and trailing); omit to render placeholder links. |
15
+ | trailingSlot | `ReactNode \| null` | `undefined` | End of the primary bar. Slots are open. Generation default: utilities (search, session) plus, below desktop, the menu `IconButton` that opens a **Sheet** from this side. Pass `null` to drop the region; omit for the built-in utility set. |
16
+ | bottomSlot | `ReactNode \| null` | `undefined` | Contents of the secondary bar. Generation default: `null` unless a persistent L2 module bar is needed (`NavLink`s with `navigationLevel="Secondary"`). Pass `null` to drop the secondary bar entirely; omit to render placeholder links. |
17
17
 
18
18
  <!-- prettier-ignore-end -->
19
19
 
@@ -22,40 +22,50 @@ Extends `HTMLAttributes` — supports standard HTML attributes.
22
22
  ## Usage
23
23
 
24
24
  ```tsx
25
+ const [menuOpen, setMenuOpen] = useState(false);
26
+
25
27
  <Navigation
26
28
  leadingSlot={
27
29
  <>
28
- <IconButton emphasis="low" size="medium" iconName="menu" iconVariant="sharpUnfilled" />
29
- <Logo name="generic" className="uxl-navigation__logo" width={48} aria-hidden />
30
- <Text type="Condensed Alt" truncation>Votre espace dédié</Text>
31
- </>
32
- }
33
- centerSlot={
34
- <>
35
- <NavLink label="Menu Link" href="#" />
36
- <NavLink label="Menu Link" href="#" />
30
+ <a href="/" aria-label="Home">
31
+ <Logo name="generic" width={48} interactive aria-hidden />
32
+ </a>
33
+ <Layout display="flex" flexDirection="column" gap={0}>
34
+ <Text type="Condensed Alt" truncation width={false}>Crop Science</Text>
35
+ <Text type="Condensed" truncation width={false}>United States</Text>
36
+ </Layout>
37
37
  </>
38
38
  }
39
+ centerSlot={null}
39
40
  trailingSlot={
40
41
  <>
41
42
  <IconButton iconName="search" aria-label="Search" emphasis="low" size="small" />
42
43
  <LanguageSelector value="EN" />
43
- <Divider orientation="vertical" emphasis="low" />
44
- <Button emphasis="low" size="small" trailingIcon trailingIconName="keyboard_arrow_down" trailingIconVariant="sharpUnfilled">Log In</Button>
45
- </>
46
- }
47
- bottomSlot={
48
- <>
49
- <NavLink label="Menu Link" href="#" navigationLevel="Secondary" />
50
- <NavLink label="Menu Link" href="#" navigationLevel="Secondary" />
44
+ <Button emphasis="low" size="small">Log In</Button>
45
+ <Sheet
46
+ open={menuOpen}
47
+ onOpenChange={setMenuOpen}
48
+ direction="Right"
49
+ title=""
50
+ aria-label="Menu"
51
+ trigger={
52
+ <IconButton emphasis="low" size="small" iconName="menu" iconVariant="sharpUnfilled" aria-label="Menu" />
53
+ }
54
+ >
55
+ <List>
56
+ <ListItem interactive href="/about" centerText="About" bottomText="" onClick={() => setMenuOpen(false)} />
57
+ </List>
58
+ </Sheet>
51
59
  </>
52
60
  }
61
+ bottomSlot={null}
53
62
  />
54
63
  ```
55
64
 
56
65
 
57
66
  ## Notes
58
67
 
59
- - **Primary nav**: Leading, center, and trailing regions. Use slots for branding, main links, and utilities.
68
+ - **Slots are open**: any node may go in `leadingSlot`, `centerSlot`, `trailingSlot`, or `bottomSlot`.
69
+ - **Generation defaults** (overridable): lockup in leading (Logo as home, Condensed Alt name, optional Condensed location or descriptor), `centerSlot` `null` unless the IA is two or more levels deep, compact menu `IconButton` in `trailingSlot` below desktop opening a **Sheet** from that same side (`open` / `onOpenChange` to close after a leaf). Omit a slot for the built-in placeholder. Pass `null` to drop leading, trailing, or the secondary bar; `centerSlot={null}` leaves blank space between leading and trailing.
70
+ - **Primary nav**: Leading, center, and trailing regions.
60
71
  - **Secondary nav**: Optional bottom bar for sub-navigation.
61
- - **Slots**: Pass `leadingSlot`, `centerSlot`, `trailingSlot`, and `bottomSlot` to customize each region. Omit a slot for the built-in placeholder; pass `null` to drop that region.
@@ -77,6 +77,7 @@ palette. The full mapping of principles to uxElle mechanisms lives in
77
77
  card row -> [recipe-card-grid.md](recipe-card-grid.md);
78
78
  metrics -> [recipe-stat-callouts.md](recipe-stat-callouts.md);
79
79
  empty/loading/error -> [recipe-states.md](recipe-states.md);
80
+ header navigation -> [recipe-navigation.md](recipe-navigation.md);
80
81
  page footer -> [recipe-footer.md](recipe-footer.md);
81
82
  hero -> [recipe-hero.md](recipe-hero.md); feature -> [recipe-feature-section.md](recipe-feature-section.md);
82
83
  trust bar -> [recipe-logo-wall.md](recipe-logo-wall.md); testimonial -> [recipe-testimonial.md](recipe-testimonial.md);
@@ -136,7 +137,9 @@ Every generated screen must pass this. It is the definition of done.
136
137
  a type (an `h1` and `h2` both on `Display Extra Small` flatten the page), and
137
138
  headings inside a `Card` step down one stop ([density.md](density.md)).
138
139
  - **Responsive**: reflows mobile -> up with no px literals; clusters stack, `Table`
139
- scrolls, nav collapses; touch targets stay >= 24 CSS px.
140
+ scrolls, compact header menu sits in `Navigation` `trailingSlot` and opens a
141
+ `Sheet` from that side (panel interior matches IA depth); touch targets stay
142
+ >= 24 CSS px.
140
143
  - **Color discipline**: at most one dominant brand band per view; status palettes
141
144
  only for semantic status, never color-alone (paired with text or icon).
142
145
  - **Accessibility**: skip link + one `main`; landmarks named; focus visible; every
@@ -164,7 +167,8 @@ Marketing: [landing-page](recipe-landing-page.md).
164
167
  [form-section](recipe-form-section.md) · [query-bar](recipe-query-bar.md) ·
165
168
  [card-grid](recipe-card-grid.md) · [summary-list](recipe-summary-list.md) ·
166
169
  [states](recipe-states.md) · [stat-callouts](recipe-stat-callouts.md) ·
167
- [logo-wall](recipe-logo-wall.md) · [footer](recipe-footer.md). Marketing: [hero](recipe-hero.md) ·
170
+ [logo-wall](recipe-logo-wall.md) · [navigation](recipe-navigation.md) ·
171
+ [footer](recipe-footer.md). Marketing: [hero](recipe-hero.md) ·
168
172
  [feature-section](recipe-feature-section.md) · [testimonial](recipe-testimonial.md) ·
169
173
  [pricing](recipe-pricing.md) · [cta-band](recipe-cta-band.md).
170
174
 
@@ -78,7 +78,7 @@ First focusable node in the DOM, targeting `id="main-content"` on `<main>`:
78
78
  <a className="uxl-skip-link" href="#main-content">
79
79
  Skip to main content
80
80
  </a>
81
- {/* Navigation — recipe-app-chrome.md */}
81
+ {/* Navigation — recipe-navigation.md */}
82
82
  <main id="main-content" tabIndex={-1} style={{ flexGrow: 1, minHeight: 0, overflow: "auto" }}>
83
83
  {/* page column — how-to-page-layout.md */}
84
84
  {/* Footer — inside this scrollport; recipe-app-chrome.md */}
@@ -184,8 +184,10 @@ Bands: `tablet` (from `--uxl-theme-layout-tablet-screen-width-min`), `desktop`,
184
184
  when a component needs an explicit count.
185
185
  - **Header / query clusters**: title over actions, search full-width with
186
186
  filters/export below, when narrow; one row when wide (see below).
187
- - **Navigation**: collapse to an `IconButton` menu opening a `Sheet` / `Menu`
188
- drawer when narrow; verify `Navigation`'s built-in behavior first.
187
+ - **Navigation**: below desktop the menu `IconButton` defaults to `Navigation`
188
+ `trailingSlot` and opens a `Sheet` from that same side (`useBreakpointUp("desktop")`,
189
+ not the tablet band). From desktop up it may live in any slot. Destinations
190
+ follow IA depth ([recipe-navigation.md](recipe-navigation.md)).
189
191
  - **Tables**: `Table` scrolls horizontally when narrow (no stacked-row API); use
190
192
  `PaginationSimple` where full `Pagination` is too wide.
191
193
  - **Hero / media**: `Hero` `contentDirection` handles narrow stacking; related-field
@@ -2,7 +2,10 @@
2
2
 
3
3
  ## When
4
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.
5
+ React app frame: skip link, [`Navigation`](../uxelle-components/Navigation.md)
6
+ (composition in [recipe-navigation.md](recipe-navigation.md)), scrolling `<main>`,
7
+ optional [`Footer`](../uxelle-components/Footer.md). Serves both product and
8
+ marketing shells.
6
9
 
7
10
  ## When not
8
11
 
@@ -17,7 +20,11 @@ React app frame: skip link, [`Navigation`](../uxelle-components/Navigation.md),
17
20
 
18
21
  ## Regions
19
22
 
20
- skip link → `Navigation` (header) and/or `NavigationSide` (side rail wrapping `NavigationSideGroup` rows) → `<main id="main-content">` → *the experience's* content column ([how-to-page-layout.md](how-to-page-layout.md#who-owns-the-content-column)) → optional `Footer` **inside that same `<main>`**
23
+ skip link → optional [`BannerAnnouncement`](../uxelle-components/BannerAnnouncement.md) → `Navigation` (header) and/or `NavigationSide` (side rail wrapping `NavigationSideGroup` rows) → `<main id="main-content">` → *the experience's* content column ([how-to-page-layout.md](how-to-page-layout.md#who-owns-the-content-column)) → optional `Footer` **inside that same `<main>`**
24
+
25
+ Pick the **chrome pattern** before filling header slots (marketing header, signed-in
26
+ side rail, hybrid portal, or focused lockup-only) —
27
+ [recipe-navigation.md](recipe-navigation.md#chrome-pattern).
21
28
 
22
29
  ## Spacing
23
30
 
@@ -29,10 +36,20 @@ Do not wrap `Footer` in extra `Layout` padding. Band padding is catalog-owned: t
29
36
 
30
37
  ## A11y
31
38
 
32
- [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`.
39
+ [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` beside a visible product name: `aria-hidden`; name the home control on the wrapping `<a>` (`aria-label="Home"`).
33
40
 
34
41
  `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.
35
42
 
43
+ ## Navigation
44
+
45
+ Header slot architecture — lockup (Logo as home, Condensed Alt name / Condensed
46
+ location or descriptor), empty `centerSlot` unless the IA is two or more levels
47
+ deep, compact hamburger in `trailingSlot` below desktop (omit it when
48
+ `NavigationSide` already owns the compact menu), Sheet `open` / `onOpenChange`
49
+ and `direction` matching the menu side, trailing search/session cluster,
50
+ IA-depth interiors — lives in [recipe-navigation.md](recipe-navigation.md). Pass
51
+ every slot explicitly (`null` to drop a region). Do not pass `false`.
52
+
36
53
  ## Side rail
37
54
 
38
55
  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.
@@ -47,6 +64,12 @@ Footer content architecture — the brand block, categorized link columns, the l
47
64
 
48
65
  Import from `@uxelle/components`. Put skip-link CSS in the host stylesheet.
49
66
 
67
+ Signed-in default: identity + utilities, **no header hamburger**. Destinations
68
+ and the compact menu live on `NavigationSide` ([Side rail](#side-rail)). Public
69
+ marketing chrome with a trailing Sheet is in
70
+ [recipe-navigation.md](recipe-navigation.md) and
71
+ [Help and marketing chrome](#help-and-marketing-chrome).
72
+
50
73
  ```tsx
51
74
  <Layout
52
75
  display="flex"
@@ -64,16 +87,25 @@ Import from `@uxelle/components`. Put skip-link CSS in the host stylesheet.
64
87
  <Navigation
65
88
  bottomSlot={null}
66
89
  leadingSlot={
67
- <Text type="Condensed Alt" truncation width={false}>
68
- Product name
69
- </Text>
70
- }
71
- centerSlot={
72
90
  <>
73
- <NavLink label="Home" href="/" aria-current="page" />
74
- <NavLink label="Settings" href="/settings" />
91
+ <a href="/" aria-label="Home">
92
+ <Logo name="generic" width={48} interactive aria-hidden />
93
+ </a>
94
+ <Layout
95
+ display="flex"
96
+ flexDirection="column"
97
+ gap={0}
98
+ >
99
+ <Text type="Condensed Alt" truncation width={false}>
100
+ Product name
101
+ </Text>
102
+ <Text type="Condensed" truncation width={false}>
103
+ Short descriptor
104
+ </Text>
105
+ </Layout>
75
106
  </>
76
107
  }
108
+ centerSlot={null}
77
109
  trailingSlot={
78
110
  <IconButton emphasis="low" size="small" iconName="search" aria-label="Search" />
79
111
  }
@@ -102,44 +134,89 @@ Import from `@uxelle/components`. Put skip-link CSS in the host stylesheet.
102
134
 
103
135
  ## Responsive
104
136
 
105
- - **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)).
106
- - **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).
137
+ - **Header navigation** — [recipe-navigation.md](recipe-navigation.md): below
138
+ desktop the visible menu control defaults to `trailingSlot` and opens a `Sheet`
139
+ from that same side. From desktop up the hamburger may live in any slot; omit
140
+ it when `centerSlot` is visible or when `NavigationSide` owns the compact menu.
141
+ When `centerSlot` holds category disclosures (two- or three-level IA), hide that
142
+ row below desktop (`useBreakpointUp("desktop")`). Trailing utilities stay a **row**
143
+ at every width — [how-to-page-layout.md](how-to-page-layout.md#action-clusters).
107
144
  - **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.
108
145
 
109
146
  ## A2UI
110
147
 
111
- 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).
148
+ 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")`. Header slot defaults for `A2uiNavigation`: [recipe-navigation.md](recipe-navigation.md). 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).
112
149
 
113
150
  ## Help and marketing chrome
114
151
 
115
- Use the same `Navigation` when there are **no** primary app links (help center, marketing). Pass `bottomSlot={null}` to hide the secondary bar. Unused **primary** slots must not be omitted — an omitted (`undefined`) `leadingSlot` / `centerSlot` / `trailingSlot` fills catalog demo content. Pass **`null`** to drop a slot entirely (`centerSlot={null}` when there is no center nav) — the same pattern as `bottomSlot={null}`. Do not pass `false`: it clears the content but still renders an empty slot wrapper in the flex row.
116
-
117
- Nav trailing (search, language, Log in / Register) stays a **row** at every width — [how-to-page-layout.md](how-to-page-layout.md#action-clusters). Do not stack those controls on mobile.
152
+ Use the same `Navigation` slot defaults as [recipe-navigation.md](recipe-navigation.md)
153
+ (help center, marketing). Pass `bottomSlot={null}` unless there is a persistent L2
154
+ bar. Unused **primary** slots must not be omitted — an omitted (`undefined`)
155
+ `leadingSlot` / `centerSlot` / `trailingSlot` fills catalog demo content. Pass
156
+ **`null`** to drop a slot. Do not pass `false`.
118
157
 
119
158
  `Breadcrumbs` belong in the **page column**, not in `Navigation`. Related articles are an in-page `Link` list under the article, not nav or crumbs.
120
159
 
160
+ Control the header Sheet with `open` / `onOpenChange` and close it from each leaf
161
+ `onClick` ([recipe-navigation.md](recipe-navigation.md)).
162
+
121
163
  ```tsx
164
+ const [menuOpen, setMenuOpen] = useState(false);
165
+
122
166
  <Navigation
123
167
  bottomSlot={null}
124
168
  leadingSlot={
125
- <a href="/" aria-label="Home">
126
- <Logo name="generic" width={40} interactive aria-hidden />
127
- </a>
169
+ <>
170
+ <a href="/" aria-label="Home">
171
+ <Logo name="generic" width={48} interactive aria-hidden />
172
+ </a>
173
+ <Layout
174
+ display="flex"
175
+ flexDirection="column"
176
+ gap={0}
177
+ >
178
+ <Text type="Condensed Alt" truncation width={false}>
179
+ Help center
180
+ </Text>
181
+ </Layout>
182
+ </>
128
183
  }
129
184
  centerSlot={null}
130
185
  trailingSlot={
131
186
  <>
132
187
  <LanguageSelector value="EN" />
133
188
  <Button emphasis="low" size="small">
134
- <Text type="Button" width>
135
- Log in
136
- </Text>
189
+ Log in
137
190
  </Button>
138
191
  <Button emphasis="high" size="small">
139
- <Text type="Button" width>
140
- Register
141
- </Text>
192
+ Register
142
193
  </Button>
194
+ <Sheet
195
+ open={menuOpen}
196
+ onOpenChange={setMenuOpen}
197
+ direction="Right"
198
+ title=""
199
+ aria-label="Menu"
200
+ trigger={
201
+ <IconButton
202
+ emphasis="low"
203
+ size="small"
204
+ iconName="menu"
205
+ iconVariant="sharpUnfilled"
206
+ aria-label="Menu"
207
+ />
208
+ }
209
+ >
210
+ <List>
211
+ <ListItem
212
+ interactive
213
+ href="/help"
214
+ centerText="Articles"
215
+ bottomText=""
216
+ onClick={() => setMenuOpen(false)}
217
+ />
218
+ </List>
219
+ </Sheet>
143
220
  </>
144
221
  }
145
222
  />
@@ -0,0 +1,433 @@
1
+ # Recipe: navigation
2
+
3
+ A shared **piece**: the header bar — brand lockup, optional category disclosures,
4
+ utilities, and a compact menu [`Sheet`](../uxelle-components/Sheet.md). Used by
5
+ [app-chrome](recipe-app-chrome.md) (product and marketing).
6
+
7
+ Composes [`Navigation`](../uxelle-components/Navigation.md),
8
+ [`Logo`](../uxelle-components/Logo.md), [`Text`](../uxelle-components/Text.md),
9
+ [`NavLink`](../uxelle-components/NavLink.md),
10
+ [`IconButton`](../uxelle-components/IconButton.md),
11
+ [`Sheet`](../uxelle-components/Sheet.md), [`Menu`](../uxelle-components/Menu.md),
12
+ [`List`](../uxelle-components/List.md), [`ListItem`](../uxelle-components/ListItem.md),
13
+ [`Layout`](../uxelle-components/Layout.md), [`Button`](../uxelle-components/Button.md),
14
+ and optionally [`Textfield`](../uxelle-components/Textfield.md),
15
+ [`LanguageSelector`](../uxelle-components/LanguageSelector.md),
16
+ [`Accordion`](../uxelle-components/Accordion.md),
17
+ [`NotificationBadge`](../uxelle-components/NotificationBadge.md), and
18
+ [`BannerAnnouncement`](../uxelle-components/BannerAnnouncement.md).
19
+
20
+ Slots are **open** — any node may live in any region. The defaults below are
21
+ generation defaults; a prompt may override them.
22
+
23
+ ## When
24
+
25
+ The top-of-page wayfinding for an experience that uses header `Navigation`. Place
26
+ it in the app frame above `<main>` ([app-chrome](recipe-app-chrome.md)).
27
+
28
+ ## When not
29
+
30
+ - Not an `AppChrome` / `NavigationBar` component to export from the customer app.
31
+ - Signed-in product destinations belong on
32
+ [`NavigationSide`](../uxelle-components/NavigationSide.md) — do not also put the
33
+ full IA in a header mega menu or a second hamburger. The header then holds
34
+ identity + utilities only.
35
+ - Focused flows (login, checkout, wizard): lockup only — no hamburger, no center
36
+ row, no footer.
37
+ - Breadcrumbs, table search, and data tabs stay in the **page column**
38
+ ([recipe-data-table-page.md](recipe-data-table-page.md)), not in `Navigation`.
39
+ - A zip / region / “add store” strip above the bar is `BannerAnnouncement`, not a
40
+ fourth Navigation slot.
41
+ - Pass real slot content; omitting a slot (`undefined`) fills catalog demo
42
+ content. Pass **`null`** to drop a region. Do not pass `false` (empty wrapper
43
+ remains).
44
+
45
+ ## Chrome pattern
46
+
47
+ Choose a frame **before** filling slots. Stacking every pattern is the usual miss.
48
+
49
+ - **Marketing / public** — header `Navigation` only (lockup + Sheet or mega menu).
50
+ This recipe’s default.
51
+ - **Signed-in product** — `NavigationSide` owns destinations; the header is
52
+ identity + utilities (search, account).
53
+ - **Hybrid portal** — lockup + hamburger Sheet, little or no center row, even on
54
+ desktop.
55
+ - **Focused flow** — lockup only; `centerSlot={null}`, no menu control.
56
+
57
+ ## Regions
58
+
59
+ `leadingSlot` (brand lockup) · `centerSlot` (desktop category disclosures when
60
+ the IA is two or more levels deep; otherwise `null`, which leaves blank space
61
+ between leading and trailing) · `trailingSlot` (utilities + compact hamburger) ·
62
+ `bottomSlot` (`null` unless a persistent L2 module bar).
63
+
64
+ Optional `BannerAnnouncement` sits **above** `Navigation`, not in a slot.
65
+
66
+ ## Lockup (`leadingSlot`)
67
+
68
+ - [`Logo`](../uxelle-components/Logo.md) when the experience has a mark
69
+ (`width={48}`). **Home is the mark**, not the whole lockup: wrap only `Logo` in
70
+ a plain `<a href="/" aria-label="Home">` and set `interactive`. Keep the mark
71
+ `aria-hidden` when title text is present.
72
+ - Two-line type — **no extra gap** (`gap={0}`). Condensed line boxes already
73
+ separate the lines; do not add Tight / `micro-2` between them
74
+ ([recipe-data-table-page.md](recipe-data-table-page.md) identity cells):
75
+ - Top — `Text type="Condensed Alt"` `truncation`: the **site or experience
76
+ name**.
77
+ - Bottom — `Text type="Condensed"`: **location or a short descriptor**. Omit
78
+ the second line when there is nothing to say.
79
+ - Do not use Display or Body types in the lockup.
80
+ - Do **not** wrap the lockup in [`Link`](../uxelle-components/Link.md) or nest the
81
+ title in `<a>` / `uxl-link`. `Link` underlines at rest — that underline on
82
+ Condensed lockup text looks wrong.
83
+ - No mark → do not turn the title into a link just to get a home control.
84
+ - Do not also add a “Home” `NavLink` or Sheet row when the logo already goes home.
85
+ - The lockup is not the page `h1`.
86
+
87
+ ## Center and overflow
88
+
89
+ - **One-level IA:** `centerSlot={null}`. Destinations live in the Sheet.
90
+ - **Two or more levels, from desktop up:** fill `centerSlot` with top-level
91
+ category controls that **click** open a disclosure panel (see
92
+ [IA depth](#ia-depth-what-is-inside-the-menu)). Hide that row below desktop.
93
+ Never show `centerSlot` and the compact hamburger together.
94
+ - About five to seven L1 items, then a `NavLink withMenu` labeled More
95
+ (overflow only). Do not wrap an unbounded row of categories.
96
+
97
+ ## Menu control and Sheet
98
+
99
+ [`IconButton`](../uxelle-components/IconButton.md) `iconName="menu"`
100
+ `iconVariant="sharpUnfilled"` `emphasis="low"` `aria-label="Menu"` opens a
101
+ `Sheet` whose body is the destination tree.
102
+
103
+ Control the Sheet with `open` / `onOpenChange` (same idea as `NavigationSide`
104
+ `menuOpen` / `onMenuOpenChange`). Close it after a leaf: `onClick={() =>
105
+ setMenuOpen(false)}` on each destination `ListItem`. An uncontrolled Sheet
106
+ stays open after in-app navigation.
107
+
108
+ - **From desktop up:** the menu control may live in **any slot**. For two- and
109
+ three-level IA, omit it while `centerSlot` is visible — the compact control
110
+ is for below desktop.
111
+ - **Below desktop:** the **visible** menu control defaults to **`trailingSlot`**
112
+ (right edge). If a desktop menu is in `leadingSlot`, hide that instance below
113
+ desktop and show a trailing one — one visible hamburger per breakpoint, not
114
+ two. Switch with `useBreakpointUp("desktop")` (the band catalog chrome uses).
115
+ Do not use the tablet band for this — tablet would keep L1 links and the
116
+ hamburger on screen together.
117
+ - Do not leave only a leading hamburger glued to the title at compact widths.
118
+ - Do not add a header hamburger when the frame already uses `NavigationSide`
119
+ (it already owns the compact menu + right Sheet).
120
+
121
+ **Sheet `direction` follows the menu.** Open the panel from the same side as the
122
+ visible control:
123
+
124
+ - Menu on the **right** (`trailingSlot`) → `direction="Right"`.
125
+ - Menu on the **left** (`leadingSlot`) → `direction="Left"`.
126
+ - Ambiguous (e.g. center) → `Right`.
127
+ - Desktop leading + below-desktop trailing: switch `direction` with the same
128
+ breakpoint as the visible trigger (`useBreakpointUp("desktop")`), or use one
129
+ Sheet per visible trigger.
130
+
131
+ Leave the Sheet header empty except the close control: `title=""` and
132
+ `aria-label="Menu"` (same as `NavigationSide`). Do not put a visible “Menu”
133
+ heading before the close button. Use the `trigger` slot for the `IconButton`.
134
+
135
+ ## Trailing utilities
136
+
137
+ Stay a **row** at every width
138
+ ([how-to-page-layout.md](how-to-page-layout.md#action-clusters)). Typical
139
+ cluster, left to right: search → language (if needed) → session → compact
140
+ hamburger.
141
+
142
+ - **Search** — marketing/catalog: visible [`Textfield`](../uxelle-components/Textfield.md)
143
+ (`label={false}`, `aria-label="Search"`) in a [`Layout`](../uxelle-components/Layout.md)
144
+ `width="16rem"` `flexShrink={0}`. Dense product: search `IconButton`. Table/query
145
+ search stays on the page, not here.
146
+ - **Session** — logged out: Log in + optional Register (one high-emphasis CTA).
147
+ Logged in: account `IconButton` + `Menu` (profile, settings, log out), optional
148
+ `NotificationBadge`. Do not keep Register when the user is signed in.
149
+ - Do not bury Log in / search in the Sheet.
150
+
151
+ ## Secondary bar (`bottomSlot`)
152
+
153
+ `null` unless there is a persistent **L2 module bar** under the primary bar
154
+ (app section switcher). `NavLink`s there use `navigationLevel="Secondary"`. Not
155
+ breadcrumbs, not in-page tabs, not a dump for extra links.
156
+
157
+ ## IA depth: what is inside the menu
158
+
159
+ Treat depth of the information architecture as the switch. Compose existing
160
+ pieces; do not invent a mega-menu component.
161
+
162
+ **Open on click, not hover.** Destination panels are `Menu` `pattern="disclosure"`
163
+ with a `NavLink` trigger (no `href`, `trailingIcon`) so Tab walks links.
164
+ `NavLink withMenu` is overflow labeled More only (`pattern="menu"`, Tab
165
+ dismisses). `keyboard_arrow_down` on a category trigger signals a disclosure.
166
+ `chevron_right` on a row means **another column / level**, not outbound.
167
+
168
+ ### One level (flat site)
169
+
170
+ Destinations are peers. `centerSlot={null}`. The Sheet holds a `List` of
171
+ interactive `ListItem`s with `href` — **no chevrons**. Example: About, Contact.
172
+ Pass `bottomText=""` so the catalog demo line does not show.
173
+
174
+ ### Two levels
175
+
176
+ Top-level groups, each with a flat list of leaves.
177
+
178
+ - **From desktop up:** `centerSlot` is `Menu` `pattern="disclosure"` per group
179
+ (`NavLink` trigger, no `href`, `trailingIcon`). Panel is one column of leaf
180
+ `ListItem`s (`href`, no chevron). Optional short group intro above the list.
181
+ Overflow labeled More uses `NavLink withMenu`.
182
+ - **Below desktop:** hide `centerSlot`. Sheet lists the groups as
183
+ [`Accordion`](../uxelle-components/Accordion.md) (omit `headingLevel` — these
184
+ are not page headings). Each panel is the leaf list. Leaves have no chevron.
185
+ Close the Sheet from each leaf `onClick`.
186
+
187
+ ### Three levels
188
+
189
+ Top-level areas → categories → leaves. Chevron means another column / level.
190
+
191
+ - **From desktop up:** `centerSlot` uses `Menu` `pattern="disclosure"` with a
192
+ `NavLink` trigger (no `href`, `trailingIcon`). The panel is a **wide**
193
+ `Layout` row, not a skinny dropdown:
194
+ 1. **Intro column** — area title (`Text`), one-line descriptor, `Link` to the
195
+ area landing. Quieter surface; not a list.
196
+ 2. **Category column** — interactive `ListItem`s. Rows with children get
197
+ `trailingSlot` + `trailingIconName="chevron_right"`. Hold the selected
198
+ category in state and set `activated` from that state (it drives the next
199
+ column). A row with no children is a leaf (no chevron).
200
+ 3. **Leaf column** — map the selected category to `ListItem` `href` rows with
201
+ **no** chevron. Optional “View all {category}” at the bottom of this
202
+ column only.
203
+ - **Below desktop:** do **not** lay out three columns in the Sheet. Same tree as
204
+ a **drill-down**: Accordion (or nested Accordion) so one extra level expands in
205
+ place; leaves are `href` rows. One visible extra level at a time.
206
+
207
+ Selecting a category in the desktop panel **updates the next column in place**
208
+ (does not navigate away). Only leaf rows and “Explore …” / “View all …”
209
+ navigate. Close the panel or Sheet after a leaf navigation (`onOpenChange` /
210
+ `setMenuOpen(false)`).
211
+
212
+ ## Responsive
213
+
214
+ - One-level: hamburger may sit in `trailingSlot` at every width (valid on
215
+ desktop and below desktop).
216
+ - Two- and three-level: show `centerSlot` from desktop up; below desktop set
217
+ `centerSlot={null}` and put the menu `IconButton` + Sheet in `trailingSlot`.
218
+ Switch with `useBreakpointUp("desktop")`
219
+ ([how-to-page-layout.md](how-to-page-layout.md#responsiveness)). Never
220
+ hard-code breakpoint px. Do not use the tablet band for this chrome switch.
221
+ - Trailing utilities stay a row at every width.
222
+
223
+ ## Spacing
224
+
225
+ `Navigation` is **density-invariant** chrome (its own tokens). Do not wrap it in
226
+ extra `p` / `gap`. Lockup lines use `gap={0}` — not the Tight step. Slot content
227
+ inherits the host experience’s density mode ([density.md](density.md)).
228
+
229
+ ## A11y
230
+
231
+ [how-to-accessibility.md](how-to-accessibility.md). `Navigation` already exposes
232
+ named `nav` landmarks. Name the home control on the Logo’s wrapping `<a>`
233
+ (`aria-label="Home"`); keep the mark decorative. Name the menu `IconButton`.
234
+ `aria-current="page"` on the current leaf `NavLink` / `ListItem` `href`.
235
+ `activated` on the selected mega-menu category (not a leaf). Name the Sheet with
236
+ `aria-label="Menu"` and `title=""` so the header is only the close control.
237
+ Every `IconButton` has an `aria-label`.
238
+
239
+ ## Color
240
+
241
+ `Navigation` owns each band’s surface — style slot content with role tokens; do
242
+ not hardcode color ([how-to-color.md](how-to-color.md)). Secondary-bar
243
+ `NavLink`s use `navigationLevel="Secondary"` so they follow that band.
244
+
245
+ ## React
246
+
247
+ Import from `@uxelle/components`. One-level marketing/public default (lockup +
248
+ trailing hamburger Sheet). Control the Sheet with `open` / `onOpenChange` and
249
+ close it from each leaf `onClick`. `desktopUp` is unused here because the menu
250
+ stays in `trailingSlot` at every width.
251
+
252
+ ```tsx
253
+ const [menuOpen, setMenuOpen] = useState(false);
254
+
255
+ <Navigation
256
+ bottomSlot={null}
257
+ leadingSlot={
258
+ <>
259
+ <a href="/" aria-label="Home">
260
+ <Logo name="generic" width={48} interactive aria-hidden />
261
+ </a>
262
+ <Layout
263
+ display="flex"
264
+ flexDirection="column"
265
+ gap={0}
266
+ >
267
+ <Text type="Condensed Alt" truncation width={false}>
268
+ Crop Science
269
+ </Text>
270
+ <Text type="Condensed" truncation width={false}>
271
+ United States
272
+ </Text>
273
+ </Layout>
274
+ </>
275
+ }
276
+ centerSlot={null}
277
+ trailingSlot={
278
+ <>
279
+ <LanguageSelector value="EN" />
280
+ <Button emphasis="low" size="small">
281
+ Log in
282
+ </Button>
283
+ <Button emphasis="high" size="small">
284
+ Register
285
+ </Button>
286
+ <Sheet
287
+ open={menuOpen}
288
+ onOpenChange={setMenuOpen}
289
+ direction="Right"
290
+ title=""
291
+ aria-label="Menu"
292
+ trigger={
293
+ <IconButton
294
+ emphasis="low"
295
+ size="small"
296
+ iconName="menu"
297
+ iconVariant="sharpUnfilled"
298
+ aria-label="Menu"
299
+ />
300
+ }
301
+ >
302
+ <List>
303
+ <ListItem
304
+ interactive
305
+ href="/about"
306
+ centerText="About"
307
+ bottomText=""
308
+ onClick={() => setMenuOpen(false)}
309
+ />
310
+ <ListItem
311
+ interactive
312
+ href="/contact"
313
+ centerText="Contact"
314
+ bottomText=""
315
+ onClick={() => setMenuOpen(false)}
316
+ />
317
+ </List>
318
+ </Sheet>
319
+ </>
320
+ }
321
+ />
322
+ ```
323
+
324
+ Two-level desktop `centerSlot` (hide below desktop; overflow More uses
325
+ `withMenu`; Sheet in trailing carries the same tree):
326
+
327
+ ```tsx
328
+ <Menu
329
+ pattern="disclosure"
330
+ direction="Bottom Left"
331
+ trigger={<NavLink label="Crop Protection" trailingIcon />}
332
+ >
333
+ <List>
334
+ <ListItem interactive href="/crop-protection/fungicides" centerText="Fungicides" bottomText="" />
335
+ <ListItem interactive href="/crop-protection/herbicides" centerText="Herbicides" bottomText="" />
336
+ <ListItem interactive href="/crop-protection/trial-data" centerText="Trial Data" bottomText="" />
337
+ </List>
338
+ </Menu>
339
+ <NavLink label="More" withMenu trailingIcon>
340
+ <List>
341
+ <ListItem interactive href="/seeds" centerText="Seeds" bottomText="" />
342
+ </List>
343
+ </NavLink>
344
+ ```
345
+
346
+ Three-level desktop `centerSlot` — selected category in state fills the next
347
+ column; close the panel from each leaf:
348
+
349
+ ```tsx
350
+ const [panelOpen, setPanelOpen] = useState(false);
351
+ const [category, setCategory] = useState<"fungicides" | "herbicides">("fungicides");
352
+
353
+ const leaves = {
354
+ fungicides: [
355
+ { href: "/products/delaro", label: "Delaro" },
356
+ { href: "/products/luna", label: "Luna Family" },
357
+ { href: "/crop-protection/fungicides", label: "View all Fungicides" },
358
+ ],
359
+ herbicides: [
360
+ { href: "/products/roundup", label: "Roundup" },
361
+ { href: "/crop-protection/herbicides", label: "View all Herbicides" },
362
+ ],
363
+ } as const;
364
+
365
+ <Menu
366
+ pattern="disclosure"
367
+ direction="Bottom Left"
368
+ open={panelOpen}
369
+ onOpenChange={setPanelOpen}
370
+ trigger={<NavLink label="Crop Protection" trailingIcon />}
371
+ >
372
+ <Layout display="flex" gap="var(--uxl-theme-layout-spacing-small-4)">
373
+ <Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-2)">
374
+ <Text type="Condensed Alt" width={false}>
375
+ Crop Protection
376
+ </Text>
377
+ <Text type="Condensed" width={false}>
378
+ Innovation to help farmers protect their harvests.
379
+ </Text>
380
+ <Link href="/crop-protection">Explore Crop Protection</Link>
381
+ </Layout>
382
+ <List>
383
+ <ListItem
384
+ interactive
385
+ activated={category === "fungicides"}
386
+ trailingSlot
387
+ trailingIconName="chevron_right"
388
+ centerText="Fungicides"
389
+ bottomText=""
390
+ onClick={() => setCategory("fungicides")}
391
+ />
392
+ <ListItem
393
+ interactive
394
+ activated={category === "herbicides"}
395
+ trailingSlot
396
+ trailingIconName="chevron_right"
397
+ centerText="Herbicides"
398
+ bottomText=""
399
+ onClick={() => setCategory("herbicides")}
400
+ />
401
+ <ListItem
402
+ interactive
403
+ href="/crop-protection/trial-data"
404
+ centerText="Trial Data"
405
+ bottomText=""
406
+ onClick={() => setPanelOpen(false)}
407
+ />
408
+ </List>
409
+ <List>
410
+ {leaves[category].map((item) => (
411
+ <ListItem
412
+ key={item.href}
413
+ interactive
414
+ href={item.href}
415
+ centerText={item.label}
416
+ bottomText=""
417
+ onClick={() => setPanelOpen(false)}
418
+ />
419
+ ))}
420
+ </List>
421
+ </Layout>
422
+ </Menu>
423
+ ```
424
+
425
+ ## A2UI
426
+
427
+ No `getUxelleRecipe("navigation")`. Hosts often own chrome; when the surface
428
+ already has `A2uiNavigation`, use the same slot assignment. Runtime chat has no
429
+ `matchMedia` — default to one-level (lockup + trailing `A2uiSheet` `direction`
430
+ `"Right"`). Empty or omitted child lists become catalog placeholders (the adapter
431
+ maps them to `undefined`); prefer host-owned chrome when a region must be absent.
432
+ Compose `A2uiLogo`, `A2uiText`, `A2uiNavLink`, `A2uiList` / `A2uiListItem`,
433
+ `A2uiIconButton`, `A2uiSheet`. See [a2ui.md](a2ui.md).