Skip to content
kerfjsGitHub

Shell

The frame of an app: header, navigation and main area, reshaping itself from phone to desktop.

SourcePackage @kerf-js/veneer

Preview

tsx
import { state } from '@kerf-js/core'import {  Badge,  Button,  Card,  Grid,  Heading,  Row,  Shell,  Stack,  Text} from '@kerf-js/veneer'import type {  IconName,  ShellProps} from '@kerf-js/veneer'export function App() {  const page = state('projects')  const nav = kerfNav((id) => (page.value = id))  return (    <AppWindow height={600}>      <Shell        island        label="kerf"        brand={{ name: 'kerf', mark: <MARK /> }}        title={pageOf[page.value]!}        crumbs={[          { label: 'Acme', href: '#' },          { label: pageOf[page.value]! },        ]}        nav={nav}        foot={[          {            id: 'settings',            label: 'Settings',            icon: 'settings',            onSelect: () => (page.value = 'settings'),          },        ]}        current={page.value}        actions={          <Button            size="sm"            icon="rocket"          >            Deploy          </Button>        }        account={ACCOUNT}      >        <Heading          level={1}          role="heading"        >          {pageOf[page.value]!}        </Heading>        <Grid          min="sm"          gap="related"        >          {PROJECTS.slice(0, 6).map((p) => (            <Card key={p.id}>              <Stack gap="hair">                <Row                  justify="between"                  gap="tight"                >                  <Text role="label">{p.name}</Text>                  <Badge                    tone={TONE_OF[p.status]}                    dot                  >                    {p.status}                  </Badge>                </Row>                <Text                  role="caption"                  subtle                >                  {p.region} · {p.latency} ms                </Text>              </Stack>            </Card>          ))}        </Grid>      </Shell>    </AppWindow>  )}const PEOPLE = [  {    id: 'mara',    name: 'Mara Lindqvist',    email: 'mara@kerf.dev',    role: 'Owner',    team: 'Platform',    initials: 'ML',  },  {    id: 'jun',    name: 'Jun Tanaka',    email: 'jun@kerf.dev',    role: 'Admin',    team: 'Platform',    initials: 'JT',  },  {    id: 'priya',    name: 'Priya Raman',    email: 'priya@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'PR',  },  {    id: 'tomas',    name: 'Tomás Oliveira',    email: 'tomas@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'TO',  },  {    id: 'lena',    name: 'Lena Fischer',    email: 'lena@kerf.dev',    role: 'Member',    team: 'Data',    initials: 'LF',  },  {    id: 'noor',    name: 'Noor Haddad',    email: 'noor@kerf.dev',    role: 'Viewer',    team: 'Data',    initials: 'NH',  },  {    id: 'sam',    name: 'Sam Okafor',    email: 'sam@kerf.dev',    role: 'Member',    team: 'Mobile',    initials: 'SO',  },  {    id: 'ines',    name: 'Inés Vidal',    email: 'ines@kerf.dev',    role: 'Viewer',    team: 'Mobile',    initials: 'IV',  },]type Status =  | 'Live'  | 'Building'  | 'Failed'  | 'Paused'const PROJECTS: {  id: string  name: string  owner: string  region: string  status: Status  deploys: number  uptime: number  latency: number  cost: number  updated: string}[] = [  {    id: 'storefront',    name: 'storefront',    owner: 'Priya Raman',    region: 'eu-west',    status: 'Live',    deploys: 412,    uptime: 99.98,    latency: 84,    cost: 1240,    updated: '2026-10-31',  },  {    id: 'checkout',    name: 'checkout',    owner: 'Tomás Oliveira',    region: 'eu-west',    status: 'Live',    deploys: 288,    uptime: 99.95,    latency: 112,    cost: 980,    updated: '2026-10-31',  },  {    id: 'search-api',    name: 'search-api',    owner: 'Lena Fischer',    region: 'us-east',    status: 'Building',    deploys: 196,    uptime: 99.91,    latency: 146,    cost: 1610,    updated: '2026-10-30',  },  {    id: 'billing',    name: 'billing',    owner: 'Mara Lindqvist',    region: 'us-east',    status: 'Live',    deploys: 154,    uptime: 99.99,    latency: 73,    cost: 720,    updated: '2026-10-29',  },  {    id: 'mobile-bff',    name: 'mobile-bff',    owner: 'Sam Okafor',    region: 'ap-south',    status: 'Failed',    deploys: 233,    uptime: 99.42,    latency: 208,    cost: 860,    updated: '2026-10-29',  },  {    id: 'notifier',    name: 'notifier',    owner: 'Jun Tanaka',    region: 'eu-north',    status: 'Live',    deploys: 97,    uptime: 99.97,    latency: 58,    cost: 310,    updated: '2026-10-27',  },  {    id: 'reports',    name: 'reports',    owner: 'Noor Haddad',    region: 'us-west',    status: 'Paused',    deploys: 41,    uptime: 99.8,    latency: 321,    cost: 190,    updated: '2026-10-22',  },  {    id: 'edge-cache',    name: 'edge-cache',    owner: 'Jun Tanaka',    region: 'global',    status: 'Live',    deploys: 318,    uptime: 100,    latency: 21,    cost: 1430,    updated: '2026-10-31',  },  {    id: 'auth',    name: 'auth',    owner: 'Mara Lindqvist',    region: 'eu-west',    status: 'Live',    deploys: 176,    uptime: 99.99,    latency: 66,    cost: 540,    updated: '2026-10-28',  },  {    id: 'ingest',    name: 'ingest',    owner: 'Lena Fischer',    region: 'us-east',    status: 'Live',    deploys: 264,    uptime: 99.93,    latency: 131,    cost: 2120,    updated: '2026-10-30',  },]const TONE_OF: Record<  Status,  'positive' | 'info' | 'critical' | 'neutral'> = {  Live: 'positive',  Building: 'info',  Failed: 'critical',  Paused: 'neutral',}const min = 60_000function AppWindow({  height,  wide,  children,}: {  height: number  wide?: number  children?: unknown}) {  const box = () =>    `--veneer-viewport-block:${height}px;` +    `block-size:${height}px;` +    (wide      ? `inline-size:min(100%,${wide}px);` +        'margin-inline:auto;'      : '') +    'overflow:auto;contain:layout paint;' +    'border:var(--veneer-border-hairline) solid ' +    'var(--veneer-color-border);' +    'border-radius:var(--veneer-radius-surface)'  return <div style={box()}>{children}</div>}const MARK = () => (  <svg    viewBox="0 0 24 24"    aria-hidden="true"  >    <rect      x="3"      y="3"      width="8"      height="8"      rx="2"      fill="var(--veneer-color-accent-solid)"    />    <rect      x="13"      y="13"      width="8"      height="8"      rx="2"      fill="var(--veneer-color-accent-solid)"    />  </svg>)const ME = PEOPLE[0]!const ACCOUNT: ShellProps['account'] = {  name: ME.name,  email: ME.email,  initials: ME.initials,  items: [    { label: 'Profile', icon: 'user' },    { label: 'Preferences', icon: 'settings' },    'separator',    {      label: 'Sign out',      icon: 'log-out',      tone: 'critical',    },  ],}function kerfNav(go: (id: string) => void) {  const item = (    id: string,    label: string,    { icon, count }: { icon: IconName; count?: number },  ) => ({    id,    label,    icon,    count,    onSelect: () => go(id),  })  return [    {      label: 'Workspace',      items: [        item('projects', 'Projects', { icon: 'layers' }),        item('deploys', 'Deploys', {          icon: 'rocket',          count: 2,        }),        item('activity', 'Activity', {          icon: 'activity',        }),        item('team', 'Team', { icon: 'users' }),        item('billing', 'Billing', { icon: 'folder' }),      ],    },    {      label: 'Saved views',      items: [        item('failed', 'Failed builds', {          icon: 'filter',          count: 1,        }),      ],    },  ]}const pageOf: Record<string, string> = {  projects: 'Projects',  deploys: 'Deploys',  activity: 'Activity',  team: 'Team',  billing: 'Billing',  failed: 'Failed builds',  settings: 'Settings',}

