Page-Layout Previews
The 20 try-before-you-apply preview routes under /page-layouts/.
Overview
The Page Layouts menu group (Dashboard → Page Layouts) is a gallery of demonstration pages. Each
route shows exactly one shell option — a sidebar mode, a sidebar theme, a navigation behavior, a widget,
or a feedback/motion setting — applied to a believable, skeleton-shaped dashboard. Opening a preview
temporarily patches the layout config so you experience the option in context; leaving the
/page-layouts/* area restores whatever you actually had configured. It is the safest way to shop
for a look before making it permanent.
There is no bespoke component per route. A single template — LayoutPreviewPage({variant}) — is
parameterized by a variant string and reads its definition from the LAYOUT_PREVIEWS array. That
array is also folded into the app's route registry, so adding an entry creates a real, code-split route
automatically. This is the same "data drives the routes" idiom used elsewhere in the template.
Architecture & files
| File | Responsibility |
|---|---|
src/pages/layouts/LayoutPreviewPage.tsx | The shared template, the LAYOUT_PREVIEWS data array, GROUP_ORDER, the PreviewSkeleton demo content, previewSession, and usePreviewSessionGuard. |
src/routes.tsx | Folds LAYOUT_PREVIEWS into appRoutes (each becomes a real route). This file is eager, not lazy, because the array is read at module-eval to build the routes. |
src/data/menu.ts | The pageLayouts menu group + its five nested subgroups (one nav leaf per preview). |
src/layout/AppLayout.tsx | Calls usePreviewSessionGuard() on the stable shell so the captured baseline is restored on exit. |
src/context/LayoutContext.tsx | Owns the LayoutConfig each preview patches (see Layout System). |
src/locales/en/pages.json | The lp*Desc / lp*Hint copy shown on each preview (keys listed below). |
Usage
Reach the previews from the sidebar (Dashboard → Page Layouts), the ⌘K palette, or by navigating to
a /page-layouts/<variant> route directly. As a developer you never render LayoutPreviewPage by hand —
you add a row to LAYOUT_PREVIEWS and the route + menu leaf appear.
// src/routes.tsx already does this — LAYOUT_PREVIEWS becomes real routes:
import {LAYOUT_PREVIEWS, LayoutPreviewPage} from '@/pages/layouts/LayoutPreviewPage'
const previewRoutes = LAYOUT_PREVIEWS.map((p) => ({
path: p.to,
element: <LayoutPreviewPage variant={p.variant} />,
}))API / Props
PreviewDef shape
Each entry in LAYOUT_PREVIEWS is a plain object:
| Field | Type | Description |
|---|---|---|
variant | LayoutPreviewVariant | Unique id; also the LayoutPreviewPage prop and the trailing URL segment. |
group | PreviewGroup | One of modes / theme / navigation / widgets / feedback (drives grouping + nav). |
to | string | The route path, e.g. /page-layouts/minified. |
navKey | string | nav: i18n key for the sidebar label. |
descKey | string | pages: i18n key for the preview's description paragraph. |
hintKey | string | pages: i18n key for the "try this" hint. |
icon | LucideIcon | Icon shown on the preview header. |
config | Partial<LayoutConfig> | The layout patch applied on mount (against the captured baseline). |
themeMode? | ThemeMode | Force a theme mode while previewing (sidebar-theme previews force 'light'). |
live? | {type: 'options', field, opts} | {type: 'toggle', field} | Optional interactive controls rendered on the page (see below). |
The five groups
Groups (and their sidebar order) come from GROUP_ORDER:
| Group | Sidebar heading (nav: key) | Previews |
|---|---|---|
modes | plSidebarModes | Default, Minified, Dual, Header Only |
theme | plSidebarTheme | Sidebar Light, Sidebar Dark, Sidebar True Black |
navigation | plNavigation | Fixed Navbar, Auto-hide Navbar, Sticky Page Header, Primary Navigation, Accordion Menu, Fixed Footer |
widgets | plWidgets | Chat Bubble, Chat Rail, Cursor Glow |
feedback | plFeedback | Toast Position, Page Transitions, Page Loader, Splash Frequency |
Every preview route
The LayoutConfig column is the exact config patch applied on mount; blank means the preview applies
no persistent patch and is driven purely by its live control. Descriptions are the
lp*Desc strings from src/locales/en/pages.json.
Sidebar Modes (group: 'modes')
| Route | What it demonstrates | LayoutConfig patch |
|---|---|---|
/page-layouts/default | The standard shell: full sidebar left, header on top, content in the main column — the baseline every other preview builds on. | minSidebar:false, dualSidebar:false, headerOnly:false |
/page-layouts/minified | Sidebar collapses to a slim icon rail that expands on hover, giving content more room. | minSidebar:true, dualSidebar:false, headerOnly:false |
/page-layouts/dual | A narrow icon rail of menu sections beside a secondary panel showing the active section. | dualSidebar:true, minSidebar:false, headerOnly:false |
/page-layouts/header-only | The sidebar is hidden entirely; navigation moves into the top header. | headerOnly:true, minSidebar:false, dualSidebar:false |
Sidebar Theme (group: 'theme' — all force themeMode: 'light' so the sidebar treatment is visible)
| Route | What it demonstrates | LayoutConfig patch |
|---|---|---|
/page-layouts/sidebar-light | A light sidebar matching a light page theme — clean and airy. | darkSidebar:false, oledSidebar:false |
/page-layouts/sidebar-dark | A dark sidebar on a light page — the popular high-contrast admin look. | darkSidebar:true, oledSidebar:false |
/page-layouts/sidebar-black | A true-black (OLED) sidebar — pure-black neutrals for maximum contrast / OLED battery savings. | darkSidebar:true, oledSidebar:true |
Navigation (group: 'navigation')
| Route | What it demonstrates | LayoutConfig patch |
|---|---|---|
/page-layouts/fixed-navbar | The header stays pinned to the top while page content scrolls beneath it. | fixedNav:true, autoHideNav:false |
/page-layouts/auto-hide-navbar | The header hides on scroll-down and reappears on scroll-up. | autoHideNav:true, fixedNav:true, stickyPageHeader:false |
/page-layouts/sticky-header | The slim page header sticks just below the top navigation as you scroll. | stickyPageHeader:true, autoHideNav:false |
/page-layouts/primary-nav | Shows the primary navigation links (Home, mega-menus) in the top header. Live toggle. | showPrimaryNav:true |
/page-layouts/accordion-menu | Sidebar groups behave as an accordion — opening one closes the others. Live toggle. | accordionMenu:true |
/page-layouts/fixed-footer | The footer is pinned to the bottom of the viewport instead of sitting at page end. | fixedFooter:true |
Widgets (group: 'widgets')
| Route | What it demonstrates | LayoutConfig patch |
|---|---|---|
/page-layouts/chat-bubble | A floating chat launcher pinned bottom-right, opening a messenger panel. | chatBubble:true, chatRail:false |
/page-layouts/chat-rail | A slim right-pinned rail of contacts with pop-out chat windows. | chatRail:true, chatBubble:false |
/page-layouts/cursor-glow | An ambient radial glow that softly follows the cursor (desktop only). | cursorGlow:true |
Feedback & Motion (group: 'feedback' — each has interactive live controls; no persistent patch)
| Route | What it demonstrates | Live control |
|---|---|---|
/page-layouts/toast-position | Choose where toast notifications appear; click a position to fire a demo toast there. | options → toastPosition |
/page-layouts/page-transition | Choose the animation played when navigating between pages. | options → pageTransition |
/page-layouts/page-loader | Choose the splash / route-loader style; clicking a style replays the full-screen splash. | options → pageLoader |
/page-layouts/splash-frequency | Choose how often the branded splash appears (once per session vs every load). | options → splashFrequency |
Configuration & customization
How a preview applies & reverts
- Apply on mount, against a captured baseline. Each preview patches the layout as
{...baseline, ...preview.config}(pluspreview.themeModewhen set), so it always shows a clean state — no leftover flags from the previously-viewed preview. - Revert is owned by a module-level
previewSessionplususePreviewSessionGuard(), which is called inAppLayout(the stable component). The routed subtree remounts on every navigation via the keyedPageTransition, so the baseline is captured once on first entry into/page-layouts/*and restored when you navigate away from the group. - One entry toast. A single informational toast fires on entry to the preview area (guarded by the session), so hopping between previews never stacks toasts.
Live controls (live)
Some previews render interactive controls that apply their value live instead of (or in addition to) a
static config patch:
{type: 'toggle', field}— an on/off switch bound to a booleanLayoutConfigkey. Used by Primary Navigation (showPrimaryNav) and Accordion Menu (accordionMenu).{type: 'options', field, opts}— a labelled pill group (reusing thecustomizer:option labels). Used by Toast Position (toastPosition), Page Transitions (pageTransition), Page Loader (pageLoader), and Splash Frequency (splashFrequency). Clicking a toast-position pill fires a demo toast; clicking a page-loader pill replays the splash by dispatching thewindoweventapp:replay-splash(whichAppSplashlistens for).
Adding a preview
Add one object to LAYOUT_PREVIEWS in src/pages/layouts/LayoutPreviewPage.tsx:
{
variant: 'my-option',
group: 'navigation',
to: '/page-layouts/my-option',
navKey: 'layoutMyOption', // add nav:layoutMyOption
descKey: 'lpMyOptionDesc', // add pages:lpMyOptionDesc
hintKey: 'lpMyOptionHint', // add pages:lpMyOptionHint
icon: SomeIcon,
config: {myFlag: true}, // a Partial<LayoutConfig>
// live: {type: 'toggle', field: 'myFlag'}, // optional interactive control
}The route (routes.tsx) and the sidebar leaf (via menu.ts) follow automatically once the i18n keys
exist. Add the matching LayoutConfig key first (see Layout System).
Examples
Force a specific theme while previewing (the sidebar-theme previews do this so the dark/OLED sidebar is actually visible on a light page):
{
variant: 'sidebar-black',
group: 'theme',
to: '/page-layouts/sidebar-black',
navKey: 'layoutSidebarBlack',
descKey: 'lpSidebarBlackDesc',
hintKey: 'lpSidebarBlackHint',
icon: MoonStar,
themeMode: 'light', // show it against a light page
config: {darkSidebar: true, oledSidebar: true},
}Best practices
- Previews are presentation-only. They never write the persisted customizer config — that's why
leaving the area cleanly reverts. Don't add a preview that permanently mutates
localStorage. - Patch against the baseline, not the live config. Reuse the
{...baseline, ...config}pattern so a preview never inherits stray flags from a sibling. - Reuse the shared option tables (
customizerOptions.ts) forliveoption pills so labels match the Customizer and stay i18n-driven. - Keep the demo content skeletal.
PreviewSkeletonis intentionally shimmer-shaped dashboard content (with aScrambleText"demo content" caption) — not a loading state and not real data.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Your real layout changed after visiting previews | The revert guard didn't run | Ensure usePreviewSessionGuard() stays called in AppLayout (the stable shell), not in a routed child |
| A preview shows leftover flags from the last one | Patch applied against live config, not the baseline | Apply {...baseline, ...preview.config} |
| The entry toast fires repeatedly | Toast not gated by the preview session | Keep the single-toast guard on previewSession |
| A new preview has no route/menu leaf | Missing i18n keys or not in LAYOUT_PREVIEWS | Add the nav:/pages: keys and the array entry |
| Dark/OLED sidebar preview looks like normal dark mode | themeMode not forced to 'light' | Set themeMode: 'light' on sidebar-theme previews |
FAQ
Do previews change my saved settings? No. They apply temporarily and revert when you leave the
/page-layouts/* area. Make a look permanent in the Customizer or
Layout Settings.
Why are these routes eager (not React.lazy)? routes.tsx reads LAYOUT_PREVIEWS at module-eval to
build appRoutes, so the module can't be lazily imported. It's the one page module (besides errors/*)
that stays eager.
Where does the preview copy live? In the pages i18n namespace — the lp*Desc / lp*Hint keys in
src/locales/<lng>/pages.json. Labels are the nav: and customizer: keys.
Can I preview two options at once? Not from the gallery — each route demonstrates one option. Combine options in the Customizer, which previews everything together live.
Notes for designers & content editors
- Copy (each preview's description + hint) is i18n text — edit
lp*Desc/lp*Hintinsrc/locales/<lng>/pages.json, not the component. - The demo content is deliberately abstract (token-based
Skeletonshimmer) so the layout reads, not fake data — keep it that way when tweaking. - Colors come from tokens; the previews inherit the active skin/theme automatically.
Related
Layout System
the LayoutConfig keys every preview patches, presets, and the shell
Sidebar & Navigation
the pageLayouts menu group + accordion behavior
Customizer & Layout Settings
where you make a previewed look permanent
Animation & Effects
page transitions, splash/loaders behind the Feedback previews
Appearance & layouts (Help Center)
the end-user walkthrough
Was this page helpful?
