@nxgt/material 3.85.1 → 3.86.1

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.
Files changed (144) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +39 -5
  3. package/dist/{alert-dialog-1t6ULy5i.js → alert-dialog-DZnt8PYE.js} +1 -1
  4. package/dist/{async-slice-xyA9Uzct.js → async-slice-D3OdV2BS.js} +1 -1
  5. package/dist/{button-DhiNGrdT.js → button-MAL5an7L.js} +1 -1
  6. package/dist/{calculator-BcgxxScL.js → calculator-C3a8YMwl.js} +10 -10
  7. package/dist/calculator-field.js +6 -6
  8. package/dist/{calculator-segment-C4WQAHcN.js → calculator-segment-BigMpjhn.js} +1 -1
  9. package/dist/calculator.js +1 -1
  10. package/dist/charts.js +3 -3
  11. package/dist/{chip-m0_usuzd.js → chip-DKAKoNxq.js} +2 -2
  12. package/dist/{code-editor-BgKFrcld.js → code-editor-B6dFQSUJ.js} +4 -4
  13. package/dist/code-editor.js +4 -4
  14. package/dist/{code-language-items-BWw17W9s.js → code-language-items-CXEMiWoF.js} +2 -2
  15. package/dist/components/components.css +2 -0
  16. package/dist/components/theme-tokens.css +18 -0
  17. package/dist/components/ui/layout/app-bar/top-app-bar/top-app-bar-collapse.css +60 -0
  18. package/dist/components/ui/layout/app-bar/top-app-bar/top-app-bar.css +205 -0
  19. package/dist/components/ui/layout/app-shell/app-header.css +70 -63
  20. package/dist/{components-DfSE2F8d.js → components-C2MBVly0.js} +17327 -16929
  21. package/dist/components.js +24 -24
  22. package/dist/{conversion-variant-DultZZQm.js → conversion-variant-BT5n4-jm.js} +3 -3
  23. package/dist/{currency-field-CPbaM7ay.js → currency-field-t41sFYg_.js} +4 -4
  24. package/dist/{dialog-DmlG9opW.js → dialog-BjKiRXJg.js} +3 -3
  25. package/dist/dnd.js +1 -1
  26. package/dist/{dropdown-menu-CDKev8dk.js → dropdown-menu-IRFp4CH9.js} +1 -1
  27. package/dist/editor.js +7 -7
  28. package/dist/{empty-state-C56000TB.js → empty-state-C70prDDr.js} +1 -1
  29. package/dist/{financial-variant-BYjQ690K.js → financial-variant-BIpzs8IL.js} +5 -5
  30. package/dist/{hooks-BTwLAy_A.js → hooks-CjO11MS7.js} +2 -2
  31. package/dist/hooks.js +2 -2
  32. package/dist/{hover-card-CDtW6utN.js → hover-card-cU_aJa18.js} +1 -1
  33. package/dist/i18n.js +3 -3
  34. package/dist/{icon-badge-ec7MmUGA.js → icon-badge-D3KO6uEQ.js} +1 -1
  35. package/dist/{icon-button-CG4aO8Gh.js → icon-button-DrqNk1Ww.js} +1 -1
  36. package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
  37. package/dist/lib/components/ui/layout/app-bar/index.d.ts +1 -1
  38. package/dist/lib/components/ui/layout/app-bar/top-app-bar/index.d.ts +1 -0
  39. package/dist/lib/components/ui/layout/app-bar/top-app-bar/partials/top-app-bar-actions.d.ts +15 -0
  40. package/dist/lib/components/ui/layout/app-bar/top-app-bar/partials/top-app-bar-headline.d.ts +4 -0
  41. package/dist/lib/components/ui/layout/app-bar/top-app-bar/partials/top-app-bar-link.d.ts +9 -0
  42. package/dist/lib/components/ui/layout/app-bar/top-app-bar/partials/top-app-bar-menu-section.d.ts +14 -0
  43. package/dist/lib/components/ui/layout/app-bar/top-app-bar/partials/top-app-bar-primary-action.d.ts +12 -0
  44. package/dist/lib/components/ui/layout/app-bar/top-app-bar/partials/top-app-bar-row.d.ts +23 -0
  45. package/dist/lib/components/ui/layout/app-bar/top-app-bar/partials/top-app-bar-title-block.d.ts +18 -0
  46. package/dist/lib/components/ui/layout/app-bar/top-app-bar/top-app-bar.actions.d.ts +43 -0
  47. package/dist/lib/components/ui/layout/app-bar/top-app-bar/top-app-bar.d.ts +9 -0
  48. package/dist/lib/components/ui/layout/app-bar/top-app-bar/top-app-bar.types.d.ts +120 -0
  49. package/dist/lib/components/ui/layout/app-bar/top-app-bar/top-app-bar.utils.d.ts +43 -0
  50. package/dist/lib/components/ui/layout/app-shell/activity-app-bar.d.ts +6 -6
  51. package/dist/lib/components/ui/layout/app-shell/activity-chrome.d.ts +6 -3
  52. package/dist/lib/components/ui/layout/app-shell/activity-column-props.d.ts +1 -1
  53. package/dist/lib/components/ui/layout/app-shell/activity-column.d.ts +2 -2
  54. package/dist/lib/components/ui/layout/app-shell/activity-layout-navigation.types.d.ts +32 -4
  55. package/dist/lib/components/ui/layout/app-shell/activity-layout.types.d.ts +12 -2
  56. package/dist/lib/components/ui/layout/app-shell/app-header.d.ts +9 -3
  57. package/dist/lib/components/ui/layout/app-shell/header-look.d.ts +14 -0
  58. package/dist/lib/components/ui/layout/app-shell/header-title.d.ts +9 -36
  59. package/dist/lib/components/ui/layout/app-shell/header.utils.d.ts +13 -3
  60. package/dist/lib/components/ui/layout/app-shell/index.d.ts +1 -1
  61. package/dist/lib/components/ui/layout/app-shell/navigation.utils.d.ts +7 -2
  62. package/dist/lib/components/ui/layout/app-shell/shell-scroll.d.ts +23 -1
  63. package/dist/lib/components/ui/layout/app-shell/shell-top-app-bar.d.ts +10 -0
  64. package/dist/lib/components/ui/layout/app-shell/shell-top-app-bar.parts.d.ts +58 -0
  65. package/dist/lib/components/ui/layout/app-shell/shell-top-app-bar.types.d.ts +26 -0
  66. package/dist/lib/components/ui/layout/app-shell/use-header-scroll.d.ts +16 -9
  67. package/dist/lib/components/ui/layout/app-shell/use-shell-header.d.ts +1 -1
  68. package/dist/lib/components/ui/primitives/tabs/tabs-press.d.ts +11 -0
  69. package/dist/lib/i18n/resources/en/en.d.ts +4 -0
  70. package/dist/lib/i18n/resources/en/material.json.d.ts +4 -0
  71. package/dist/lib/i18n/resources/fr/fr.d.ts +4 -0
  72. package/dist/lib/i18n/resources/fr/material.json.d.ts +4 -0
  73. package/dist/lib/i18n/resources/resources.d.ts +8 -0
  74. package/dist/lib/i18n/translator.d.ts +1 -1
  75. package/dist/lib.js +1 -1
  76. package/dist/main.js +28 -28
  77. package/dist/map.js +7 -7
  78. package/dist/material.css +1 -1
  79. package/dist/pdf-reader.js +1 -1
  80. package/dist/{percent-field-8_pILs15.js → percent-field-D47IlaPF.js} +1 -1
  81. package/dist/{picker-surface-pyGv-Qh9.js → picker-surface-Cg4V4-E0.js} +4 -4
  82. package/dist/{popover-3EF4q2CT.js → popover-Dzk4-l9U.js} +1 -1
  83. package/dist/{pos-types-Dv2UxKSN.js → pos-types-JOXMY4Y2.js} +1 -1
  84. package/dist/{pos-variant-DJvtN9N8.js → pos-variant-B2mS8bMB.js} +4 -4
  85. package/dist/print.js +1 -1
  86. package/dist/{programmer-variant-Bmcq2KzC.js → programmer-variant-C_Tl0P4_.js} +2 -2
  87. package/dist/{public-BNebh4z0.js → public-ByAsqBOf.js} +9 -9
  88. package/dist/{rich-code-block-DmX7d58S.js → rich-code-block-CJAfqk6b.js} +5 -5
  89. package/dist/{scientific-variant-DijUrpP8.js → scientific-variant-CskM4MLV.js} +2 -2
  90. package/dist/{sortable-provider-Cnmt_gWu.js → sortable-provider-ICS_vkLA.js} +2 -2
  91. package/dist/{spinner-C6LP2Rw8.js → spinner-CweqX5A9.js} +1 -1
  92. package/dist/styles.css +2 -0
  93. package/dist/{tabs-Bj1zOe7-.js → tabs-95Y2LmnV.js} +65 -43
  94. package/dist/theme-picker.js +2 -2
  95. package/dist/{theme-toggle-Cxku5iBP.js → theme-toggle-WAFkncYx.js} +1 -1
  96. package/dist/{toolbar-items-CEIuSxU_.js → toolbar-items-BsgzHSNf.js} +4 -4
  97. package/dist/{translate-material-DI3nmQ6I.js → translate-material-DTIAPmlU.js} +1 -1
  98. package/dist/{translator-BstxG_hN.js → translator-BRZNhsy5.js} +8 -0
  99. package/dist/{use-chat-composer-BqLkk_iH.js → use-chat-composer-CleSUh_y.js} +15 -15
  100. package/dist/{use-heading-spy-nFyb9_KE.js → use-heading-spy-D8WbHJuK.js} +4 -4
  101. package/dist/{use-material-translation-1kCHtB0p.js → use-material-translation-CCy1Me0h.js} +1 -1
  102. package/dist/{use-return-focus-Hv8rn55-.js → use-return-focus-BA8G1qVU.js} +1 -1
  103. package/docs/README.md +1 -0
  104. package/docs/components/navigation-and-layout.md +85 -32
  105. package/docs/guides/app-shell.md +158 -63
  106. package/docs/guides/drawing.md +2 -0
  107. package/docs/guides/layouts.md +3 -3
  108. package/docs/guides/migrating-to-top-app-bar.md +156 -0
  109. package/docs/guides/theming.md +5 -0
  110. package/docs/previews/navigation-layout-appshell-blue-moon.webp +0 -0
  111. package/docs/previews/navigation-layout-appshell-footer-icon.webp +0 -0
  112. package/docs/previews/navigation-layout-appshell-menu-badge.webp +0 -0
  113. package/docs/previews/navigation-layout-appshell-page-ground.webp +0 -0
  114. package/docs/previews/navigation-layout-appshell-themes.webp +0 -0
  115. package/docs/previews/navigation-layout-appshell.webp +0 -0
  116. package/docs/previews/navigation-layout-topappbar.webp +0 -0
  117. package/docs/roadmap.md +17 -6
  118. package/docs/troubleshooting.md +80 -0
  119. package/lib/components/components.css +2 -0
  120. package/lib/components/theme-tokens.css +18 -0
  121. package/lib/components/ui/layout/app-bar/top-app-bar/top-app-bar-collapse.css +60 -0
  122. package/lib/components/ui/layout/app-bar/top-app-bar/top-app-bar.css +205 -0
  123. package/lib/components/ui/layout/app-shell/app-header.css +70 -63
  124. package/lib/styles.css +2 -0
  125. package/lib/themes/ambre.css +5 -1
  126. package/lib/themes/ardoise.css +5 -1
  127. package/lib/themes/bleumono.css +5 -1
  128. package/lib/themes/bluemoon.css +5 -1
  129. package/lib/themes/corail.css +5 -1
  130. package/lib/themes/foret.css +5 -1
  131. package/lib/themes/lavande.css +5 -1
  132. package/lib/themes/menthe.css +5 -1
  133. package/lib/themes/ocean.css +5 -1
  134. package/lib/themes/pink.css +5 -1
  135. package/lib/themes/prune.css +5 -1
  136. package/lib/themes/sable.css +5 -1
  137. package/lib/themes/slate.css +5 -1
  138. package/lib/themes/teal.css +5 -1
  139. package/package.json +2 -2
  140. package/dist/lib/components/ui/layout/app-bar/top-app-bar.d.ts +0 -18
  141. package/dist/lib/components/ui/layout/app-shell/app-header-row.d.ts +0 -3
  142. package/dist/lib/components/ui/layout/app-shell/header-context.d.ts +0 -10
  143. package/dist/lib/components/ui/layout/app-shell/page-title.d.ts +0 -27
  144. /package/dist/lib/components/ui/layout/{app-shell → app-bar/top-app-bar}/use-truncated-title.d.ts +0 -0