Installation

npm install @kerf-js/veneer

Usage

tsx
import { Shell } from '@kerf-js/veneer'

Collapsed to a rail

Bind collapsed to a state to keep it. Press [, or the toggle at the foot of the rail.

tsx
import { state } from '@kerf-js/core'import { Heading, Shell, Text } from '@kerf-js/veneer'import type {  IconName,  ShellProps} from '@kerf-js/veneer'export function Rail() {  const page = state('deploys')  const folded = state<boolean | 'hidden'>(true)  const nav = kerfNav((id) => (page.value = id))  return (    <AppWindow height={420}>      <Shell        island        label="kerf"        brand={{ name: 'kerf', mark: <MARK /> }}        crumbs={[{ label: pageOf[page.value]! }]}        nav={nav}        current={page.value}        collapsed={folded}        account={ACCOUNT}      >        <Heading          level={1}          role="heading"        >          {pageOf[page.value]!}        </Heading>        <Text subtle>          The rail keeps icons and short labels. Its          toggle opens the full sidebar.        </Text>      </Shell>    </AppWindow>  )}const PEOPLE = [  {    id: 'mara',    name: 'Mara Lindqvist',    email: 'mara@kerf.dev',    role: 'Owner',    team: 'Platform',    initials: 'ML',  },  {    id: 'jun',    name: 'Jun Tanaka',    email: 'jun@kerf.dev',    role: 'Admin',    team: 'Platform',    initials: 'JT',  },  {    id: 'priya',    name: 'Priya Raman',    email: 'priya@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'PR',  },  {    id: 'tomas',    name: 'Tomás Oliveira',    email: 'tomas@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'TO',  },  {    id: 'lena',    name: 'Lena Fischer',    email: 'lena@kerf.dev',    role: 'Member',    team: 'Data',    initials: 'LF',  },  {    id: 'noor',    name: 'Noor Haddad',    email: 'noor@kerf.dev',    role: 'Viewer',    team: 'Data',    initials: 'NH',  },  {    id: 'sam',    name: 'Sam Okafor',    email: 'sam@kerf.dev',    role: 'Member',    team: 'Mobile',    initials: 'SO',  },  {    id: 'ines',    name: 'Inés Vidal',    email: 'ines@kerf.dev',    role: 'Viewer',    team: 'Mobile',    initials: 'IV',  },]const min = 60_000function AppWindow({  height,  wide,  children,}: {  height: number  wide?: number  children?: unknown}) {  const box = () =>    `--veneer-viewport-block:${height}px;` +    `block-size:${height}px;` +    (wide      ? `inline-size:min(100%,${wide}px);` +        'margin-inline:auto;'      : '') +    'overflow:auto;contain:layout paint;' +    'border:var(--veneer-border-hairline) solid ' +    'var(--veneer-color-border);' +    'border-radius:var(--veneer-radius-surface)'  return <div style={box()}>{children}</div>}const MARK = () => (  <svg    viewBox="0 0 24 24"    aria-hidden="true"  >    <rect      x="3"      y="3"      width="8"      height="8"      rx="2"      fill="var(--veneer-color-accent-solid)"    />    <rect      x="13"      y="13"      width="8"      height="8"      rx="2"      fill="var(--veneer-color-accent-solid)"    />  </svg>)const ME = PEOPLE[0]!const ACCOUNT: ShellProps['account'] = {  name: ME.name,  email: ME.email,  initials: ME.initials,  items: [    { label: 'Profile', icon: 'user' },    { label: 'Preferences', icon: 'settings' },    'separator',    {      label: 'Sign out',      icon: 'log-out',      tone: 'critical',    },  ],}function kerfNav(go: (id: string) => void) {  const item = (    id: string,    label: string,    { icon, count }: { icon: IconName; count?: number },  ) => ({    id,    label,    icon,    count,    onSelect: () => go(id),  })  return [    {      label: 'Workspace',      items: [        item('projects', 'Projects', { icon: 'layers' }),        item('deploys', 'Deploys', {          icon: 'rocket',          count: 2,        }),        item('activity', 'Activity', {          icon: 'activity',        }),        item('team', 'Team', { icon: 'users' }),        item('billing', 'Billing', { icon: 'folder' }),      ],    },    {      label: 'Saved views',      items: [        item('failed', 'Failed builds', {          icon: 'filter',          count: 1,        }),      ],    },  ]}const pageOf: Record<string, string> = {  projects: 'Projects',  deploys: 'Deploys',  activity: 'Activity',  team: 'Team',  billing: 'Billing',  failed: 'Failed builds',  settings: 'Settings',}

Focus

variant="focus" has no navigation: Close, a title and the step, with dock for the main action.

tsx
import { state } from '@kerf-js/core'import {  Button,  Heading,  Input,  Shell} from '@kerf-js/veneer'export function Focus() {  const step = state(1)  const next = () =>    (step.value = step.value === 3 ? 1 : step.value + 1)  return (    <AppWindow height={420}>      <Shell        island        label="New project"        variant="focus"        title="New project"        step={`Step ${step.value} of 3`}        close={{          label: 'Close',          onClick: () => (step.value = 1),        }}        dock={          <Button onClick={next}>            {step.value === 3              ? 'Create project'              : 'Continue'}          </Button>        }      >        <Heading          level={1}          role="heading"        >          {FOCUS_STEPS[step.value - 1]}        </Heading>        <Input          label="Repository"          defaultValue="kerf/storefront"        />      </Shell>    </AppWindow>  )}const min = 60_000function AppWindow({  height,  wide,  children,}: {  height: number  wide?: number  children?: unknown}) {  const box = () =>    `--veneer-viewport-block:${height}px;` +    `block-size:${height}px;` +    (wide      ? `inline-size:min(100%,${wide}px);` +        'margin-inline:auto;'      : '') +    'overflow:auto;contain:layout paint;' +    'border:var(--veneer-border-hairline) solid ' +    'var(--veneer-color-border);' +    'border-radius:var(--veneer-radius-surface)'  return <div style={box()}>{children}</div>}const FOCUS_STEPS = [  'Connect a repository',  'Choose a region',  'Check your settings',]

Phone

Bottom tabs

The first four items become tabs. The rest move into More, or mark your own with tab.

tsx
import { state } from '@kerf-js/core'import {  Badge,  Heading,  Row,  Shell,  Stack,  Text} from '@kerf-js/veneer'import type {  IconName,  ShellProps} from '@kerf-js/veneer'export function Phone() {  const page = state('projects')  const nav = kerfNav((id) => (page.value = id))  return (    <AppWindow      height={560}      wide={375}    >      <Shell        island        label="kerf"        brand={{ name: 'kerf', mark: <MARK /> }}        title={pageOf[page.value]!}        nav={nav}        current={page.value}        account={ACCOUNT}      >        <Heading          level={1}          role="heading"        >          {pageOf[page.value]!}        </Heading>        <Stack          divided          gap="related"        >          {PROJECTS.slice(0, 5).map((p) => (            <Row              key={p.id}              justify="between"              gap="tight"            >              <Text>{p.name}</Text>              <Badge                tone={TONE_OF[p.status]}                dot              >                {p.status}              </Badge>            </Row>          ))}        </Stack>      </Shell>    </AppWindow>  )}const PEOPLE = [  {    id: 'mara',    name: 'Mara Lindqvist',    email: 'mara@kerf.dev',    role: 'Owner',    team: 'Platform',    initials: 'ML',  },  {    id: 'jun',    name: 'Jun Tanaka',    email: 'jun@kerf.dev',    role: 'Admin',    team: 'Platform',    initials: 'JT',  },  {    id: 'priya',    name: 'Priya Raman',    email: 'priya@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'PR',  },  {    id: 'tomas',    name: 'Tomás Oliveira',    email: 'tomas@kerf.dev',    role: 'Member',    team: 'Web',    initials: 'TO',  },  {    id: 'lena',    name: 'Lena Fischer',    email: 'lena@kerf.dev',    role: 'Member',    team: 'Data',    initials: 'LF',  },  {    id: 'noor',    name: 'Noor Haddad',    email: 'noor@kerf.dev',    role: 'Viewer',    team: 'Data',    initials: 'NH',  },  {    id: 'sam',    name: 'Sam Okafor',    email: 'sam@kerf.dev',    role: 'Member',    team: 'Mobile',    initials: 'SO',  },  {    id: 'ines',    name: 'Inés Vidal',    email: 'ines@kerf.dev',    role: 'Viewer',    team: 'Mobile',    initials: 'IV',  },]type Status =  | 'Live'  | 'Building'  | 'Failed'  | 'Paused'const PROJECTS: {  id: string  name: string  owner: string  region: string  status: Status  deploys: number  uptime: number  latency: number  cost: number  updated: string}[] = [  {    id: 'storefront',    name: 'storefront',    owner: 'Priya Raman',    region: 'eu-west',    status: 'Live',    deploys: 412,    uptime: 99.98,    latency: 84,    cost: 1240,    updated: '2026-10-31',  },  {    id: 'checkout',    name: 'checkout',    owner: 'Tomás Oliveira',    region: 'eu-west',    status: 'Live',    deploys: 288,    uptime: 99.95,    latency: 112,    cost: 980,    updated: '2026-10-31',  },  {    id: 'search-api',    name: 'search-api',    owner: 'Lena Fischer',    region: 'us-east',    status: 'Building',    deploys: 196,    uptime: 99.91,    latency: 146,    cost: 1610,    updated: '2026-10-30',  },  {    id: 'billing',    name: 'billing',    owner: 'Mara Lindqvist',    region: 'us-east',    status: 'Live',    deploys: 154,    uptime: 99.99,    latency: 73,    cost: 720,    updated: '2026-10-29',  },  {    id: 'mobile-bff',    name: 'mobile-bff',    owner: 'Sam Okafor',    region: 'ap-south',    status: 'Failed',    deploys: 233,    uptime: 99.42,    latency: 208,    cost: 860,    updated: '2026-10-29',  },  {    id: 'notifier',    name: 'notifier',    owner: 'Jun Tanaka',    region: 'eu-north',    status: 'Live',    deploys: 97,    uptime: 99.97,    latency: 58,    cost: 310,    updated: '2026-10-27',  },  {    id: 'reports',    name: 'reports',    owner: 'Noor Haddad',    region: 'us-west',    status: 'Paused',    deploys: 41,    uptime: 99.8,    latency: 321,    cost: 190,    updated: '2026-10-22',  },  {    id: 'edge-cache',    name: 'edge-cache',    owner: 'Jun Tanaka',    region: 'global',    status: 'Live',    deploys: 318,    uptime: 100,    latency: 21,    cost: 1430,    updated: '2026-10-31',  },  {    id: 'auth',    name: 'auth',    owner: 'Mara Lindqvist',    region: 'eu-west',    status: 'Live',    deploys: 176,    uptime: 99.99,    latency: 66,    cost: 540,    updated: '2026-10-28',  },  {    id: 'ingest',    name: 'ingest',    owner: 'Lena Fischer',    region: 'us-east',    status: 'Live',    deploys: 264,    uptime: 99.93,    latency: 131,    cost: 2120,    updated: '2026-10-30',  },]const TONE_OF: Record<  Status,  'positive' | 'info' | 'critical' | 'neutral'> = {  Live: 'positive',  Building: 'info',  Failed: 'critical',  Paused: 'neutral',}const min = 60_000function AppWindow({  height,  wide,  children,}: {  height: number  wide?: number  children?: unknown}) {  const box = () =>    `--veneer-viewport-block:${height}px;` +    `block-size:${height}px;` +    (wide      ? `inline-size:min(100%,${wide}px);` +        'margin-inline:auto;'      : '') +    'overflow:auto;contain:layout paint;' +    'border:var(--veneer-border-hairline) solid ' +    'var(--veneer-color-border);' +    'border-radius:var(--veneer-radius-surface)'  return <div style={box()}>{children}</div>}const MARK = () => (  <svg    viewBox="0 0 24 24"    aria-hidden="true"  >    <rect      x="3"      y="3"      width="8"      height="8"      rx="2"      fill="var(--veneer-color-accent-solid)"    />    <rect      x="13"      y="13"      width="8"      height="8"      rx="2"      fill="var(--veneer-color-accent-solid)"    />  </svg>)const ME = PEOPLE[0]!const ACCOUNT: ShellProps['account'] = {  name: ME.name,  email: ME.email,  initials: ME.initials,  items: [    { label: 'Profile', icon: 'user' },    { label: 'Preferences', icon: 'settings' },    'separator',    {      label: 'Sign out',      icon: 'log-out',      tone: 'critical',    },  ],}function kerfNav(go: (id: string) => void) {  const item = (    id: string,    label: string,    { icon, count }: { icon: IconName; count?: number },  ) => ({    id,    label,    icon,    count,    onSelect: () => go(id),  })  return [    {      label: 'Workspace',      items: [        item('projects', 'Projects', { icon: 'layers' }),        item('deploys', 'Deploys', {          icon: 'rocket',          count: 2,        }),        item('activity', 'Activity', {          icon: 'activity',        }),        item('team', 'Team', { icon: 'users' }),        item('billing', 'Billing', { icon: 'folder' }),      ],    },    {      label: 'Saved views',      items: [        item('failed', 'Failed builds', {          icon: 'filter',          count: 1,        }),      ],    },  ]}const pageOf: Record<string, string> = {  projects: 'Projects',  deploys: 'Deploys',  activity: 'Activity',  team: 'Team',  billing: 'Billing',  failed: 'Failed builds',  settings: 'Settings',}

A child page

depth="child" with back swaps the brand for a back button.

tsx
import { state } from '@kerf-js/core'import { Heading, Shell, Text } from '@kerf-js/veneer'import type { IconName } from '@kerf-js/veneer'export function ChildPage() {  const left = state(false)  return (    <AppWindow      height={420}      wide={375}    >      <Shell        island        label="kerf"        depth="child"        title="storefront"        back={{          label: 'Back to projects',          onClick: () => (left.value = !left.value),        }}        brand={{ name: 'kerf' }}        nav={ROOT_NAV}        current="projects"      >        <Heading          level={1}          role="heading"        >          storefront        </Heading>        <Text subtle>          {left.value            ? 'Back pressed. A real app would route ' +              'to the list here.'            : 'On a phone, a child page swaps the ' +              'brand for a back button.'}        </Text>      </Shell>    </AppWindow>  )}const min = 60_000function AppWindow({  height,  wide,  children,}: {  height: number  wide?: number  children?: unknown}) {  const box = () =>    `--veneer-viewport-block:${height}px;` +    `block-size:${height}px;` +    (wide      ? `inline-size:min(100%,${wide}px);` +        'margin-inline:auto;'      : '') +    'overflow:auto;contain:layout paint;' +    'border:var(--veneer-border-hairline) solid ' +    'var(--veneer-color-border);' +    'border-radius:var(--veneer-radius-surface)'  return <div style={box()}>{children}</div>}function kerfNav(go: (id: string) => void) {  const item = (    id: string,    label: string,    { icon, count }: { icon: IconName; count?: number },  ) => ({    id,    label,    icon,    count,    onSelect: () => go(id),  })  return [    {      label: 'Workspace',      items: [        item('projects', 'Projects', { icon: 'layers' }),        item('deploys', 'Deploys', {          icon: 'rocket',          count: 2,        }),        item('activity', 'Activity', {          icon: 'activity',        }),        item('team', 'Team', { icon: 'users' }),        item('billing', 'Billing', { icon: 'folder' }),      ],    },    {      label: 'Saved views',      items: [        item('failed', 'Failed builds', {          icon: 'filter',          count: 1,        }),      ],    },  ]}const ROOT_NAV = kerfNav(() => {})

Showing a shell inside a page

A shell fills the screen by default, which is what you want in an app: put it at the top of your layout and nothing else is needed.

To preview one inside a page, as here, pass island so the page keeps its one <main>, set --veneer-viewport-block to the height you want, and give the box around it contain: layout so the shell's overlays stay inside. The AppWindow helper in these examples does exactly that.

When to use

Use it for

  • The outermost layout of a signed-in app
  • A wizard or checkout: `variant="focus"` drops the navigation

Reach for something else

  • Split, when it is a sidebar inside a page
  • Cover, when the page has no navigation at all

Accessibility

[
Collapses or expands the sidebar
⌘K
Runs `command`, when it is set
Esc
Closes the open navigation
Tab
The first stop is Skip to content
  • Parts are props rather than children, so the focus order is always skip link, brand, header, navigation, main, dock.
  • A change to current announces the new page's h1, so give every page one.
  • With island, main becomes a named region, since a document has one <main>.

API Reference

PropTypeDefaultDescription
variant"focus" | "app"—app (default) · focus: a wizard or checkout, with no nav.
brand{ name: string; href?: string | undefined; mark?: unknown; }—The product's name and mark. Shown in the header, and as the home link.
titleunknown—The phone header's title.
crumbsCrumb[]—The path in the header from tablet up. The last is the current page.
depth"root" | "child"—child puts a back button in the brand's place on a phone, for a page under another.
back{ label: string; href?: string | undefined; onClick?: (() => void) | und…—Phone, depth child: the back button in the brand's place.
close{ label: string; href?: string | undefined; onClick?: (() => void) | und…—Focus variant: Close at the start and the step count at the end.
stepunknown—How far one key press moves it.
command{ label?: string | undefined; hint?: string | undefined; shortcut?: stri…—A search field in the header. It takes over ⌘K.
actionsunknown—Your own buttons in the header.
account{ name: string; email?: string | undefined; initials: string; items?: Me…—The account button and its menu.
switcher{ current: string; workspaces: Workspace[]; onSwitch: (id: string) => vo…—A workspace switcher above the navigation.
navNavGroup[]—Groups of items. The first is the main list; the rest are extras such as saved views, in the sidebar only.
footNavItem[]—Items pinned to the bottom of the navigation.
currentstring—The id of the current item. A change plays the route line and announces the page.
navLabelstring—Names the navigation landmark for screen readers.
collapsedState<boolean | "hidden">—A state: false, true for the rail, or hidden for an edge tab.
labelstring—Names main when island, or the region around a preview.
islandbooleanfalseFor a shell inside a page: main becomes a named region.
dockunknown—A sticky bar under the content, for the page's main action.
overlaysunknown—Dialogs, toasts and palettes, kept outside main.
childrenunknown—What it contains.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="shell"] [data-part="page"]

page

RelatedSplitCoverNavMenu