Layout structure

Visual map of where each subcomponent lives in the viewport.

AppShell.Header
AppShell.Navbar
AppShell.Main
AppShell.Aside
AppShell.Footer

Full layout

import { AppShell, Burger, ScrollArea } from '@davaux/ui'

<AppShell
  header={{ height: 56 }}
  navbar={{ width: 260, breakpoint: 'md', burgerId: 'my-burger' }}
  aside={{ width: 200 }}
  footer={{ height: 40 }}
  withBorder
>
  <AppShell.Header>
    <Burger id="my-burger" />
    <span>My App</span>
  </AppShell.Header>

  <AppShell.Navbar>
    <ScrollArea style="flex:1;min-height:0">
      {/* nav links */}
    </ScrollArea>
  </AppShell.Navbar>

  <AppShell.Main>
    {/* page content */}
  </AppShell.Main>

  <AppShell.Aside>
    {/* aside content */}
  </AppShell.Aside>

  <AppShell.Footer>
    <span>© 2025</span>
  </AppShell.Footer>
</AppShell>

Navbar only

Omit any subcomponent you don't need. Here footer and aside are omitted.

<AppShell
  header={{ height: 56 }}
  navbar={{ width: 260, breakpoint: 'sm' }}
  withBorder
>
  <AppShell.Header>...</AppShell.Header>
  <AppShell.Navbar>...</AppShell.Navbar>
  <AppShell.Main>...</AppShell.Main>
</AppShell>

Alt layout

layout="alt" makes the navbar and aside span the full viewport height instead of sitting between the header and footer.

AppShell.Navbar
AppShell.Header
AppShell.Main
AppShell.Footer
AppShell.Aside
<AppShell layout="alt" navbar={{ width: 260 }} header={{ height: 56 }}>
  ...
</AppShell>

Mobile navbar with Burger

Set navbar.breakpoint to a size token. Below that viewport width the navbar becomes a slide-in overlay. Wire a Burger by passing its id to navbar.burgerId — the AppShell's inline script connects them automatically.

<AppShell
  navbar={{ width: 260, breakpoint: 'md', burgerId: 'nav-burger' }}
  header={{ height: 56 }}
>
  <AppShell.Header>
    {/* Burger is hidden on desktop via CSS, visible below the breakpoint */}
    <Burger id="nav-burger" aria-label="Toggle navigation" />
    <span>My App</span>
  </AppShell.Header>
  <AppShell.Navbar>...</AppShell.Navbar>
  <AppShell.Main>...</AppShell.Main>
</AppShell>

Collapsed desktop navbar

Pass collapsed.desktop: true to start with the navbar hidden on desktop. The inline script and burger button toggle it open/closed.

<AppShell
  navbar={{
    width: 260,
    breakpoint: 'md',
    burgerId: 'nav-burger',
    collapsed: { desktop: true },
  }}
  header={{ height: 56 }}
>
  ...
</AppShell>

AppShell.Section

Use AppShell.Section inside AppShell.Navbar or AppShell.Aside to divide the sidebar into fixed-height and scrollable regions. Sections support Style Props, so padding can be applied with shorthand props like p="md".

<AppShell.Navbar>
  {/* Fixed top area — e.g. a search input */}
  <AppShell.Section style="padding: var(--dv-spacing-md)">
    <input placeholder="Search..." />
  </AppShell.Section>

  {/* Scrollable nav links — grows to fill remaining space */}
  <AppShell.Section grow>
    <ScrollArea style="height:100%">
      <nav>{/* links */}</nav>
    </ScrollArea>
  </AppShell.Section>

  {/* Fixed bottom area — e.g. user profile */}
  <AppShell.Section style="padding: var(--dv-spacing-md)">
    <UserProfile />
  </AppShell.Section>
</AppShell.Navbar>

Scrollable navbar / aside

Add scrollable to AppShell.Navbar or AppShell.Aside to automatically wrap the children in a ScrollArea that fills the available height. Use this when the sidebar has a single scrollable region. For the fixed-header + scrollable-middle + fixed-footer pattern, leave scrollable unset and compose with AppShell.Section and ScrollArea manually.

{/* Simple — single scrollable region */}
<AppShell.Navbar scrollable>
  <nav>{/* links */}</nav>
</AppShell.Navbar>

{/* Flexible — fixed top/bottom, scrollable middle */}
<AppShell.Navbar>
  <AppShell.Section p="md">
    <input placeholder="Search..." />
  </AppShell.Section>
  <AppShell.Section grow>
    <ScrollArea style="height:100%">
      <nav>{/* links */}</nav>
    </ScrollArea>
  </AppShell.Section>
  <AppShell.Section p="md">
    <UserProfile />
  </AppShell.Section>
</AppShell.Navbar>

Non-sticky footer

By default AppShell.Footer renders in document flow below Main and scrolls with the page — no footer config needed on AppShell. Add sticky to fix it to the bottom of the viewport instead; in that case also pass footer={ height } so Main and the Navbar reserve the correct space.

{/* Non-sticky footer (default) — no footer config needed */}
<AppShell ...>
  <AppShell.Main>...</AppShell.Main>
  <AppShell.Footer>
    <span>© 2025</span>
  </AppShell.Footer>
</AppShell>