package/docs/README.md CHANGED
@@ -32,6 +32,7 @@ detail: every option, the second example, and the traps.
32
32
  | [SSR](guides/ssr.md) | Your app renders on a server and bundles this package |
33
33
  | [Content Security Policy](guides/csp.md) | Your app sends a strict CSP: the directives to use, the components that still inject a `<style>`, the zod and KaTeX settings, self-hosting a font, the per-request nonce |
34
34
  | [The Vite plugin](guides/vite-plugin.md) | You are adding `nxgtMaterial()` to `vite.config.ts`: the CSP by hash or nonce, fonts, the styles check, PDFs on a bundled server, and each option's opt-out |
35
+ | [Moving to TopAppBar](guides/migrating-to-top-app-bar.md) | Your app uses `AppHeader`, `TopAppBar`'s `variant` / `headline`, the shell's `'center'` / `'medium'` / `'large'` header values or the `app-header*` slots: each old spelling and its replacement, the visual changes, the slot rename planned for the next major |
35
36
  | [Migrating to 3.0](guides/migrating-to-3.md) | You are upgrading from 2.x: the React 19.3 and React Router 8.5 peers, `ActivityLayout`'s default route transition, `use(browser())` in server HTML, and each API removed in 3.0 with what replaces it |
36
37
  | [Date and time fields: from 2.x](guides/date-time-fields-2-x.md) | Your app used `DateField`, `DateRangeField`, `TimeField` or `TimePicker` before their redesign: each change in look and behaviour, the 2.x ways back that 3.0 removed, and what to change in tests |
