Layouts
Every preset ships a self-contained shell instead of nesting under your site's
navbar and footer. Docs get a sidebar that owns the whole viewport; blog and
changelog get a slim top bar. Either way the chrome is part of the scaffold, so
the surface looks finished the moment you run voxx init.
Everything on this page except the shells' anatomy is Next.js-specific —
layout nesting and root layouts are App Router concepts. Static builds
(voxx build) have no layout system to fight; skip to
Static builds.
The docs shell
Docs scaffold a sidebar layout and the client pieces that drive it:
app/docs/layout.tsx # the shell: sidebar + content grid
app/docs/_voxx/
sidebar-nav.tsx # the page tree
mobile-nav.tsx # drawer behind the ☰ button on small screens
theme-toggle.tsx # light/dark, persisted to localStorage
The layout renders one grid — sidebar on the left, your page on the right:
<div className="voxx voxx-docs">
<aside className="voxx-docs__nav">
<div className="voxx-docs__nav-inner">
<div className="voxx-docs__nav-header">
{" "}
{/* logo / site title */}
<MobileNav items={tree} title={config.site.title} />
<Link href="/docs" className="voxx-docs__title">
…
</Link>
</div>
<SidebarNav items={tree} /> {/* the page tree */}
<div className="voxx-docs__nav-footer">
{" "}
{/* pinned to the bottom */}
<ThemeToggle />
</div>
</div>
</aside>
{children}
</div>
The slots are plain class names from the Voxx stylesheet, so you can put anything in the header and footer — a logo component, a version picker, a GitHub link. On screens under 768px the sidebar collapses into a sticky top bar: the ☰ button opens the page tree in a drawer, and the footer contents sit on the right.
The blog and changelog shell
Blog and changelog don't need a page tree, so they scaffold a slimmer shell — a sticky top bar over the content:
app/blog/layout.tsx # the shell: title, RSS link, theme toggle
app/blog/_voxx/theme-toggle.tsx # light/dark, persisted to localStorage
<div className="voxx">
<header className="voxx-header">
<Link href="/blog" className="voxx-header__title">
…
</Link>
<div className="voxx-header__actions">
{" "}
{/* right-aligned controls */}
{/* RSS link, shown when features.rss is on */}
<ThemeToggle />
</div>
</header>
{children}
</div>
Same idea as the docs shell at a smaller scale: the title on the left links
back to the index, and .voxx-header__actions holds the right-aligned
controls — the RSS link (rendered when features.rss is on) and the theme
toggle. The slots are plain class names, so swap in whatever your site needs.
Changelog uses the identical bar; only the feed label differs.
Your Next.js root layout wraps the shell
A Next.js segment layout always renders inside every layout above it. If
app/layout.tsx has a navbar and footer, they wrap /docs (or /blog, or
/changelog) too — there is no opt-out, and the surface ends up with two
competing chromes. This bites docs hardest, since the sidebar expects the full
viewport, but it applies to every preset.
Two ways to arrange this:
-
Keep the root layout bare. Put
<html>,<body>, fonts, and providers inapp/layout.tsxand nothing else; move your navbar and footer into a route group layout for the rest of the site:app/ ├── layout.tsx # html/body only — no chrome ├── (site)/ │ ├── layout.tsx # navbar + footer live here │ └── page.tsx # / └── docs/ └── layout.tsx # the Voxx shellThis works, but it's a convention — a navbar added to the root layout later leaks into the surface again.
-
Multiple root layouts. Delete the top-level
app/layout.tsxentirely. Any layout without one above it becomes a root layout, so give each branch its own<html>and<body>:app/ ├── (site)/ │ ├── layout.tsx # root layout #1: html/body + your chrome │ └── page.tsx # / └── docs/ └── layout.tsx # root layout #2: html/body + the Voxx shellNow leakage is structurally impossible — the content branch has no shared ancestor with the rest of your app. The trade-off: navigating between
/and/docsis a full page load instead of a client-side transition, and each root layout declares its own fonts and providers. Top-level files that aren't pages (sitemap.ts,robots.ts, route handlers) stay where they are; they don't need a layout.
This is how voxx itself is arranged — the marketing page and these docs are separate root layouts.
Static builds
voxx build ships the same shells with zero JavaScript. Docs render the
sidebar with your site.title in the header, a <details> dropdown for
navigation on the mobile bar, and dark mode that follows
prefers-color-scheme. Blog and changelog render the same top bar — title
link plus RSS — as part of the HTML. There's nothing to wire up; the chrome is
baked into the rendered output.