Marble UI
← Components

App Shell / Layout

Preview: Inset

Page frame: sidebar + header + scrolling main region with the right landmarks.

Import

import { AppShell } from '@marbleui/ui'

Props

<AppShell>

PropTypeDefaultDescription
children *requiredReactNode—
classNamestring—
headerReactNode—
sidebarReactNode—
variant'flush' | 'inset''flush'"inset": content sits in a rounded panel next to the sidebar. "flush": edge to edge.

Usage notes

Page frame with an optional sidebar, a header and a scrolling main region. For just a navigation column use Sidebar; for a centered marketing page you don’t need it.

<AppShell sidebar={<Sidebar>{nav}</Sidebar>} header={<Breadcrumbs>{crumbs}</Breadcrumbs>} variant="inset">
  <PageContent />
</AppShell>
  • It fills its parent (h-full), so the parent needs a height, e.g. h-screen.
  • Renders header in a <header> and children in <main id="main">, which scrolls on its own. Use id="main" as the target of a skip link, and use only one AppShell per page.
  • variant is flush (default) or inset, where the content sits in a rounded panel beside the sidebar.
Example source · show
import { Breadcrumb, Breadcrumbs } from '../breadcrumbs'
import { Button } from '../button'
import { SearchField } from '../search-field'
import { Sidebar, SidebarItem, SidebarSection } from '../sidebar'
import { Stat } from '../stat'
import { AppShell } from './app-shell'

const Dot = () => <span className="size-1.5 rounded-full bg-current" />
const Nav = () => (
  <Sidebar collapsible header={<span className="font-semibold">Acme</span>} variant="plain" className="border-r-0 bg-transparent">
    <SidebarSection>
      <SidebarItem href="#" icon={<Dot />} isCurrent>Overview</SidebarItem>
      <SidebarItem href="#" icon={<Dot />}>Customers</SidebarItem>
      <SidebarItem href="#" icon={<Dot />}>Invoices</SidebarItem>
    </SidebarSection>
  </Sidebar>
)

const Page = () => (
  <div className="grid grid-cols-3 gap-3 p-5">
    <Stat label="MRR" value="$48.2k" delta={6.1} />
    <Stat label="Customers" value="1,204" delta={2.3} />
    <Stat label="Churn" value="1.9%" delta={-0.4} invert />
  </div>
)

const Frame = ({ variant }: { variant: 'flush' | 'inset' }) => (
  <div className="h-[26rem] w-[50rem] overflow-hidden rounded-xl border border-border">
    <AppShell
      variant={variant}
      sidebar={<Nav />}
      header={
        <>
          <Breadcrumbs><Breadcrumb href="#">Acme</Breadcrumb><Breadcrumb>Overview</Breadcrumb></Breadcrumbs>
          <SearchField aria-label="Search" className="ml-auto w-56" variant="filled" shortcut="⌘K" />
          <Button size="sm">New invoice</Button>
        </>
      }
    >
      <Page />
    </AppShell>
  </div>
)

export const styles = { Inset: () => <Frame variant="inset" />, Flush: () => <Frame variant="flush" /> }

Source: packages/ui/src/app-shell/app-shell.tsx

Catalog

Category
layout
Priority
Core
Primitive
composed