AppShell
The frame every signed-in screen sits in: a tinted frame holding the sidebar and an inset content card.
View code
<AppShell
labels={labels}
collapsed={readAppShellCookie(request.headers.get("Cookie"))}
workspace={
<>
<AppShell.Identity avatar={<Logo />} name="Acme Ops" detail="Pro plan"
commandfor="workspace-menu" command="toggle-popover" />
<Menu id="workspace-menu" aria-label="Workspace">
<Menu.Item href="/workspaces">Switch workspace</Menu.Item>
<Menu.Item href="/settings">Workspace settings</Menu.Item>
</Menu>
</>
}
nav={
<>
<AppShell.Group>
<AppShell.Item href="/overview" icon={<LayoutDashboardIcon />} label="Overview" />
<AppShell.Item href="/monitors" icon={<ActivityIcon />} label="Monitors"
count={2} countTone="danger" countLabel="2 monitors down" current />
<AppShell.Item href="/incidents" icon={<SirenIcon />} label="Incidents" unread />
<AppShell.Item href="/status-pages" icon={<GlobeIcon />} label="Status pages" />
</AppShell.Group>
<AppShell.Group title="Settings">
<AppShell.Item href="/settings/team" icon={<UsersIcon />} label="Team" />
</AppShell.Group>
</>
}
footer={
<>
<AppShell.Identity avatar={<Avatar />} name="Ada Lovelace" detail="ada@acme.dev"
commandfor="account-menu" command="toggle-popover" />
<Menu id="account-menu" placement="top-start" aria-label="Account">
<Menu.Item href="/settings/profile">Account settings</Menu.Item>
<Menu.Separator />
<Menu.Item danger>Sign out</Menu.Item>
</Menu>
</>
}
topbar={<Breadcrumbs>…</Breadcrumbs>}
>
<PageHeader title="Monitors" primaryAction={<Button>New monitor</Button>} />
</AppShell>Demo apps
Whole pages built only from @sdxc/ui, each for an invented product with its own palette and type. They open in a new tab.
- CMSInkwellInkwell, a demo CMS built from @sdxc/ui/app: posts with count tabs and quick edit, an editor with a quiet save status and a loud Publish, media and settings./demo/apps/inkwell
- Ops dashboardPulsePulse, an invented ops dashboard: Northwind's services with live filters, a peek sheet and a bulk bar, metrics that switch one chart, incidents, deploys and settings./demo/apps/pulse
- Support inboxRelayRelay, an invented support inbox: mailboxes, conversations and the open conversation on one screen, with contacts, reports and settings./demo/apps/relay
Every demo, landings and docs sites alike, is listed in the demo gallery.
Installation
An ordinary dependency: install it, import the theme once, and import the component where it is used.
npm add @sdxc/ui remiximport "@sdxc/ui/theme.css";Usage
import { AppShell } from "@sdxc/ui/app";Renders the shell. The sidebar is one <aside popover="auto">: a grid column from sheetBelow,
the icon rail while collapsed, and a sheet the top bar's menu button opens below it. Load
@sdxc/ui/app/app-shell-toggle in the browser entry for the cookie, ⌘B and Esc-back.
Composition
The parts the component publishes, each a static property of the host.
AppShellAppShell.GroupRenders a titled group of rows.AppShell.ItemRenders one row: a 16px icon at reduced ink, the label, and an optional count pill.AppShell.BackRenders the back row of a swapped panel ("‹ Settings"), drawn by the server in place of the app's nav.AppShell.IdentityRenders a switcher or account row: a 24px avatar, a name and an optional detail line.
Examples
Collapsed to the icon rail
Monitors
2 down48 checks across 6 regions. Two are failing right now.
checkout-api and eu-west webhooks have failed their last 3 checks.
View code
<AppShell
labels={labels}
collapsed={readAppShellCookie(request.headers.get("Cookie"))}
workspace={workspace}
nav={nav}
footer={footer}
>
{page}
</AppShell>Settings panel swapped in for the nav
General
The workspace's name, URL and default time zone.
Workspace name: Acme Ops
View code
<AppShell
labels={labels}
workspace={workspace}
footer={footer}
nav={
<>
<AppShell.Back href="/monitors" label="Settings" />
<AppShell.Group title="Workspace">
<AppShell.Item href="/settings" icon={<SettingsIcon />} label="General" current />
<AppShell.Item href="/settings/team" icon={<UsersIcon />} label="Team" />
<AppShell.Item href="/settings/billing" icon={<CreditCardIcon />} label="Billing" />
</AppShell.Group>
<AppShell.Group title="Integrations">
<AppShell.Item href="/settings/alerts" icon={<BellIcon />} label="Alert channels" />
<AppShell.Item href="/settings/api-keys" icon={<KeyIcon />} label="API keys" />
</AppShell.Group>
</>
}
>
<PageHeader title="General" />
</AppShell>Props
Read from the component's own types, so every prop, every default and every allowed value is listed.
| Prop | Type | Required | Description |
|---|---|---|---|
labels | Labels | Yes | |
collapsed | boolean | No | Draws the sidebar as the icon rail; read it with readAppShellCookie. @default false |
workspace | RemixNode | No | The workspace switcher at the top of the sidebar, usually an AppShell.Identity. |
nav | RemixNode | Yes | The sidebar's links: AppShell.Groups of AppShell.Items, or a swapped panel. |
footer | RemixNode | No | The foot of the sidebar, usually the user menu's AppShell.Identity. |
topbar | RemixNode | No | The top bar's content: a breadcrumb, a search field, the one global action. |
banner | RemixNode | No | A notice above the whole frame, such as a sandbox banner; it pushes the frame down. |
children | RemixNode | Yes | The screen, rendered inside <main>. |
peek | boolean | No | Opens the rail over the content while the pointer rests on it. @default true |
indicator | "fill" | "pill" | No | |
id | string | No | Prefix for the sidebar's and main's ids (<id>-sidebar, <id>-main). |
cookieName | string | No | The collapse cookie's name, for an app with two shells. @default APP_SHELL_COOKIE |
sheetBelow | SheetBelow | No | The width under which the sidebar is a sheet, for a screen whose own panes need the room, such as a three-pane split view at 1024px. @default "48rem" |
AppShell.Group
| Prop | Type | Required | Description |
|---|---|---|---|
title | string | No | A plain sentence-case title; it turns into a hairline in the rail. |
children | RemixNode | No |
Also accepts everything in Omit<TagProps<"div">, "title">.
AppShell.Item
AppShell.Back
AppShell.Identity
| Prop | Type | Required | Description |
|---|---|---|---|
avatar | RemixNode | Yes | An avatar or logo, drawn whole at 24px whatever its own size: a root Avatar at any size fills the slot and an <img> covers it. It stays visible in the rail. |
name | string | Yes | |
detail | string | No | A second line, such as a plan or an email. |
chevron | boolean | No | Draws the up-down chevron on a row that opens a menu. @default true |
Also accepts everything in Omit<TagProps<"button">, "children">.
Types
| Name | Values | What it decides |
|---|---|---|
Indicator | "fill" | "pill" | How the current row is marked: a soft fill, or a 2px pill on its leading edge. |
CountTone | "neutral" | "brand" | "success" | "warning" | "danger" | The tone of a row's count; a solid tone reads as "needs you", neutral as a tally. |