Components
Content
A deliberately thin component: the semantic <main> element that fills the shell's remaining space. Everything else — padding, scrolling, insets — stays under your control.
What it is
Content renders a <main> with flex-1, so it stretches to fill whatever vertical space the shell’s flex column has left. It does not create a scroll container (the page scrolls the window as usual), does not apply safe-area padding, and does not track scroll position — scroll direction comes from useScrollDirection listening on the window, not from this component. It does clear a fixed Footer for you: a margin-bottom bound to the --appshell-footer-height variable the Footer publishes, resolving to 0px when there is no Footer. A pinned Header needs no matching accommodation — it stays in normal document flow (sticky, not fixed) and already occupies its own space.
import { AppShell, Content, Footer, FooterItem, Header } from "appshell-react";
import { Home, Search, User } from "lucide-react";
export default function App() {
return (
<AppShell safeArea>
<Header behavior="fixed" logo={<span className="font-bold">MyApp</span>} />
{/* p-4 for breathing room — no bottom padding needed, Content already
reserves the tab bar's exact height */}
<Content className="p-4">
<h1 className="text-2xl font-bold">Welcome</h1>
<p className="mt-2 text-muted-foreground">Your page goes here.</p>
</Content>
<Footer variant="tab-bar">
<FooterItem icon={<Home />} label="Home" active />
<FooterItem icon={<Search />} label="Search" />
<FooterItem icon={<User />} label="Profile" />
</Footer>
</AppShell>
);
}style prop — Content accepts only className and children. All spacing is done with utility classes.Clearing fixed bars
Fixed footers overlap the end of the page, but you don’t pad for it yourself: Content reads --appshell-footer-height, the exact pixel height the mounted Footer publishes (any variant — tab bar, floating, mini), and applies it as margin-bottom. Render a different Footer and the margin follows; remove it and the margin resolves back to 0px. Pinned headers need nothing at all — the Header stays in normal flow, so top padding is rarely yours to manage either. Reach for your own bottom padding only when you want extra breathing room beyond the bar’s exact height.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Extra classes. |
| id | string | "appshell-content" | Element id. The default is what the AppShell skip link targets and where focus lands on a route change. |
| children* | ReactNode | — | Page content. |
When to reach for SafeArea
With <AppShell safeArea> the shell already keeps Content’s column inside the device’s safe region, so most pages need nothing more. Reach for SafeArea when a specific region inside your content needs its own insets — a full-bleed map or carousel that must respect the landscape notch, for example — by wrapping just that region with the edges it should pad.