SearchCtrlK

  • ↑↓navigate
  • ↵open
  • escclose

Can’t find it? Contact us

AppShell

The frame every signed-in screen sits in: a tinted frame holding the sidebar and an inset content card.

Open in a new tab
View code
tsx
<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.

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.

Terminal
npm add @sdxc/ui remix
typescript
import "@sdxc/ui/theme.css";

Usage

typescript
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.

  • AppShell
  • AppShell.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

Skip to content
Acme Ops / Monitors

Monitors

2 down

48 checks across 6 regions. Two are failing right now.

checkout-api and eu-west webhooks have failed their last 3 checks.

View code
tsx
<AppShell
	labels={labels}
	collapsed={readAppShellCookie(request.headers.get("Cookie"))}
	workspace={workspace}
	nav={nav}
	footer={footer}
>
	{page}
</AppShell>

Settings panel swapped in for the nav

Skip to content
Acme Ops / Monitors

General

The workspace's name, URL and default time zone.

Workspace name: Acme Ops

View code
tsx
<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.

PropTypeRequiredDescription
labelsLabelsYes
collapsedbooleanNoDraws the sidebar as the icon rail; read it with readAppShellCookie. @default false
workspaceRemixNodeNoThe workspace switcher at the top of the sidebar, usually an AppShell.Identity.
navRemixNodeYesThe sidebar's links: AppShell.Groups of AppShell.Items, or a swapped panel.
footerRemixNodeNoThe foot of the sidebar, usually the user menu's AppShell.Identity.
topbarRemixNodeNoThe top bar's content: a breadcrumb, a search field, the one global action.
bannerRemixNodeNoA notice above the whole frame, such as a sandbox banner; it pushes the frame down.
childrenRemixNodeYesThe screen, rendered inside <main>.
peekbooleanNoOpens the rail over the content while the pointer rests on it. @default true
indicator"fill" | "pill"No
idstringNoPrefix for the sidebar's and main's ids (<id>-sidebar, <id>-main).
cookieNamestringNoThe collapse cookie's name, for an app with two shells. @default APP_SHELL_COOKIE
sheetBelowSheetBelowNoThe 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

PropTypeRequiredDescription
titlestringNoA plain sentence-case title; it turns into a hairline in the rail.
childrenRemixNodeNo

Also accepts everything in Omit<TagProps<"div">, "title">.

AppShell.Item

AppShell.Back

AppShell.Identity

PropTypeRequiredDescription
avatarRemixNodeYesAn 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.
namestringYes
detailstringNoA second line, such as a plan or an email.
chevronbooleanNoDraws the up-down chevron on a row that opens a menu. @default true

Also accepts everything in Omit<TagProps<"button">, "children">.

Types

NameValuesWhat 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.
@sdxc/ui

Can’t find it? Contact us