37
38
  | [The field look (2.x)](guides/field-look-2-x.md) | Your fields or pickers look different after an upgrade (8px corners, the focus halo, a dashed read-only, a 44px `NativeSelect`, a list connected to its trigger, a `CountryField` with no chip, new group headers, the date and time fields' boxes) and you want to know what changed or get the earlier look back |
@@ -7,11 +7,11 @@ documentation-site shell. Everything is imported from
7
7
 
8
8
  | | Component | What it does |
9
9
  | --- | --- | --- |
10
- | <img src="../previews/navigation-layout-appshell.webp" width="96" alt=""> | [ActivityLayout](#activitylayout) | App shell: sidebar, header, breadcrumbs, titled page; `header` sets the app bar (small, center, medium or large collapsing on scroll, hide on scroll, a route's `handle.header`, tabs under it, a route's `handle.back` arrow); a route's `handle.title` is the page's `h1` in the app bar (it labels `main`; `usePageTitleFocus()` focuses it after a navigation), `handle.titleAfter` / `titleAfter` a node right after it, outside the `h1` (a record's `Switcher`, or `<Switcher size="compact">` for an icon-only one), `headerContext` a small tenant line after it from md up, `breadcrumbRoot` a text root crumb or (`icon: true`) the home icon with its own target and name, `breadcrumb={false}` no trail, `context` the tenant line atop the phone's menu sheet; `menuOpen` / `onMenuOpenChange` control the phone's menu sheet, which closes itself on a followed link or any location change |
10
+ | <img src="../previews/navigation-layout-appshell.webp" width="96" alt=""> | [ActivityLayout](#activitylayout) | App shell: sidebar, header, breadcrumbs, titled page; `header` sets the app bar, a `TopAppBar` (small, medium-flexible, large-flexible or search, collapsing on scroll, hide on scroll, a route's `handle.header`, tabs under it, a route's `handle.back` arrow); a route's `handle.title` is the page's `h1` in the app bar (it labels `main`; `usePageTitleFocus()` focuses it after a navigation), `handle.titleAfter` / `titleAfter` a node right after it, outside the `h1` (a record's `Switcher`, or `<Switcher size="compact">` for an icon-only one), `headerContext` a small tenant line after it from md up, `breadcrumbRoot` a text root crumb or (`icon: true`) the home icon with its own target and name, `breadcrumb={false}` no trail, `context` the tenant line atop the phone's menu sheet; `menuOpen` / `onMenuOpenChange` control the phone's menu sheet, which closes itself on a followed link or any location change |
11
11
  | <img src="../previews/navigation-layout-accountmenu.webp" width="96" alt=""> | [AccountMenu](#accountmenu) | The app bar's avatar menu: the account, the app's links, Sign out |
12
12
  | <img src="../previews/navigation-layout-authcard.webp" width="96" alt=""> | [AuthCard](#authcard) | Signed-out page frame: one `main`, a centred card, its title the `h1` |
13
13
  | <img src="../previews/navigation-layout-sidebar.webp" width="96" alt=""> | [Sidebar](#sidebar) | Collapsible navigation sidebar |
14
- | <img src="../previews/navigation-layout-topappbar.webp" width="96" alt=""> | [TopAppBar, BottomAppBar](#topappbar) | Top (small, center, medium, large) and bottom bars |
14
+ | <img src="../previews/navigation-layout-topappbar.webp" width="96" alt=""> | [TopAppBar, BottomAppBar](#topappbar) | The top app bar (small, medium-flexible, large-flexible, search; collapse, icon actions as data, account menu, primary action) and a bottom bar |
15
15
  | | [AppSearchBar, SearchView](#appsearchbar) | The app bar's search: a pill with a docked results panel from 768px, a full-screen search view on a phone |
16
16
  | <img src="../previews/navigation-layout-stickyactionbar.webp" width="96" alt=""> | [StickyActionBar](#stickyactionbar) | A page's main decision, stuck to the bottom on a phone, inline from `md` |
17
17
  | <img src="../previews/navigation-layout-breadcrumb.webp" width="96" alt=""> | [Breadcrumb](#breadcrumb) | Breadcrumb primitives |
@@ -68,7 +68,7 @@ const items: MenuItem[] = [
68
68
  ```
69
69
 
70
70
  Parts: `ActivityLayout` (the chrome), `ActivityContent` (one screen),
71
- `AppSidebar`, `AppHeader` and `AppBreadcrumb`, which the layout composes and
71
+ `AppSidebar`, `AppHeader` (deprecated, use `TopAppBar`) and `AppBreadcrumb`, which the layout composes and
72
72
  which work alone. `AppHeader` also takes `tabs` (`ReactNode`, default none: a
73
73
  `TabsNav` as a row at the foot of the bar, pinned when the bar hides) and `back`
74
74
  (`ShellBack`, default none: the back arrow, named "Back" or "Back to <label>"
@@ -90,10 +90,11 @@ phone's menu sheet pad clear of `env(safe-area-inset-*)`, as classes; see the
90
90
  | `headerTitle` | `ReactNode` | — | the header's leading title (the context: an organisation's name), after the phone's menu button, truncated when long; when it is cut off, its full text is the `title` tooltip (#629) |
91
91
  | `handle.title` | `ReactNode \| (match: UIMatch) => ReactNode` | — | on a route: the page's title in the app bar, its one `h1`, in the first server HTML; `ActivityContent` leaves its own title out while it is set (see the [app shell guide](../guides/app-shell.md#the-pages-title-in-the-app-bar)) |
92
92
  | `titleAfter` / `handle.titleAfter` | `ReactNode \| (match: UIMatch) => ReactNode` | — | a node right after the page's title in the app bar, outside its `h1` (a record's `Switcher`); stays on screen on a phone, the title truncates first; the prop wins over the handle (see the [app shell guide](../guides/app-shell.md#a-slot-after-the-title)) |
93
- | `headerContext` | `ReactNode` | — | a small muted context line (the tenant) after the page's title and its slot, from `md` up, outside the `h1`, truncated with its full text as the tooltip; hidden on a phone, where `context` fills the menu sheet (see the [app shell guide](../guides/app-shell.md#a-context-line-after-the-title)) |
93
+ | `headerContext` | `ReactNode` | — | the bar's `subtitle`: a small muted context line (the tenant) under the page's title and its slot, from `md` up, outside the `h1`, truncated with its full text as the tooltip; hidden on a phone, where `context` fills the menu sheet (see the [app shell guide](../guides/app-shell.md#a-context-line-after-the-title)) |
94
94
  | `breadcrumb` | `boolean` | `true` | `false` hides the breadcrumb trail (an error page); it is also hidden when every matched `handle.breadcrumb` renders `null`; see the [app shell guide](../guides/app-shell.md#breadcrumbs) |
95
95
  | `breadcrumbRoot` | `{ label: ReactNode; to: string }` or `{ label: string; to: string; icon: true }` | — | the breadcrumb's first crumb: a text link in place of the home icon, or with `icon: true` the home icon alone linked to `to`, named and tooltipped `label` (`AppBreadcrumb`'s `root`); see the [app shell guide](../guides/app-shell.md#breadcrumbs) |
96
- | `headerActions` | `ReactNode` | — | header, before the avatar |
96
+ | `headerActions` | `ReactNode \| TopAppBarAction[]` | — | header, before the avatar; data (`TopAppBarAction`: `label`, `icon`, `onClick` / `href`, `placement` `'bar'` or `'account'`, `group`, `selected`, `disabled`; see [TopAppBar](#topappbar)) draws free icon buttons and an account menu |
97
+ | `headerPrimaryAction` | `TopAppBarPrimaryAction` (`{ label, icon, onClick?, disabled? }`) | — | the bar's `primaryAction`, the screen's main action: a `Button` from 768 px, a `Fab` on a phone |
97
98
  | `title`, `subtitle`, `actions` | `ReactNode` | — | `ActivityContent`'s header card; `title` is left out while a route's `handle.title` puts the page's title in the app bar |
98
99
  | `header` / `hideHeader` | `ReactNode` / `boolean` | — | `ActivityContent`: replace or drop the header card |
99
100
  | `contentAs` / `scroll` | `'main' \| 'div'` / `'document' \| 'container'` | `'main'` / `'document'` | the content region is the page's `<main>`; the document scrolls (see the [app shell guide](../guides/app-shell.md#landmarks-headings-and-scrolling)) |
@@ -107,7 +108,7 @@ phone's menu sheet pad clear of `env(safe-area-inset-*)`, as classes; see the
107
108
  | `navigation` | `ShellNavigation \| { phone?, tablet?, desktop?, hideOnScroll?, persistKey? }` | `{ phone: 'modal-drawer', tablet: 'rail', desktop: 'rail' }` | `ActivityLayout`: the navigation per window size (phone below 768px, tablet to 1279px, desktop from 1280px). Every `ShellNavigation` name is drawn: `'modal-drawer'`, `'rail'`, `'rail-expanded'`, `'drawer'`, `'sidebar'`, `'top'`, `'two-level'`, `'floating'`, `'bar'` and `'none'`. `hideOnScroll` (a field of this object, `false` by default) slides the `'floating'` toolbar away on scroll; no other preset reads it. `persistKey` (a field of this object) remembers the `'rail-expanded'` rail's, the `'sidebar'`'s and the `'two-level'` panel's state. A name alone (`navigation="sidebar"`, `"drawer"`, `"top"`, `"two-level"`) leaves a phone its default navigation; `{ phone: 'sidebar' }` draws it there. A phone never draws `'rail'` or `'rail-expanded'`: it keeps its menu sheet. `navigation="bar"` alone is phone-only (hidden from 768px, where the default rail shows; `{ tablet: 'bar' }` keeps it there). `'floating'` is drawn at every size, the phone included. See [`navigation` and `header`](../guides/app-shell.md#navigation-and-header) |
108
109
  | `navigationFab` | `ReactNode \| ({ expanded }) => ReactNode` | — | `ActivityLayout`: the screen's primary action, at the top of the `'rail-expanded'` rail (a `Fab`; the function form receives the rail's state). The other presets do not draw it |
109
110
  | `sidebarExpanded` / `defaultSidebarExpanded` / `onSidebarExpandedChange` | `boolean` / `boolean` / `(expanded: boolean) => void` | — / `false` / — | `ActivityLayout`: the `'sidebar'`'s expanded state (labels beside the icons; it starts collapsed), controlled or not; with `navigation.persistKey` the last choice is kept and read after hydration |
110
- | `header` | `'small' \| 'center' \| 'medium' \| 'large' \| 'none' \| { variant, collapse?, hideOnScroll?, tabs?, search? }` | `'small'` | `ActivityLayout`: the app bar's size. The five variants and `hideOnScroll` are drawn; `search` draws an `AppSearchBar` (see [AppSearchBar](#appsearchbar)); `tabs` (a `TabsNav`) draws a row stuck under the bar, which stays pinned when the bar hides (see [Tabs under the app bar](../guides/app-shell.md#tabs-under-the-app-bar)); a route's `handle.back` (`ShellBack`: `{ to, label? }` or `true`) draws a back arrow before the title, the phone's menu button following it while the phone has the menu sheet (see [The back arrow](../guides/app-shell.md#the-back-arrow)). A route's `handle.header` overrides it; a `'medium'` or `'large'` header needs a `handle.title`, else it stays small; `'none'` also drops the phone's menu button, so pair it with `navigation="bar"`. See [The app bar variants](../guides/app-shell.md#the-app-bar-variants), [Scroll direction](../guides/app-shell.md#scroll-direction) and [Room at the bottom](../guides/app-shell.md#room-at-the-bottom) |
111
+ | `header` | `'small' \| 'medium-flexible' \| 'large-flexible' \| 'search' \| 'none' \| { size?, align?, collapse?, hideOnScroll?, tabs?, search? }` | `'small'` | `ActivityLayout`: the app bar, drawn with `TopAppBar`. `size` and `align` pick its look (the old `'center'`, `'medium'`, `'large'` and `variant` still work, deprecated); `collapse` (default on for medium and large) and `hideOnScroll` follow the scroll; `search` draws an `AppSearchBar` (see [AppSearchBar](#appsearchbar)); `tabs` (a `TabsNav`) draws a row stuck under the bar, which stays pinned when the bar hides (see [Tabs under the app bar](../guides/app-shell.md#tabs-under-the-app-bar)); a route's `handle.back` (`ShellBack`: `{ to, label? }` or `true`) draws a back arrow before the title, the phone's menu button following it while the phone has the menu sheet (see [The back arrow](../guides/app-shell.md#the-back-arrow)). A route's `handle.header` overrides it; a medium or large header needs a `handle.title`, else it stays small; `'none'` also drops the phone's menu button, so pair it with `navigation="bar"`. See [Top app bar sizes](../guides/app-shell.md#top-app-bar-sizes), [Scroll direction](../guides/app-shell.md#scroll-direction) and [Room at the bottom](../guides/app-shell.md#room-at-the-bottom) |
111
112
  | `align` | `'start' \| 'center'` | `'start'` | `ActivityLayout`, `AppSidebar`: the collapsed rail's entries sit right under the brand, or `'center'` for centred in the height below it |
112
113
  | `backdrop` | `ReactNode \| false` | — | `ActivityLayout`: an exception to the theme's background (drawn by default; Lavande's is the built-in gradient and blobs); `false` draws a plain page. A node is a decorative background behind the frame: a `Background` preset from `@nxgt/material/backgrounds`. Rendered in an `inert`, `aria-hidden` layer as wide as the content column and one viewport high, fixed in the viewport while the content scrolls; the chrome (app bar, breadcrumb, tabs strip) is lightly translucent and frosted over it (80%, 87% for the sidebar, breadcrumb and tabs strip, 92% and no blur under `prefers-reduced-transparency` or without `backdrop-filter`), and the lists, tables and cards stay opaque; `--nxgt-backdrop-chrome` sets the chrome's fill and `--nxgt-backdrop-surface` the lists'. See [Use a background preset behind the shell](../guides/app-shell.md#use-a-background-preset-behind-the-shell) |
113
114
  | `routeTransition` | `boolean \| Omit<ViewTransitionProps, 'children' \| 'ref'>` | `true` | `ActivityLayout`: React's `<ViewTransition>` around the content region, so a navigation cross-fades it (by default only when the pathname changes, not on a search change, a fetcher or a revalidation). `false` renders none (your app has its own boundary); an object passes `default` / `update` classes, `name`, callbacks through. `ROUTE_TRANSITION_CLASS` (`'nxgt-route'`) is exported for nested boundaries. See [Route transitions](../guides/app-shell.md#route-transitions) |
@@ -128,7 +129,7 @@ pads its bottom and shortens its sidebar by the bar's height
128
129
  footer stay above it. Another layout adds `pb-(--nxgt-media-dock)` to what
129
130
  scrolls.
130
131
 
131
- **Storybook:** `Navigation & layout/AppShell` · **Vue:** `ActivityLayout`, `ActivityContent`, `AppSidebar`, `AppHeader`, `AppBreadcrumb`
132
+ **Storybook:** `Navigation & layout/AppShell` · **Vue:** `ActivityLayout`, `ActivityContent`, `AppSidebar`, `AppHeader` (deprecated, use `TopAppBar`), `AppBreadcrumb`
132
133
 
133
134
  ---
134
135
 
@@ -360,19 +361,79 @@ recolour the rail. See [Theming](../guides/theming.md#the-active-entry-of-the-si
360
361
 
361
362
  ![TopAppBar](../previews/navigation-layout-topappbar.webp)
362
363
 
363
- Top bar surface, and a bottom bar of icon actions with an active state.
364
+ The top app bar: the bar `ActivityLayout` draws, for a layout of your own. A
365
+ `title` that is the page's one `<h1>`, a `subtitle` under it, four sizes, a
366
+ collapse on scroll, free icon actions with an account menu, and a main action.
367
+ Flat on the page ground; once the page scrolls it takes a soft tint and a hairline.
368
+
369
+ ```tsx
370
+ import { Bell, Languages, Plus } from 'lucide-react';
371
+ import { Avatar, TopAppBar } from '@nxgt/material/components';
372
+
373
+ <TopAppBar
374
+ as="header"
375
+ size="large-flexible"
376
+ collapse
377
+ title="Orders"
378
+ subtitle="Acme workspace"
379
+ actions={[
380
+ { label: 'Notifications', icon: <Bell />, onClick: openInbox },
381
+ { label: 'English', icon: <Languages />, group: 'Language', selected: true, placement: 'account' },
382
+ { label: 'Français', group: 'Language', placement: 'account', onClick: () => setLocale('fr') },
383
+ ]}
384
+ trailing={<Avatar alt="Ada Lovelace" fallback="AL" />}
385
+ primaryAction={{ label: 'New order', icon: <Plus />, onClick: createOrder }}
386
+ />
387
+ ```
388
+
389
+ `collapse` and `hideOnScroll` only mark the bar (`data-collapse`,
390
+ `data-hide-on-scroll`): the scroll is followed by the shell, so in `ActivityLayout`
391
+ use `header` instead (see [Top app bar sizes](../guides/app-shell.md#top-app-bar-sizes)).
392
+
393
+ | Prop | Type | Default | Effect |
394
+ | --- | --- | --- | --- |
395
+ | `size` | `'small' \| 'medium-flexible' \| 'large-flexible' \| 'search'` | `'small'` | `small`: one row. `medium-flexible` and `large-flexible`: the `title` on a second row (28 px, and 40 px or 36 px on a phone; up to two lines in large), collapsing into the small row. `search`: the `search` field is the bar's centre. Without a `title` a medium or large bar draws as small |
396
+ | `align` | `'start' \| 'center'` | `'start'` | `center` centres a small bar's title |
397
+ | `title` / `titleId` | `ReactNode` / `string` | — | the bar renders `title` as the page's one `<h1>`; `titleId` is its `id`, to name a region |
398
+ | `subtitle` | `ReactNode` | — | a muted `<p>` right after the `<h1>`, one line, truncated |
399
+ | `titleAfter` | `ReactNode` | — | beside the title: a badge, a status |
400
+ | `leading` / `trailing` | `ReactNode` | — | start of the row (menu or back button) / its end (the avatar) |
401
+ | `actions` | `ReactNode \| TopAppBarAction[]` | — | a node is drawn as given. An array of data (every item a plain object with a string `label`) draws free ghost icon buttons (one on a phone, two from 768 px) and gathers the rest, and every `'account'` action, in a menu opened by `trailing` (named "Account menu") or by a More button when there is no `trailing` |
402
+ | `primaryAction` | `TopAppBarPrimaryAction` | — | `{ label, icon, onClick?, disabled? }`: a `Button` from 768 px; on a phone a `Fab` above `--nxgt-bottom-chrome`, portalled to `document.body` after mount |
403
+ | `search` | `ReactNode` | — | the field of a `'search'` bar; the title stays in the page as an `sr-only` `<h1>` |
404
+ | `tabs` | `ReactNode` | — | a row under the bar: a `Tabs` list |
405
+ | `collapse` / `hideOnScroll` | `boolean` | `false` | mark the bar as collapsing its second row, or hiding on scroll down |
406
+ | `linkComponent` | `LinkComponent` | nearest `LinkComponentProvider`'s, else `<a>` | draws an action with an `href` |
407
+ | `as` | `'div' \| 'header'` | `'div'` | the element drawn; use `'header'` for the page's banner |
408
+ | `variant` | `'small' \| 'center' \| 'medium' \| 'large'` | — | deprecated for `size` and `align`: warns once in development |
409
+ | `headline` | `ReactNode` | — | deprecated for `title`: renders in a non-heading element; warns once in development |
410
+
411
+ `TopAppBarAction` (the items of `actions`):
412
+
413
+ | Field | Type | Effect |
414
+ | --- | --- | --- |
415
+ | `label` | `string` | required: the icon button's accessible name and the menu entry's text |
416
+ | `icon` | `ReactNode` | decorative; the action is named by `label` |
417
+ | `onClick` | `() => void` | the action was chosen |
418
+ | `href` | `string` | makes the action a link, drawn with `linkComponent` |
419
+ | `placement` | `'bar' \| 'account'` | `'bar'` (default): a bar icon while there is room. `'account'`: always in the account menu |
420
+ | `group` | `string` | the menu section ("Language", "Theme"): entries sit under that label, inline (never a submenu, which overflows at 360 px); entries without one come first |
421
+ | `selected` | `boolean` | the current choice of its group: a group of non-link entries where one sets `selected` is a radio group (`menuitemradio`, `aria-checked`); a link entry with `selected` is the current page (`aria-current="page"`) |
422
+ | `disabled` | `boolean` | the action cannot be chosen |
423
+ | `id` | `string` | the key; defaults to `label` |
424
+
425
+ The `trailing` avatar becomes the menu's trigger (a `<button>`), so it must hold no
426
+ link, button or input (a warning in development). New message keys:
427
+ `material.top-app-bar.account` and `material.top-app-bar.more` (en, fr).
428
+
429
+ `BottomAppBar` is a row of icon actions that spaces its children and styles the
430
+ one with `data-state="active"`; it takes a `div`'s props.
364
431
 
365
432
  <img src="../previews/navigation-layout-bottomappbar.webp" height="56" alt="BottomAppBar">
366
433
 
367
434
  ```tsx
368
- import { Book, Camera, Menu, Search } from 'lucide-react';
369
- import { BottomAppBar, IconButton, TopAppBar, Typography } from '@nxgt/material/components';
370
-
371
- <TopAppBar>
372
- <IconButton variant="ghost" aria-label="Open menu"><Menu /></IconButton>
373
- <Typography variant="title-md" emphasized className="flex-1">Inbox</Typography>
374
- <IconButton variant="ghost" aria-label="Search"><Search /></IconButton>
375
- </TopAppBar>
435
+ import { Book, Camera } from 'lucide-react';
436
+ import { BottomAppBar, IconButton } from '@nxgt/material/components';
376
437
 
377
438
  <BottomAppBar>
378
439
  <IconButton data-state="active" aria-label="Camera"><Camera /></IconButton>
@@ -380,22 +441,14 @@ import { BottomAppBar, IconButton, TopAppBar, Typography } from '@nxgt/material/
380
441
  </BottomAppBar>
381
442
  ```
382
443
 
383
- `BottomAppBar` spaces its children and styles the one with
384
- `data-state="active"`; both take a `div`'s props. `TopAppBar` is a bordered row
385
- with a `variant`: `'small'` (the default), `'center'` (the row's content
386
- centred), and `'medium'` or `'large'`, which draw the `headline` on a second
387
- row in display type (without a `headline` they are small). It does not scroll
388
- by itself: `ActivityLayout`'s `header` does that for the shell's own bar (see
389
- the [app shell guide](../guides/app-shell.md#the-app-bar-variants)).
444
+ The root is a column (`flex-col`) whose children sit in a
445
+ `[data-slot="top-app-bar-row"]`: a `justify-between`, `gap-*` or `items-*` in the
446
+ root's `className` does not reach them (aim it at the row), and `title` is the
447
+ bar's `<h1>`, no longer the HTML `title` attribute. A `className` background,
448
+ radius or shadow wins at rest; once scrolled the tint replaces the background.
449
+ The shell's bar takes its height from `--nxgt-header-height`.
390
450
 
391
- ```tsx
392
- import { Menu } from 'lucide-react';
393
- import { IconButton, TopAppBar } from '@nxgt/material/components';
394
-
395
- <TopAppBar variant="large" headline={<h1>Projects</h1>}>
396
- <IconButton variant="ghost" aria-label="Open menu"><Menu /></IconButton>
397
- </TopAppBar>
398
- ```
451
+ Moving from `AppHeader` or the old props: [Moving to TopAppBar](../guides/migrating-to-top-app-bar.md).
399
452
 
400
453
  **Storybook:** `Navigation & layout/TopAppBar`, `Navigation & layout/BottomAppBar` · **Vue:** `TopAppBar`, `BottomAppBar`
401
454
 
@@ -422,7 +475,7 @@ function Shell() {
422
475
  <ActivityLayout
423
476
  items={items}
424
477
  header={{
425
- variant: 'small',
478
+ size: 'small',
426
479
  search: {
427
480
  value: query,
428
481
  onValueChange: setQuery,
@@ -201,10 +201,11 @@ partitionMenu(items); // { content, footer }
201
201
  | `linkComponent` | `AppShellLinkComponent` | React Router `Link` | The link each menu entry renders, given `href` (see below). Defaults to the [`LinkComponentProvider`](../components/primitives.md#linkcomponentprovider)'s link when one is set. |
202
202
  | `headerTitle` | `ReactNode` | — | The header's leading title: the context the app is in (the active organisation's name), right after the phone's menu button, truncated when long; when it is cut off, its full text is the `title` tooltip (#629) |
203
203
  | `titleAfter` | `ReactNode` | — | A node right after the page's title in the app bar, outside its `h1` (a record's `Switcher`); wins over a route's `handle.titleAfter` (see [A slot after the title](#a-slot-after-the-title)) |
204
- | `headerContext` | `ReactNode` | — | A small muted context line (the tenant's name) after the page's title and its slot, from `md` up: outside the `h1`, truncated, its full text the tooltip once cut off. Not shown on a phone, where the menu sheet's `context` carries it (see [A context line](#a-context-line-after-the-title)) |
204
+ | `headerContext` | `ReactNode` | — | The bar's `subtitle`: a small muted context line (the tenant's name) under the page's title, from `md` up: outside the `h1`, truncated, its full text the tooltip once cut off. Not shown on a phone, where the menu sheet's `context` carries it (see [A context line](#a-context-line-after-the-title)) |
205
205
  | `breadcrumb` | `boolean` | `true` | `false` hides the breadcrumb trail, for an error page whose matched routes would show a misleading one (see [Breadcrumbs](#breadcrumbs)) |
206
206
  | `breadcrumbRoot` | `AppBreadcrumbRoot` (`{ label: ReactNode; to: string }`, or `{ label: string; to: string; icon: true }`) | — | The breadcrumb's first crumb, in place of the default home icon: a text link to `to`, or with `icon: true` the home icon alone, linked to `to` and named `label` (see [Breadcrumbs](#breadcrumbs)) |
207
- | `headerActions` | `ReactNode` | — | In the header, before the avatar: notifications, help, `ThemeToggle` |
207
+ | `headerActions` | `ReactNode \| TopAppBarAction[]` | — | In the header, before the avatar: notifications, help, `ThemeToggle`; an array of data draws free icon buttons, the rest in the account menu |
208
+ | `headerPrimaryAction` | `TopAppBarPrimaryAction` (`{ label, icon, onClick?, disabled? }`) | — | The screen's main action in the header: a `Button` from 768 px, a `Fab` on a phone |
208
209
  | `children` | `ReactNode` | — | The routed content, usually `<Outlet />` |
209
210
  | `contentAs` | `'main' \| 'div'` | `'main'` | The content region's element: the page's `<main>` landmark, or a `div` when your app renders its own `<main>` |
210
211
  | `scroll` | `'document' \| 'container'` | `'document'` | Who scrolls: the page (header and sidebar sticky), or the content region inside a shell one screen high |
@@ -212,7 +213,7 @@ partitionMenu(items); // { content, footer }
212
213
  | `menuOpen` | `boolean` | — | Controls the phone's menu sheet: open while `true`. Leave it out and the layout manages the sheet. Only a phone shows the sheet |
213
214
  | `onMenuOpenChange` | `(open: boolean) => void` | — | Called for every change of the sheet: the menu button, Escape, the close button, a followed link, a new location |
214
215
  | `navigation` | `ShellNavigation \| { phone?, tablet?, desktop?, hideOnScroll?, persistKey? }` | `modal-drawer` / `rail` / `rail` | The navigation per window size; see [`navigation` and `header`](#navigation-and-header) |
215
- | `header` | `'small' \| 'center' \| 'medium' \| 'large' \| 'none' \| { variant, collapse?, hideOnScroll?, tabs?, search? }` | `'small'` | The app bar's size, overridable per route with `handle.header`; see [The app bar variants](#the-app-bar-variants) |
216
+ | `header` | `'small' \| 'medium-flexible' \| 'large-flexible' \| 'search' \| 'none' \| { size?, align?, collapse?, hideOnScroll?, tabs?, search? }` | `'small'` | The app bar's size, overridable per route with `handle.header`; the old `'center'`, `'medium'`, `'large'` and `variant` are deprecated; see [Top app bar sizes](#top-app-bar-sizes) |
216
217
  | `backdrop` | `ReactNode \| false` | — | An exception to the theme's background (the page ground, [`--ground`](#the-page-ground), is painted either way), which the layout draws by default (Lavande's: the built-in gradient and blobs). A node replaces it for this screen: usually a `Background` preset from `@nxgt/material/backgrounds` (see [Use a background preset behind the shell](#use-a-background-preset-behind-the-shell)); `false` draws no background, a plain page |
217
218
  | `keepClear` | `boolean \| 'gaps'` | `true` | how the backdrop's glyphs keep clear of the page: `true` drops every glyph within 8px of a surface, a text or a control; `'gaps'` also keeps a glyph that fits in a gap of 24px or more between two surfaces; `false` keeps nothing clear (the edge and the header band still are). See [Keep the backdrop clear of the content](#keep-the-backdrop-clear-of-the-content) |
218
219
 
@@ -250,11 +251,49 @@ const MyLink: AppShellLinkComponent = ({ href, ...props }) => <Link to={href} {.
250
251
  // <ActivityLayout items={items} linkComponent={MyLink}>…</ActivityLayout>
251
252
  ```
252
253
 
254
+ Actions as data: `headerActions` also takes a `TopAppBarAction[]` (`label`,
255
+ `icon`, `onClick` or `href`, `placement: 'bar' | 'account'`, `group`, `selected`,
256
+ `disabled`). The first ones are free icon buttons in the bar (one on a phone, two
257
+ from 768 px); the others, and every `'account'` action, go to the menu opened by
258
+ the `avatar`, which then must hold no link or button. `headerPrimaryAction` is
259
+ the screen's main action:
260
+
261
+ ```tsx
262
+ <ActivityLayout
263
+ items={items}
264
+ avatar={<Avatar src={user.photo} />}
265
+ headerActions={[
266
+ { label: 'Notifications', icon: <Icon name="bell" />, onClick: openInbox },
267
+ { label: 'Help', icon: <Icon name="circle-question" />, href: '/help' },
268
+ { label: 'Sign out', placement: 'account', onClick: signOut },
269
+ ]}
270
+ headerPrimaryAction={{ label: 'New invoice', icon: <Icon name="plus" />, onClick: create }}
271
+ >
272
+ <Outlet />
273
+ </ActivityLayout>
274
+ ```
275
+
253
276
  `className` and the rest of the `div` props (`id`, `data-*`…) go to the outer
254
277
  element, `[data-slot=activity-layout]`. The sticky app bar (menu button,
255
278
  `headerTitle`, `headerActions` and avatar) is `[data-slot=app-header]`, so a
256
279
  test or an accessibility helper can name it without a structural selector;
257
- `AppHeader` carries the same slot when used on its own.
280
+ `AppHeader` (deprecated, use `TopAppBar`) carries the same slot when used on
281
+ its own. The bar is a `TopAppBar` drawn as the page's `<header>` banner; it keeps
282
+ the `app-header*` slot names below, which `TopAppBar` styles too. They become
283
+ `top-app-bar*` at the next major.
284
+
285
+ | `data-slot` | Marks |
286
+ | --- | --- |
287
+ | `app-header` | the bar's root (`<header>`) |
288
+ | `app-header-page-title` | the page's `h1` |
289
+ | `app-header-context` | the muted line under the title (`headerContext`) |
290
+ | `app-header-headline` | the second row of a medium or large bar |
291
+ | `app-header-collapsed-title` | the title's copy in the small row of a collapsing bar |
292
+ | `app-header-tabs` | the tab row under the bar |
293
+ | `app-header-title` | the leading `headerTitle` |
294
+ | `app-header-title-after` | the slot after the title (`titleAfter`) |
295
+ | `app-header-back` | the back arrow |
296
+
258
297
  `avatar` and `headerActions` are props because the shell holds no session:
259
298
  read the user wherever your app keeps it and pass the node.
260
299
 
@@ -328,7 +367,7 @@ today: the menu sheet on a phone, the rail from `md`, a small header.
328
367
  <ActivityLayout
329
368
  items={items}
330
369
  navigation={{ phone: 'modal-drawer', tablet: 'rail', desktop: 'rail' }}
331
- header={{ variant: 'small' }}
370
+ header={{ size: 'small' }}
332
371
  >
333
372
  <Outlet />
334
373
  </ActivityLayout>
@@ -346,9 +385,10 @@ today: the menu sheet on a phone, the rail from `md`, a small header.
346
385
  name alone draws them at every size, the phone included. Every name is drawn:
347
386
  `'modal-drawer'`, `'rail'`, `'rail-expanded'`, `'drawer'`, `'sidebar'`, `'top'`,
348
387
  `'two-level'`, `'floating'`, `'bar'` and `'none'`.
349
- - `header`: `'small'`, `'center'`, `'medium'`, `'large'` or `'none'`, or
350
- `{ variant, collapse, hideOnScroll, tabs, search }`. The five variants and
351
- `hideOnScroll` are drawn (see [The app bar variants](#the-app-bar-variants));
388
+ - `header`: `'small'`, `'medium-flexible'`, `'large-flexible'`, `'search'` or
389
+ `'none'`, or `{ size, align, collapse, hideOnScroll, tabs, search }` (the old
390
+ `'center'`, `'medium'`, `'large'` and `variant` are deprecated). The sizes and
391
+ `hideOnScroll` are drawn (see [Top app bar sizes](#top-app-bar-sizes));
352
392
  `search` and `tabs` are drawn (see [The app bar's search](#the-app-bars-search) and
353
393
  [Tabs under the app bar](#tabs-under-the-app-bar)). A route's `handle.back` draws a
354
394
  back arrow ([The back arrow](#the-back-arrow)).
@@ -676,71 +716,122 @@ the user asked for less motion: skip the slide and snap. The header
676
716
  (`navigation.hideOnScroll`) read it; the shell tracks the scroll only when one of
677
717
  them asks, and `NavigationToolbar` reads it by hand too.
678
718
 
679
- ### The app bar variants
719
+ ### Top app bar sizes
680
720
 
681
- `header` chooses the bar's size, the way Material 3 does. Unset is `'small'`:
682
- today's one row, byte for byte.
721
+ `header` chooses the bar's size, the way Material 3 does, and the shell draws it
722
+ with [`TopAppBar`](../components/navigation-and-layout.md#topappbar). Unset is
723
+ `'small'`: one row, the title beside the actions.
683
724
 
684
725
  ```tsx
685
726
  import { ActivityLayout, type AppShellHandle } from '@nxgt/material/components';
686
727
 
687
- <ActivityLayout items={items} header="large">
728
+ <ActivityLayout items={items} header="large-flexible">
688
729
  <Outlet />
689
730
  </ActivityLayout>
690
731
 
691
732
  // a route opts in, whatever the layout says
692
- export const handle = { title: 'Projects', header: 'large' } satisfies AppShellHandle;
733
+ export const handle = { title: 'Projects', header: 'large-flexible' } satisfies AppShellHandle;
693
734
 
694
- // a bar that goes away on the way down
695
- <ActivityLayout items={items} header={{ variant: 'small', hideOnScroll: true }} />
735
+ // a centred small bar that goes away on the way down
736
+ <ActivityLayout items={items} header={{ size: 'small', align: 'center', hideOnScroll: true }} />
696
737
  ```
697
738
 
698
- Defaults: `variant` is `'small'`; `collapse` is `true` for `'medium'` and
699
- `'large'`; `hideOnScroll` is `false`. Nothing to import beyond `styles.css`: the
700
- bar's CSS ships with it.
701
-
702
- - `'small'`: one row, the title beside the actions.
703
- - `'center'`: the title centred in the room between the menu button and the
704
- actions.
705
- - `'medium'` and `'large'`: the page's title on a second row, in display type
706
- (`large` is taller and bigger, up to two lines), under the small row. As the
707
- page scrolls the headline fades and the bar shrinks into the small row, which
708
- then shows the title (a copy hidden from assistive technology). `collapse:
709
- false` keeps the headline however far the page scrolls.
710
- - `'none'`: no bar. The screen's own `ActivityContent` title is then the page's
711
- `h1`; the phone's menu button goes with the bar, so pair it with a navigation
712
- that has one on its own (`navigation="bar"`).
713
- - `hideOnScroll`: the bar slides away while the page scrolls down (past 64px) and
714
- comes back on the way up. While away it is `inert` and out of the tab order; if
715
- the focus was inside it, the focus moves to the content region.
716
-
717
- A route gives its own through `handle.header` (a variant or the options, like
718
- the prop). The deepest route that declares one wins, and its options win over
719
- the layout's one by one: a route that only names a variant keeps the layout's
720
- `hideOnScroll`. It is read from the matched routes, so the server and the
721
- browser draw the same bar.
722
-
723
- The headline **is** the page's one `h1` (the small row's copy is `aria-hidden`).
739
+ Defaults: `size` is `'small'`, `align` is `'start'`, `collapse` is `true` for the
740
+ two flexible sizes, `hideOnScroll` is `false`. Nothing to import beyond
741
+ `styles.css`: the bar's CSS ships with it.
742
+
743
+ | Size | Use it for | What it draws |
744
+ | --- | --- | --- |
745
+ | `'small'` | most screens, a dense tool | one row: the title beside the actions. `align: 'center'` centres the title between the menu button and the actions |
746
+ | `'medium-flexible'` | a section's landing screen | the page's title on a second row (28 px), under the small row |
747
+ | `'large-flexible'` | a screen that opens a flow or a long list | the title on a second row, bigger (40 px, 36 px on a phone) and up to two lines |
748
+ | `'search'` | a search-first screen | the `header.search` field is the bar's centre; the title stays in the page as a visually hidden `h1`, so the screen keeps its heading |
749
+ | `'none'` | a full-screen tool that draws its own chrome | no bar. The screen's own `ActivityContent` title is then the page's `h1`; the phone's menu button goes with the bar, so pair it with a navigation that has one on its own (`navigation="bar"`) |
750
+
751
+ - **Collapse.** A flexible bar folds its second row into the small row as the
752
+ page scrolls: the headline fades and the bar shrinks over the headline row's
753
+ measured height, the title shrinks to 90% from its start edge, and the small
754
+ row then shows the title (a copy hidden from assistive technology). A scrolled
755
+ bar takes a soft tint and a 1px hairline. `collapse: false` keeps the headline
756
+ however far the page scrolls.
757
+ - **`hideOnScroll`**: the bar slides away while the page scrolls down (past 64px)
758
+ and comes back on the way up. While away it is `inert` and out of the tab order;
759
+ if the focus was inside it, the focus moves to the content region.
760
+ - **Subtitle.** `headerContext` is the bar's subtitle, a muted line under the title
761
+ (see [A context line after the title](#a-context-line-after-the-title)).
762
+ - **Actions and main action.** `headerActions` takes nodes or a `TopAppBarAction[]`,
763
+ and `headerPrimaryAction` the screen's main action (see
764
+ [The app bar](#the-app-bar-a-title-the-actions-the-account)).
765
+
766
+ The bar's height is `--nxgt-header-height` (default `4rem`, see
767
+ [the header height](./theming.md#the-header-height)): the row is that height
768
+ less the bar's own vertical padding, so a custom value resizes the bar, the
769
+ rail's header and the first-paint total together. A `className` on the bar (a
770
+ background, a radius, a shadow) wins over its at-rest look; once the page
771
+ scrolls, the scrolled tint replaces a `className` background. The bar is drawn
772
+ as the page's `<header>`; `title` on a standalone `TopAppBar` is its `<h1>` (it
773
+ was the HTML `title` attribute), and its root is a column whose children sit in
774
+ a `[data-slot="top-app-bar-row"]`, so layout utilities on the root's `className`
775
+ (`justify-between`, `gap-*`, `items-*`) no longer reach them (see
776
+ [Moving to TopAppBar](./migrating-to-top-app-bar.md#what-looks-different)).
777
+
778
+ A route gives its own through `handle.header`: a size (`'large-flexible'`) or the
779
+ options (`{ size, align, collapse, hideOnScroll, tabs, search }`, like the prop).
780
+ The deepest route that declares one wins, and its options win over the layout's
781
+ one by one: a route that only names a size keeps the layout's `hideOnScroll` and
782
+ `align`. It is read from the matched routes, so the server and the browser draw
783
+ the same bar.
784
+
785
+ The title **is** the page's one `h1` (the small row's copy is `aria-hidden`).
724
786
  A medium or large header needs a title in the bar, a route's `handle.title`;
725
787
  without one the bar stays small, so nothing changes for a screen that titles
726
788
  itself with `ActivityContent`. The server always renders the bar expanded and
727
789
  shown; the browser then follows the scroll.
728
790
 
791
+ **The search size.** `header: { size: 'search', search: { placeholder: 'Search projects' } }`
792
+ puts the search field at the bar's centre. A route that sets a `handle.title`
793
+ still gets its `h1`, visually hidden but in the accessibility tree and
794
+ labelling `<main>`; without a title the screen's own `ActivityContent` title is
795
+ the heading, as for any small bar. See [The app bar's search](#the-app-bars-search)
796
+ for the field's props.
797
+
798
+ **Phone and desktop.**
799
+
800
+ - On a phone the menu button is a ghost icon button leading the bar, with no
801
+ brand or logo; the bar keeps one free icon action, the rest go to the account
802
+ menu as inline sections. `headerPrimaryAction` is a floating button above the
803
+ bottom navigation. `headerContext` is hidden (the sheet's `context` covers it).
804
+ - From 768 px two icon actions fit, `headerPrimaryAction` is a `Button` in the
805
+ bar, and `headerContext` shows under the title.
806
+ - Heights: the headline row of a medium bar is 3 rem and a large bar's 6.5 rem
807
+ before it is measured, then follows the title's real height.
808
+
809
+ **Reduced motion.** Under `prefers-reduced-motion: reduce` the collapse snaps
810
+ between the two states at the halfway point, with no morph, no slide for
811
+ `hideOnScroll` and no transition on the bar's tint.
812
+
729
813
  The scroll is the shell's ([Scroll direction](#scroll-direction)): the window,
730
814
  or the content region with `scroll="container"` (a hidden bar then gives its
731
815
  room back). Once per animation frame the bar sets one CSS variable on itself,
732
- `--nxgt-header-progress` (0 expanded, 1 collapsed), through `setProperty`, so
733
- no inline style reaches the markup; the sizes are CSS in `app-header.css`.
734
- Under reduced motion the bar snaps between the two states halfway, with no slide.
735
- A plain shell, with no collapsing or hiding bar, tracks no scroll at all.
736
-
737
- `AppHeader` takes the same as props (`variant`, `collapse`, `hideOnScroll`, the
738
- `headline` node, `tabs` (a node, default none) and `back` (a `ShellBack`,
739
- default none); see [Using the parts](#using-the-parts)), and so does `TopAppBar` (`variant`, `headline`) for a bar
740
- of your own. Known limits: a hidden bar in a `scroll="container"` shell that is
741
- barely taller than its container can flicker at the very bottom (the region
742
- grows, so the scroll clamps); the scroll-driven CSS animation enhancement is not
743
- used, the JavaScript path drives every browser.
816
+ `--nxgt-header-progress` (0 expanded, 1 collapsed), through `setProperty`, and
817
+ `data-scrolled` once the page has moved, so no inline style reaches the markup
818
+ and a frame of scrolling does not re-render. A plain shell, with no collapsing
819
+ or hiding bar, tracks no scroll at all.
820
+
821
+ **Deprecated spellings.** `'center'`, `'medium'`, `'large'` and `variant` in the
822
+ options still work and log a one-time development warning (see
823
+ [troubleshooting](../troubleshooting.md#activitylayout-header-center-medium-or-large-is-deprecated)):
824
+ `'center'` is `{ size: 'small', align: 'center' }`, `'medium'` is
825
+ `'medium-flexible'`, `'large'` is `'large-flexible'`. See
826
+ [Moving to TopAppBar](./migrating-to-top-app-bar.md).
827
+
828
+ `TopAppBar` takes the same as props (`size`, `align`, `collapse`, `hideOnScroll`,
829
+ `title`, `subtitle`, `tabs`, `search`) for a bar of your own; `AppHeader` is its
830
+ deprecated wrapper (see [Using the parts](#using-the-parts)). Known limits: a
831
+ hidden bar in a `scroll="container"` shell that is barely taller than its
832
+ container can flicker at the very bottom (the region grows, so the scroll
833
+ clamps); the scroll-driven CSS animation enhancement is not used, the JavaScript
834
+ path drives every browser.
744
835
 
745
836
  ### The app bar's search
746
837
 
@@ -755,13 +846,13 @@ import { ActivityLayout } from '@nxgt/material/components';
755
846
  <ActivityLayout
756
847
  items={items}
757
848
  header={{
758
- variant: 'small',
849
+ size: 'small',
759
850
  search: { placeholder: 'Search projects', shortcut: '/', onSubmit: (q) => navigate(`/search?q=${q}`) },
760
851
  }}
761
852
  />
762
853
 
763
854
  // a route turns it off, or changes it, like the other options
764
- export const handle = { title: 'Settings', header: { variant: 'small', search: false } };
855
+ export const handle = { title: 'Settings', header: { size: 'small', search: false } };
765
856
  ```
766
857
 
767
858
  The search never changes the shape of the page: the content stays mounted when
@@ -792,7 +883,7 @@ const tabs = (
792
883
  );
793
884
 
794
885
  export function Layout({ items }: { items: MenuItem[] }) {
795
- return <ActivityLayout items={items} header={{ variant: 'small', tabs }} />;
886
+ return <ActivityLayout items={items} header={{ size: 'small', tabs }} />;
796
887
  }
797
888
  ```
798
889
 
@@ -809,13 +900,13 @@ const projectTabs = (
809
900
 
810
901
  export const handle = {
811
902
  title: 'Atlas',
812
- header: { variant: 'small', tabs: projectTabs }, // tabs: null drops the layout's
903
+ header: { size: 'small', tabs: projectTabs }, // tabs: null drops the layout's
813
904
  } satisfies AppShellHandle;
814
905
  ```
815
906
 
816
907
  The option is `header.tabs` (there is no `handle.headerTabs`): a route's `tabs`
817
908
  replaces the layout's whole, `tabs: null` drops them for that route, and a route
818
- that only names a variant keeps the layout's. The row's height is one CSS
909
+ that only names a size keeps the layout's. The row's height is one CSS
819
910
  variable, `--nxgt-header-tabs` (3rem, in `app-header.css`). The bar's whole
820
911
  height (the row, a headline row and the tabs) is `--nxgt-header-total` on the
821
912
  `ActivityLayout` root, measured with a `ResizeObserver` (worked out in CSS from
@@ -1045,14 +1136,14 @@ A multi-tenant app keeps the current tenant visible on a desktop screen with
1045
1136
  ```
1046
1137
 
1047
1138
  - **It is outside the `h1`** (`[data-slot=app-header-context]`), so the
1048
- heading's name stays the page's title; it is truncated (at most 14rem), and
1049
- its full text is the `title` tooltip once it is cut off (#629).
1139
+ heading's name stays the page's title; it sits under the title (the bar's
1140
+ subtitle), is truncated to the title's width, and its full text is the `title` tooltip once it is cut off (#629).
1050
1141
  - **From `md` up only.** On a phone it is hidden: the menu sheet's `context`
1051
1142
  line covers it. The two props are independent, so setting `context` alone
1052
1143
  adds nothing to the bar, and setting both never shows the name twice on one
1053
1144
  screen.
1054
1145
  - **The title truncates first**: with a long title, the slot and the line
1055
- together, the title gives way (two lines on a phone, then an ellipsis); the slot stays whole and the line keeps its cap.
1146
+ together, the title gives way (two lines on a phone, then an ellipsis); the slot stays whole and the line stays under the title.
1056
1147
  - **It needs a page title**: while the bar shows none (no `handle.title`),
1057
1148
  `headerTitle` is the context line and `headerContext` is ignored.
1058
1149
 
@@ -1793,8 +1884,8 @@ export function InvoiceTrail() {
1793
1884
 
1794
1885
  ## Using the parts
1795
1886
 
1796
- `AppSidebar`, `AppHeader` and `AppBreadcrumb` are exported for a layout of your
1797
- own. They take the same `items`; `AppHeader` takes `actions`, `avatar`,
1887
+ `AppSidebar`, `AppHeader` (deprecated, use `TopAppBar`) and `AppBreadcrumb` are
1888
+ exported for a layout of your own. They take the same `items`; `AppHeader` takes `actions`, `avatar`,
1798
1889
  `showMenuIcon` (default `true`), and `menuOpen` / `onMenuOpenChange` to control
1799
1890
  its phone menu sheet, `tabs` (a node, default none: a `TabsNav` drawn as a row
1800
1891
  at the foot of the bar) and `back` (a `ShellBack`, default none: the back arrow,
@@ -1802,6 +1893,10 @@ before the menu button). The lower-level `Sidebar`, `TopAppBar` and
1802
1893
  `Breadcrumb` primitives are on the
1803
1894
  [navigation & layout](../components/navigation-and-layout.md) page.
1804
1895
 
1896
+ The shell draws its bar with `TopAppBar`, as the page's `<header>`. `AppHeader`
1897
+ is deprecated: it stays as a wrapper over `TopAppBar` with the same props, and
1898
+ warns once in development.
1899
+
1805
1900
  `AppHeader` needs a React Router context even on its own: its menu sheet reads
1806
1901
  the location (`useLocation`) to close when it changes, and outside a router
1807
1902
  that hook throws. Render it under your `RouterProvider` — or, in a test, inside
@@ -187,6 +187,8 @@ A fill, a stroke or a gradient stop is a **role**, a **tone** or a free colour.
187
187
  `outline-variant`, `success`, `info`, `warning`, `neutral`. `ROLES` lists them
188
188
  and `ROLE_VARS` gives each one's CSS variable (`on-primary-container` is
189
189
  `--primary-text`, `outline` is `--muted-foreground`).
190
+ The drawing role `surface-container` resolves to `--card`; it is not the
191
+ `--surface-container` theme token (`bg-surface-container`).
190
192
  They follow light and dark, scoped themes and a retheme with no re-render.
191
193
  - **Tones**: `tone('success')`, `tone('success', 'container')` (variants `base`,
192
194
  `text`, `on`, `container`) name a theme tone or an app tone of your own, the
@@ -75,7 +75,7 @@ layout of your own. Every preset, its entries (`bar`, `priority`, `group`,
75
75
  <ActivityLayout
76
76
  items={items}
77
77
  header={{
78
- variant: 'large',
78
+ size: 'large-flexible',
79
79
  hideOnScroll: true,
80
80
  search: { placeholder: 'Search projects', shortcut: '/' },
81
81
  tabs: (
@@ -95,13 +95,13 @@ A route changes its own bar with a handle, and draws a back arrow:
95
95
  ```tsx
96
96
  export const handle = {
97
97
  title: 'Atlas redesign',
98
- header: { variant: 'medium' },
98
+ header: { size: 'medium-flexible' },
99
99
  back: { to: '/projects', label: 'Projects' },
100
100
  } satisfies AppShellHandle;
101
101
  ```
102
102
 
103
103
  The search is a pill with a docked results panel from 768px and a full-screen
104
- view on a phone. See [The app bar variants](app-shell.md#the-app-bar-variants),
104
+ view on a phone. See [Top app bar sizes](app-shell.md#top-app-bar-sizes),
105
105
  [the search](app-shell.md#the-app-bars-search),
106
106
  [the tabs](app-shell.md#tabs-under-the-app-bar) and
107
107
  [the back arrow](app-shell.md#the-back-arrow).