

# AppShell

Application chrome that lays out optional banner, top nav, side nav, main content, and aside in a CSS grid. Includes a skip-to-content link and a mobile breakpoint that hides the persistent side column. Wrap pages in `AppShell` instead of hand-rolling landmarks.

## Examples

### Default

Pass `topNav` and `sideNav` slots; children render in the main landmark.

```tsx
import { AppShell, SideNav, TopNav } from '@var-ui/react';

<AppShell
  topNav={<TopNav>…</TopNav>}
  sideNav={<SideNav>…</SideNav>}
>
  Main content
</AppShell>
```

```astro
---
import { AppShell, SideNav, TopNav } from '@var-ui/astro';
---
<AppShell>
  <TopNav slot="topNav">…</TopNav>
  <SideNav slot="sideNav">…</SideNav>
  Main content
</AppShell>
```

```html
<div data-var-ui-app-shell class="var-ui-app-shell">…</div>
```

### Alt layout

Set `layout="alt"` so the side column spans the full viewport height and the header sits above main only — useful when the side nav is the primary chrome.

```tsx
import { AppShell, SideNav, TopNav } from '@var-ui/react';

<AppShell
  layout="alt"
  contentPadding={3}
  topNav={<TopNav heading={<TopNav.Heading heading="Acme" />}>…</TopNav>}
  sideNav={<SideNav header={<SideNav.Heading heading="Workspace" />}>…</SideNav>}
>
  Main content
</AppShell>
```

```astro
<!-- No Astro binding yet — use @var-ui/react -->
```

```html
<!-- No HTML demo yet — use @var-ui/react -->
```

## Props

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| aside | ReactNode | no |  | Optional right rail (e.g. an `Outline`). Hidden below `mobileBreakpoint`. |
| asideCollapsed | boolean \| undefined | no |  | Fully hides the aside column. |
| banner | ReactNode | no |  | Full-width banner above the top nav / content frame. |
| children | ReactNode | no |  | Rendered inside the `<main>` landmark. |
| className | string \| undefined | no |  |  |
| contentPadding | AppShellContentPadding \| undefined | no | 0 | Main content padding — design-token space step. |
| disabled | boolean \| undefined | no |  | When `true`, renders only the main landmark — no chrome slots, skip link, or mobile provider wrapper. Useful for auth and onboarding pages. |
| headerHidden | boolean \| undefined | no |  | Hides the top nav (pairs with `headroom` or manual scroll logic). |
| headerOffset | boolean \| undefined | no |  | When `true` (default), a hidden header still reserves grid space. When `false`, the top row collapses so main expands into the header band. |
| headroom | boolean \| UseHeadroomOptions \| undefined | no |  | Scroll-driven header hide — sets `headerHidden` from `useHeadroom`. Observes the shell `<main>` scroll container. |
| height | "fill" \| "auto" \| undefined | no | 'fill' |  |
| layout | AppShellLayout \| undefined | no |  | `default` — header spans full width above the side column. `alt` — side column spans full viewport height; header sits above main only. |
| mobileBreakpoint | AppShellMobileBreakpoint \| undefined | no | 'md' | Viewport width below which `sideNav` hides and `data-mobile` is set on the root. `'none'` never goes mobile. |
| mobileNav | ReactNode | no |  | A `MobileNav` drawer. Stays mounted at every width — its own `isOpen` (or the shared `MobileNavProvider`) controls visibility. Consumers place a `MobileNav.Toggle` in `topNav`'s `endContent` themselves; `AppShell` doesn't inject one. |
| sideNav | ReactNode | no |  | Rendered in the persistent side column above `mobileBreakpoint`; hidden below it. |
| sideNavCollapsed | boolean \| undefined | no |  | Fully hides the side column on desktop (distinct from `SideNav` icon-only collapse). |
| topNav | ReactNode | no |  | Rendered in the top bar zone, e.g. a `TopNav`. |
| variant | "surface" \| "elevated" \| "section" \| "wash" \| undefined | no | 'elevated' |  |

## Accessibility

`AppShell` renders a skip-to-content link targeting the main landmark (`id` `var-ui-app-shell-main`). Keep meaningful content in `children` so the skip target is useful. The shell wraps slots in `MobileNavProvider` so a `MobileNav` / `MobileNav.Toggle` pair can share open state at the mobile breakpoint without extra wiring.
