Shilp Sutra
shell · Shell

AppShell

@devalok/shilp-sutra/shell/app-shellView in Storybook
Live preview coming

Hand-curated previews ship in rolling waves. See it live in Storybook →

Reference

Overview

The frame that lets a bar span above both the sidebar and the content.

SidebarProvider renders a single flex row, so a bar can only live inside the content pane — which is why every shadcn example puts its <header> inside SidebarInset. AppShell is the other arrangement: the bar is a sibling above the row, the way Carbon's Header sits above its SideNav.

It provides layout and surfaces only. Put TopBar in the bar slot and compose the Sidebar primitives in the sidebar slot — AppShell does not replace either.

Subcomponents

ComponentPurpose
AppShellRoot — owns the chrome surface and the arrangement
AppShellBarFull-width bar above everything. Minimum height, never fixed.
AppShellBodyThe row beneath the bar: sidebar alongside canvas
AppShellSidebarFixed-width slot, 16rem or 3rem collapsed
AppShellCanvasThe content pane; becomes an inset panel when variant="inset"

Props

AppShell

variant?: 'flat' | 'inset' chrome?: 'dim' | 'bright' className?: string

Defaults: variant is flat, chrome is dim. chrome only applies when variant="inset".

AppShellBar

bordered?: boolean className?: string

Defaults: bordered is true when the shell is flat and false when inset.

AppShellSidebar

collapsed?: boolean className?: string

Defaults: collapsed is false — 16rem expanded, 3rem collapsed.

AppShellBody / AppShellCanvas

className?: string

Example

import {
  AppShell, AppShellBar, AppShellBody, AppShellCanvas, AppShellSidebar,
} from '@devalok/shilp-sutra/shell/app-shell'
import { TopBar } from '@devalok/shilp-sutra/shell/top-bar'

export function Layout({ children }) {
  return (
    <AppShell variant="inset" chrome="dim">
      <AppShellBar>
        <TopBar>
          <TopBar.Left><TopBar.Title>Projects</TopBar.Title></TopBar.Left>
          <TopBar.Right><TopBar.UserMenu user={user} /></TopBar.Right>
        </TopBar>
      </AppShellBar>

      <AppShellBody>
        <AppShellSidebar>
          <Nav />
        </AppShellSidebar>
        <AppShellCanvas className="overflow-auto">
          {children}
        </AppShellCanvas>
      </AppShellBody>
    </AppShell>
  )
}

The three arrangements

variant="flat" — chrome and content share one surface; a hairline on the sidebar and under the bar does the separating. Widest content area.

variant="inset" — the content sits in a rounded panel inside the chrome. chrome then decides which plane the frame takes and which the work takes:

light chromelight canvasdark chromedark canvas
chrome="dim"#eeeeee#ffffff#0a0a0a#171717
chrome="bright"#ffffff#eeeeee#171717#0a0a0a

Both come from the same two tokens — surface-sunken and surface-panel — swapped. There is no third tier and no per-theme special case.

The choice is not really about lightness. With dim a brand tint lands on the frame and the work area stays neutral; with bright it is the reverse. That decides whether your brand colour surrounds the work or sits underneath it.

Composability

Layout only. It sets surfaces, widths and the inset geometry; everything inside each slot is yours. Any component can go in any slot.

AppShellSidebar uses the same widths as Sidebar (16rem, 3rem collapsed), so the two agree whether or not you nest one inside the other.

Gotchas

The bar height is a minimum, not a fixed value. TopBar sizes to its content, and a two-line workspace switcher legitimately needs more room than a one-line one. Setting a fixed height clips it.

The inset canvas is flush to the bar and the sidebar and floats only off the bottom-right. That is deliberate: it reads as the frame opening up rather than a card dropped into the middle. A symmetric gap fights that.

No shadow on the inset canvas. It is a frame, not a floating thing — shadows are reserved for what genuinely floats (menus, dialogs, popovers).

A bar is optional, and dropping it is a product decision. Without one, the account and workspace switcher move into the sidebar footer. That changes what the shell is for, not just how it looks.

Changes

  • 0.57.0 — added