Acrylic
Components

Shell

A composable macOS app shell for source lists, toolbars, split panes, and main content.

Shell is the window-level layout primitive for Acrylic apps. It owns the geometry of the app frame: source-list columns, split panes, toolbars, and scrollable content. Sidebar primitives can be used inside that frame for source-list content and styling, but Shell remains the layout owner.

This boundary matters: Sidebar's collapsible layout reads --sidebar-width and --sidebar-width-icon to size both its layout reservation and visible rail. Inside Shell, those geometry tokens should come from Shell, not ad-hoc width classes on the Sidebar itself. That keeps the sidebar gap, visible surface, and collapsed icon rail aligned.

Installation

npx shadcn add https://acrylic-ui.vercel.app/r/shell.json

Usage

import {
  Shell,
  ShellBody,
  ShellContent,
  ShellInset,
  ShellNavbar,
  ShellNavbarActions,
  ShellNavbarTitle,
  ShellPanel,
  ShellPanelDescription,
  ShellPanelHeader,
  ShellPanelTitle,
} from "@/components/acrylic/shell"
import {
  Sidebar,
  SidebarContent,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from "@/components/acrylic/sidebar"

<Shell sidebarWidth={150} sidebarCollapsedWidth={48}>
  <Sidebar collapsible="icon">
    <SidebarHeader>{/* source list header */}</SidebarHeader>
    <SidebarContent>
      <SidebarMenu>
        <SidebarMenuItem>
          <SidebarMenuButton isActive>Inbox</SidebarMenuButton>
        </SidebarMenuItem>
      </SidebarMenu>
    </SidebarContent>
  </Sidebar>
  <ShellInset>
    <ShellNavbar>
      <ShellNavbarTitle>All Inboxes</ShellNavbarTitle>
      <ShellNavbarActions>{/* global actions */}</ShellNavbarActions>
    </ShellNavbar>
    <ShellBody>
      <ShellPanel variant="list">
        <ShellPanelHeader>
          <div className="min-w-0">
            <ShellPanelTitle>Inbox</ShellPanelTitle>
            <ShellPanelDescription>67 messages</ShellPanelDescription>
          </div>
        </ShellPanelHeader>
        <ShellContent padding="flush">{/* list */}</ShellContent>
      </ShellPanel>
      <ShellPanel variant="detail">
        <ShellPanelHeader>
          <div className="min-w-0">
            <ShellPanelTitle>Message title</ShellPanelTitle>
            <ShellPanelDescription>sender@example.com</ShellPanelDescription>
          </div>
        </ShellPanelHeader>
        <ShellContent padding="reading">{/* detail */}</ShellContent>
      </ShellPanel>
    </ShellBody>
  </ShellInset>
</Shell>

Composition

Shell
├── Sidebar (source-list region; width tokens owned by Shell)
│   ├── SidebarHeader
│   └── SidebarContent
└── ShellInset
    ├── ShellNavbar
    └── ShellBody
        ├── ShellPanel
        │   ├── ShellPanelHeader
        │   └── ShellContent
        └── ShellPanel
            ├── ShellPanelHeader
            └── ShellContent

Examples

Mail

Compose a Mail-style split view: source list, message list, and reading pane.

Inbox
67 messages
AU
Acrylic UI
9:42 AM
Registry review notes
The Shell primitive keeps app structure separate from Sidebar.

Shell owns the window structure. Sidebar remains the source-list primitive, while the main panel can split into list and detail panes at exact macOS frame sizes.

This Mail example uses Item for each message preview, so density, truncation, and actions can be reused without turning Shell into a mail-specific API.

Notes

Use the same structure for a Notes-style source list, note list, and editor.

Folders
Notes
4 notes
Search

Shell component boundary

Edited today

A shell describes the app frame: sidebar, inset, panels, navbar, and scrollable content. Users compose those pieces into Mail, Notes, or their own product surface.

Panel headers are for local list metadata. Reading and editing surfaces should let the document title live inside the content.

Anatomy

partrole
Shellthe outer app frame and geometry owner; pass variant="inset" for a rounded preview/window surface
Sidebarsource-list region inside the shell; may use collapsible, but Shell owns sidebarWidth and sidebarCollapsedWidth
ShellInsetthe main region beside the sidebar
ShellNavbaran inset-level navigation/toolbar row that can span multiple panels
ShellBodythe horizontal region below an inset-level navbar
ShellPanela split pane inside the inset; adjacent panels share one divider
ShellPanelHeadera per-panel title/meta strip below the navbar
ShellNavbarTitle / ShellNavbarSubtitlecompact title text for pane chrome
ShellPanelTitle / ShellPanelDescriptioncompact title/meta text for panel headers
ShellNavbarActionsright-aligned action cluster with standard spacing
ShellContentthe scrollable body; defaults to padding="flush"; pass default or reading for built-in spacing

Notes

  • Shell owns pane geometry. Source-list width, collapsed width, and adjacent panel flow should be decided at the Shell layer. Pass sidebarWidth and sidebarCollapsedWidth to Shell so Sidebar's gap reservation and visible rail read the same geometry source.
  • Avoid setting source-list width only with className on Sidebar inside Shell: that can resize the visible rail without updating Sidebar's internal gap reservation.
  • Sidebar content primitives (SidebarHeader, SidebarContent, SidebarMenuButton, badges, groups) are still the right vocabulary for source-list rows inside Shell.
  • The panel variants set common structural widths only. Override widths with className when the app needs a different split.
  • Put ShellNavbar directly under ShellInset when the toolbar should span multiple panels, as in Mail. Use ShellPanelHeader for per-panel title and metadata rows.
  • ShellNavbar and ShellPanelHeader draw their own horizontal bottom rules. ShellPanel owns vertical dividers, so two adjacent panels do not double their borders.
  • List panels (ShellPanel variant="list" + ShellContent padding="flush") are not a separate list API — rows are Item compositions (ItemContent / ItemTitle / ItemDescription / ItemMeta), selection is Item's own selected prop, and rows are divided with ItemSeparator. Mail and Notes both compose the same Item anatomy; see their source for the row + selection + separator pattern.

API Reference

Every Shell sub-part renders a plain element with a data-slot styling hook and forwards all native props (className, onClick, style, …). className is for layout only — color always flows through the --acr-* tokens. Three legacy aliases ship for back-compat: ShellPane (= ShellPanel), ShellToolbar (= ShellNavbar), ShellTitle (= ShellNavbarTitle) — prefer the primary names in new code.

Shell

The outer app frame and geometry owner. Wraps SidebarProvider, so defaultOpen / open / onOpenChange control the sidebar's collapsed state. Renders <div data-slot="shell" data-variant>.

PropTypeDefaultDescription
variant"default" | "inset""default""inset" adds the rounded frosted-glass window surface (acr-frosted, blur, shadow) used for preview/window contexts; "default" is a flat full-bleed frame.
sidebarWidthnumber | stringSets --sidebar-width (numbers are treated as px). Read by Sidebar's own layout — set it here, not as a className width on Sidebar.
sidebarCollapsedWidthnumber | stringSets --sidebar-width-icon, the collapsed icon-rail width.
defaultOpen / open / onOpenChangeSidebarProvider propsForwarded to the underlying SidebarProvider to control the sidebar's open/collapsed state.
classNamestringMerged onto the frame (layout only).
...propsReact.ComponentProps<"div">Forwarded to the root <div>.

ShellInset

The main region beside the sidebar — a flex column that clips overflow. Renders <main data-slot="shell-inset">.

PropTypeDefaultDescription
classNamestringMerged onto the region.
...propsReact.ComponentProps<"main">Forwarded to the <main>.

ShellNavbar

An inset-level toolbar row; place directly under ShellInset when it should span every panel below it (as in Mail), or inside a single panel for per-panel chrome. Draws its own bottom rule. Renders <div data-slot="shell-navbar" data-size>.

PropTypeDefaultDescription
size"default" | "compact" | "large""default"Row height: compact h-10, default h-11, large h-[52px].
classNamestringMerged onto the row.
...propsReact.ComponentProps<"div">Forwarded to the <div>.

ShellNavbarHeading

Flexible title/subtitle stack for ShellNavbar — takes the remaining row width. Renders <div data-slot="shell-navbar-heading">.

PropTypeDefaultDescription
classNamestringMerged onto the stack.
...propsReact.ComponentProps<"div">Forwarded to the <div>.

ShellNavbarTitle / ShellNavbarSubtitle

Compact title (13px semibold) and subtitle (11px muted) text for navbar chrome, each truncating. Render <div data-slot="shell-navbar-title"> / <div data-slot="shell-navbar-subtitle">.

PropTypeDefaultDescription
classNamestringMerged onto the text.
...propsReact.ComponentProps<"div">Forwarded to the <div>.

ShellNavbarActions

Right-aligned action cluster (ml-auto) with standard spacing, for navbar toolbar buttons. Renders <div data-slot="shell-navbar-actions">.

PropTypeDefaultDescription
classNamestringMerged onto the cluster.
...propsReact.ComponentProps<"div">Forwarded to the <div>.

ShellBody

The horizontal region below an inset-level navbar; lays out adjacent ShellPanels in a row. Renders <div data-slot="shell-body">.

PropTypeDefaultDescription
classNamestringMerged onto the row.
...propsReact.ComponentProps<"div">Forwarded to the <div>.

ShellPanel

A split pane inside ShellBody. Adjacent panels share one vertical divider ([&+&]:border-l) so borders never double up. Renders <section data-slot="shell-panel" data-variant>.

PropTypeDefaultDescription
variant"default" | "list" | "detail""default""list" sets a fixed w-72 non-growing column (source/message/note lists); "detail" grows at flex-[1.35] (reading/detail panes). Override the width with className when the app needs a different split.
classNamestringMerged onto the pane.
...propsReact.ComponentProps<"section">Forwarded to the <section>.

ShellPanelHeader

A per-panel title/meta strip below the navbar; draws its own bottom rule. Renders <div data-slot="shell-panel-header">.

PropTypeDefaultDescription
classNamestringMerged onto the strip.
...propsReact.ComponentProps<"div">Forwarded to the <div>.

ShellPanelTitle / ShellPanelDescription

Compact title (13px semibold) and metadata (11px muted) text for panel headers, each truncating. Render <div data-slot="shell-panel-title"> / <div data-slot="shell-panel-description">.

PropTypeDefaultDescription
classNamestringMerged onto the text.
...propsReact.ComponentProps<"div">Forwarded to the <div>.

ShellPanelActions

Right-aligned action cluster (ml-auto) for ShellPanelHeader, the per-panel counterpart to ShellNavbarActions. Renders <div data-slot="shell-panel-actions">.

PropTypeDefaultDescription
classNamestringMerged onto the cluster.
...propsReact.ComponentProps<"div">Forwarded to the <div>.

ShellContent

The scrollable body of a panel. Renders <div data-slot="shell-content" data-padding>.

PropTypeDefaultDescription
padding"flush" | "default" | "reading""flush""flush" adds no padding (for full-bleed lists composed of Item rows); "default" adds p-3; "reading" adds px-8 py-7 for prose/detail panes.
classNamestringMerged onto the scroll container.
...propsReact.ComponentProps<"div">Forwarded to the <div>.

On this page