{/* Sticky footer — reserve height in the AppShell config */}
<AppShell footer={{ height: 40 }} ...>
  <AppShell.Footer sticky>
    <span>© 2025</span>
  </AppShell.Footer>
</AppShell>

Surface elevation

Each subcomponent accepts a surface prop (0–5) that maps to the global --dv-surface-N CSS token. The defaults reflect a natural depth hierarchy — Main sits at surface 0 (page canvas), Navbar and Aside at 1 (raised panel), and Header and Footer at 2. Override any part to shift it up or down the elevation scale.

{/* Raise the navbar one level above default */}
<AppShell.Navbar surface={2}>...</AppShell.Navbar>

{/* Drop the header to match the navbar */}
<AppShell.Header surface={1}>...</AppShell.Header>

Responsive padding

Pass a breakpoint object to padding to use different spacing at different viewport widths. Keys follow the same size tokens as breakpoint — unspecified breakpoints inherit from the nearest smaller one.

<AppShell padding={{ base: 'sm', md: 'xl' }} ...>
  ...
</AppShell>

Custom transition

The navbar and aside slide-in animation defaults to 250ms. Pass transitionDuration to speed it up, slow it down, or set it to 0 to disable it.

<AppShell transitionDuration={400} ...>
  ...
</AppShell>

Custom Main element

AppShell.Main renders as <main> by default. Pass component to change the element — useful when <main> is used elsewhere on the page.

<AppShell.Main component="div">
  {/* page content */}
</AppShell.Main>

Burger button

The Burger component is a standalone button — see the Burger docs for its own props. AppShell does not manage its opened state: the inline script on the shell root handles all toggling by reading and writing data-navbar-opened / data-aside-opened attributes directly on the shell element.

AppShell props

PropTypeDefaultDescription
headerAppShellHeaderConfigEnables the fixed header. Requires height.
footer{ height: number | string }Reserves viewport space for a fixed footer. Only needed when AppShell.Footer is sticky (the default). Omit when using sticky={false}.
navbarAppShellNavbarConfigEnables the left sidebar. Requires width.
asideAppShellAsideConfigEnables the right sidebar. Requires width.
padding'xs'|'sm'|'md'|'lg'|'xl'|number|string|AppShellPaddingBreakpoints'md'Inner padding applied to AppShell.Main. Pass a breakpoint object for responsive values.
layout'default' | 'alt''default''alt': navbar and aside span full viewport height.
withBorderbooleantrueAdds borders between header, footer, navbar, and aside.
zIndexnumber100Base z-index. Header/footer = base+2, navbar/aside = base+1.
transitionDurationnumber250Navbar/aside slide animation duration in ms. Set to 0 to disable.
idstring'dv-app-shell'ID of the root element (used by the burger toggle script).
classstringAdditional CSS class names.
styleobjectInline styles on the root element.

AppShellNavbarConfig

KeyTypeDefaultDescription
widthnumber | stringWidth of the navbar (number = px, string = any CSS value).
breakpoint'xs'|'sm'|'md'|'lg'|'xl'Viewport width below which the navbar becomes a slide-in overlay.
collapsed.desktopbooleanStart collapsed on desktop.
collapsed.mobilebooleanStart collapsed on mobile (overlay hidden).
burgerIdstringID of the Burger button that controls the navbar overlay.

AppShellAsideConfig

KeyTypeDefaultDescription
widthnumber | stringWidth of the aside (number = px, string = any CSS value).
breakpoint'xs'|'sm'|'md'|'lg'|'xl'Viewport width below which the aside becomes a slide-in overlay.
collapsed.desktopbooleanStart collapsed on desktop.
collapsed.mobilebooleanStart collapsed on mobile (overlay hidden).
burgerIdstringID of a button that controls the aside overlay.

Breakpoint reference

TokenMax-width
'xs'30em (480 px)
'sm'40em (640 px)
'md'48em (768 px)
'lg'62em (992 px)
'xl'75em (1200 px)

Subcomponent props

All subcomponents support Style Props as well as class, style, and children. The table below lists only the props unique to each subcomponent.

SubcomponentPropTypeDefaultDescription
AppShell.HeaderstickybooleanfalseWhen true, fixes the header to the top of the viewport.
AppShell.Headersurface0–52Surface elevation level. Controls the background via --dv-surface-N.
AppShell.FooterstickybooleanfalseWhen true, fixes the footer to the bottom of the viewport. Requires footer={ height } on AppShell to reserve space.
AppShell.Footersurface0–52Surface elevation level. Controls the background via --dv-surface-N.
AppShell.NavbarscrollablebooleanWraps children in a ScrollArea that fills available height.
AppShell.Navbarsurface0–51Surface elevation level. Controls the background via --dv-surface-N.
AppShell.AsidescrollablebooleanWraps children in a ScrollArea that fills available height.
AppShell.Asidesurface0–51Surface elevation level. Controls the background via --dv-surface-N.
AppShell.Maincomponentstring'main'HTML element to render as the root node.
AppShell.Mainsurface0–50Surface elevation level. Controls the background via --dv-surface-N.

AppShell.Section props

Supports Style Props for shorthand inline style manipulation.

PropTypeDefaultDescription
growbooleanfalseExpands the section to fill remaining space in the sidebar column.
classstringAdditional CSS class names.
styleobjectInline styles.
childrenanySection content.