MenuChevron Down
Layout - Docs - Artefact

Layout

Layout
Presentational

Introduction

Nest a <Layout> inside another <Layout>'s content or nested children to build composite shells, e.g. an outer Layout with a page header/footer and an inner Layout holding a sidenav rail.

Usage

Header + Content + Footer

With no sider, the parts stack in a single column.

Header

Content

Footer

import { Layout } from "../components/ui";

export default function MyPage() {
  return (
    <Layout
      header={<SiteHeader />}
      content={<Article />}
      footer={<SiteFooter />}
    />
  );
}

Header + Sider + Content

Passing sider wraps it and content in a row. siderWidth picks a rail width (sm 14rem, md 16rem default, lg 18rem); siderHideBelow hides the rail under a breakpoint for small screens.

Dashboard

Content next to the sider

import { Layout } from "../components/ui";

export default function MyPage() {
  return (
    <Layout
      header={<SiteHeader />}
      sider={<Sidenav />}
      siderWidth="sm"
      siderHideBelow="md"
      content={<Article />}
    />
  );
}

Sticky header and sider

stickyHeader pins the header to the top of the page scroll. stickySider pins the sider below it and scrolls its own overflow — useful for a long nav next to short content, or vice versa. fullHeight fills the viewport for the outermost page shell.

import { Layout } from "../components/ui";

export default function DocsPage() {
  return (
    <Layout
      fullHeight
      stickyHeader
      stickySider
      header={<SiteHeader />}
      sider={<DocsSidenav />}
      siderHideBelow="md"
      content={<Article />}
    />
  );
}

Nested Layout

Nest a <Layout> inside content when the header/footer span the full width but only part of the body needs a sider rail:

import { Layout } from "../components/ui";

export default function MyPage() {
  return (
    <Layout
      header={<SiteHeader />}
      content={
        <Layout sider={<Sidenav />} content={<Article />} />
      }
      footer={<SiteFooter />}
    />
  );
}

CMS Page Builder

This component is available as a layout block in the Page Builder (content/pages/*.json). header, sider, content, and footer are each a list of component blocks — but which block types are offered depends on how deep the layout itself sits, same as any other container in the CMS's ~4-level nesting cap:

  • Root-level — a layout placed directly in a page's top-level content — accepts any block type in these four parts, including containers (Stack, Card, …) and another nested layout:

    {
      "type": "layout",
      "siderWidth": "sm",
      "siderHideBelow": "md",
      "header": [
        { "type": "heading", "text": "Dashboard", "as": "h3", "size": "lg" }
      ],
      "sider": [
        {
          "type": "stack",
          "direction": "vertical",
          "gap": "2",
          "children": [
            { "type": "link", "text": "Overview", "href": "#" },
            { "type": "link", "text": "Reports", "href": "#" }
          ]
        }
      ],
      "content": [
        { "type": "text", "content": "Pick a page from the rail on the left." }
      ],
      "footer": [
        { "type": "text", "size": "sm", "content": "© 2026 Acme" }
      ]
    }
    
  • Nested one level deeper — a layout placed inside another container's children (e.g. a Stack or Card) — restricts header/sider/ content/footer to leaf components only (Button, Badge, Text, Link, Icon, …): the CMS editing UI's nesting cap bottoms out there, so it can't offer another container inside them:

    {
      "type": "stack",
      "children": [
        {
          "type": "layout",
          "header": [
            { "type": "heading", "text": "Card sub-panel", "as": "h4", "size": "sm" }
          ],
          "content": [
            { "type": "text", "content": "Leaf blocks only at this depth." }
          ]
        }
      ]
    }
    

Leaving a part's list empty (or omitting it) skips that part's wrapper element entirely — an untouched optional field like siderWidth/siderHideBelow arrives as "" from the CMS and is treated as unset, falling back to the component default. This depth limit is a constraint of the CMS editing UI only — app/components/page-registry.tsx's renderer itself has no depth limit, so a hand-edited JSON file can nest a layout (or anything else) arbitrarily deep and it will still render correctly.

Props

Layout

PropTypeDescription
header`string | JSX.Element`Rendered inside a semantic <header> above the body. (Shorthand API only)
sider`string | JSX.Element`Rendered inside a semantic <aside> rail. Its presence switches the body to a row of sider + content. (Shorthand API only)
content`string | JSX.Element`Rendered inside a semantic <main>. children are appended after it. (Shorthand API only)
footer`string | JSX.Element`Rendered inside a semantic <footer> below the body. (Shorthand API only)
fullHeightbooleanFill the viewport height — for the outermost page shell.
stickyHeaderbooleanPin the header to the top of the page scroll.
stickySiderbooleanPin the sider below a sticky header; it scrolls internally.
siderWidth`"sm" | "md" | "lg"`Sider rail width: sm (14rem), md (16rem, default), lg (18rem).
siderHideBelow`"sm" | "md" | "lg"`Hide the sider under this breakpoint. Pair with an in-flow disclosure.
hasSiderbooleanManually force layout direction to row. (Compound API)
headerClassstringExtra class for the <header> part. (Shorthand API only)
siderClassstringExtra class for the <aside> part. (Shorthand API only)
contentClassstringExtra class for the <main> part. (Shorthand API only)
footerClassstringExtra class for the <footer> part. (Shorthand API only)
bodyClassstringExtra class for the row wrapper around sider + content (only rendered when sider is set).
childrenanyChildren to render inside the layout shell.
classstringCustom CSS classes for the root element.