Layout
App Shell
The page shell that places the Header, main and footer in rows, with side panes beside the main.
The main row takes the free height.
Code
<div class="border rounded-md">
<div class="app min-h-0">
<div class="header header--compact">
<div class="header__bar"><span class="small-heading">Header</span></div>
</div>
<div class="app__workspace">
<div class="sidebar sidebar--subtle">
<div class="sidebar__body"><p class="small-text">Sidebar</p></div>
</div>
<div class="main"><p class="text">The main row takes the free height.</p></div>
</div>
<div class="footer">
<div class="footer__bar"><p class="small-text text-muted">Footer</p></div>
</div>
</div>
</div>
Why it exists
Every page has the same outline: a Header, the main content, a Footer. The shell places those three in rows and gives the free height to the main, so the Footer stays at the bottom of a short page. It also keeps the landmarks in the one place a page needs them.
An app often wants its panes to scroll inside the screen, and that’s the risky part, because a locked region clips whatever doesn’t fit. So anything holding content scrolls, and the lock releases on small or short screens, where panes would be too short to use.
It decides nothing about small-screen navigation. Moving a Sidebar into a drawer means choosing what to duplicate, and that’s the product’s call.
When to use
- Once per page, on
<body>, to place the Header, main and footer. - For an app whose panes scroll inside the screen, with
.app--viewport. - Not to centre or cap content: that is a Wrapper.
- Not for panes inside the content: that is a Split.
Rules
- Landmarks: one shell per page. Its
<header>is the Banner, its<footer>the contentinfo, and there is one<main>. The Skip Link comes first; it is fixed, so it takes no row. - Small screens: nothing becomes a drawer on its own. A Sidebar stacks above the main, or the product hides it (
max-md:hide) and puts the same navigation in a.dialog--draweropened from the Header; see Sidebar. - Never clip a locked region. The shell and workspace hide overflow because their rows and panes are sized to fit; anything holding content (the main, a pane) scrolls instead, so no screen size can strand content out of reach.
- Viewport: in
.app--viewportthe main scrolls; give ittabindex="-1"if it holds nothing focusable near the top, so keyboard users can scroll it after the Skip Link.
Keyboard and screen readers
The shell adds no behaviour. The browser provides this from the elements in it.
- Tab first reaches the Skip Link, which moves past the Header and any Sidebar to the main.
- Landmarks let a screen reader jump straight to the banner, the main and the contentinfo. They come from
<header>,<main>and<footer>, which is why each appears once. - Reading order follows the source. Below 48rem a side pane stacks above the main, in the same order.
- A scrolling main in
.app--viewportneeds focus inside it before arrow keys scroll it. A Skip Link to a main withtabindex="-1"gives it that.
A Whole Page
A whole page, so it can’t be shown live here. The miniature at the top uses <div>s and .min-h-0 for that reason.
<body class="app">
<a href="#main" class="skip-link">Skip to content</a>
<header class="header header--sticky">…</header>
<div class="app__workspace">
<div class="sidebar sidebar--sticky max-md:hide">…</div>
<main id="main" class="main">
<div class="wrapper stack">…</div>
</main>
</div>
<footer class="footer">…</footer>
</body>
Do and Don’t
| Do | Don’t |
|---|---|
Use one shell, one <header>, one <main> and one <footer> per page. | Nest a shell in a shell. |
| Put the Skip Link first. | Give the Skip Link a row. |
Put side panes in .app__workspace. | Add an aside as a direct child of .app. |
| Let the main scroll when its content outgrows a locked shell. | Clip anything that holds content. |
Give a scrolling main tabindex="-1" if nothing near its top takes focus. | Leave keyboard users unable to scroll it. |
In Storybook
See also
- Header: The band at the top of a page or app, holding the Brand, the main navigation and global actions.
- Sidebar: A side panel beside the main content, for section navigation, filters or tools.
- Footer: The band at the bottom of a page or app, with site links, legal text and contact details.
- Wrapper: Centres content, caps its width and keeps it off the screen edge.
- Split: Two panes side by side, a side pane at its preferred width and a main pane taking the rest.
- Dialog: A modal window on top of the page, for a decision, a short form or a drawer of settings.