Layout

The page frame: Header, Sider, Content and Footer, with a sider that collapses and reports when it does.

Usage

When to use Layout, what each of its parts is for, and the decisions it leaves to you.

When to use

Layout is the page frame: a flex container plus four regions (Header, Sider, Content, Footer) that carry a background and a flex direction. It has no gutter, no column count and no spacing scale. The part that does the most is Sider, which collapses and reports when the window crosses a breakpoint.
  • The application shell: a bar across the top, a navigation rail down the side, a body that scrolls, a footer under all of it.
  • Any frame whose rail collapses. collapsible plus breakpoint is the reason to use this component instead of two divs.
  • A full-height frame a router renders pages into, so the shell survives navigation instead of remounting with every route.
Use something else when
  • It is a region inside a page, not the page. A column set with gutters is Row and Col, and a row of controls is Flex. Layout gives you neither a gutter nor a responsive column span, and Content renders a <main>, which a page is only allowed one of.
  • Nothing about Sider is wanted. A fixed rail beside a body is <div className="grid grid-cols-[240px_1fr]">, and it arrives with no dark default, no collapse state to own and no <aside> you then have to explain to a screen reader.
  • The rail should cover the content on small screens. Sider collapses in the flow: it narrows and the content widens. A panel that slides over the page with a scrim and a focus trap is a Drawer.
  • You wanted a navigation component. Layout places the rail, and Menu fills it. Sider knows the width and the collapsed flag, and it passes that flag down, but it draws no items.

The four parts, and the one nesting rule

A Layout is a column, unless one of its direct children is a Sider. Then it becomes a row. That rule is why the standard shell is two Layouts, not one.
  • Sider beside the content: an outer Layout holding Sider and an inner Layout, with the inner one holding Header, Content and Footer. The outer one is a row because it has a Sider child, and the inner one has none, so it stays a column.
  • Header above the sider: an outer Layout holding Header and an inner Layout, with the inner one holding the Sider and the Content. Now the inner Layout is the row.
  • A Sider that will not sit beside the content is usually one level too deep. Its own parent is the Layout that becomes a row.
None of the five has a height of its own. For a full-page shell, put min-h-dvh on the outer Layout, or the Sider stops where the content ends and leaves a strip at the bottom of the page.

Collapsing the Sider

collapsible puts a bar at the bottom of the Sider and lets it toggle between width (200 by default) and collapsedWidth (80). At 80, an inline Menu still shows its icons once the labels are gone.
  • defaultCollapsed for a Sider that owns its own state. Switch to collapsed plus onCollapse once anything else needs to know: a button in the header, a stored preference, a content column that reflows.
  • onCollapse is called with a second argument, 'clickTrigger' or 'responsive'. Persist the first and ignore the second, or you will save a collapse the reader never asked for.
  • onBreakpoint(broken) is called with true when the window is narrower than the breakpoint. It fires once on mount as well as on every crossing, so treat it as a state report, not an event.
  • The steps are xs 480, sm 576, md 768, lg 992, xl 1200, xxl 1600. The kit's Tailwind breakpoints use the same numbers, so a lg: utility and a breakpoint="lg" Sider change at the same width.
  • breakpoint collapses the Sider by itself; you do not have to wire onBreakpoint back into collapsed. Wire it anyway if something outside the Sider has to move with it. The breakpoint reads the browser window, not the width of the box the Sider sits in.

collapsedWidth={0} and what it hides

Set collapsedWidth={0} and collapsing takes the rail away completely. The bar becomes a small tab on the outside edge of the Sider, so there is still a way back. This is the mobile pattern: breakpoint="lg" plus collapsedWidth={0} and the rail is gone below 992px.
  • Zero width is a width, not an unmount. The Sider's children stay in the DOM, only clipped, so every link in the collapsed rail is still focusable and still read aloud. The Accessibility tab has the fix.
  • The tab sits one header height from the top, so it assumes a header above it. Without one, move it with zeroWidthTriggerStyle.
  • The tab is drawn even when collapsible is false, as long as the window is narrower than the breakpoint, so a rail that hid on its own still has a handle.
  • trigger={null} removes the tab too, not just the bar. One prop feeds both slots, so switching the bar off on a zero-width Sider leaves no way to open it.

The trigger and its icons

trigger is one node used in two places: the bar at the bottom of a normal Sider, and the tab on a zero-width one. Their default icons come from your icon pack as follows.
  • A zero-width Sider's tab always shows the pack's menu glyph.
  • On a normal Sider the chevron is defaulted only when you control collapsed. The chevron points one way open and the other way closed, and flips for right-to-left and reverseArrow, so it needs the state. An uncontrolled Sider keeps the built-in chevrons.
  • Your own node always wins. Pass trigger for a labeled bar (“Collapse” beside the chevron), or pass trigger={null} and put the control somewhere else on the page.
  • The bar uses position: fixed at the bottom. That suits a Sider that spans the page. Inside anything smaller (a modal, a split pane, a card) the bar pins to the bottom of the window, so add contain-paint to the frame around the Layout to keep it inside.

Fixed headers and sticky siders

Layout pins nothing for you. A header that stays put and a rail that scrolls on its own are position utilities you add.
  • A fixed Header leaves a hole. Taking it out of the flow removes its height from the column, so the Content needs the same height back as padding. Read the header height from the theme token instead of writing 64 twice.
  • A fixed Sider needs the content offset by hand. Use logical properties so RTL follows on its own (ms-* and start-*, never ml-* and left-*), and remember the offset has to change when the Sider collapses, so derive it from the same state.
  • Prefer sticky to fixed where it works. A sticky top-0 h-screen overflow-auto Sider stays in the flow, so nothing needs offsetting and the rail scrolls on its own. It stops working if an ancestor has overflow: hidden.
  • A scrolling rail needs its own overflow-y-auto on the Sider's children, not on the Sider: the Sider itself carries the collapse transition, and a scrollbar on it fights the width animation.

hasSider, and when you need it

hasSider sets the row direction up front instead of working it out. Most layouts do not need it.
  • A direct Sider child is found on the first paint. That includes a Sider inside a fragment or a conditional.
  • A Sider inside a component of your own is found only after it mounts. The server renders a column, the browser switches to a row after mount, and a wide shell visibly shifts on first load.
  • Pass hasSider on that Layout. It removes the shift.

A page frame against a section frame

The parts are not neutral containers. Content renders a <main>, Header a <header>, Footer a <footer> and Sider an <aside>. Used twice on a page those become two mains and two banners, which is a worse document than the one you started with.
  • One Layout is the page. Put it above the router outlet, once, and let routes render into its Content.
  • A nested Layout is a direction, not a second page. The inner Layout in the standard shell holds the column. It is a plain <div> and costs nothing. A second Header or Content is what duplicates landmarks.
  • For a panel inside the page that happens to want a rail (a settings screen, a docs body, a split editor), use plain elements and a grid. A panel rail usually does not collapse, and the collapse is what Layout adds.