A whole app layout: drag the sidebar's edge to resize it or snap it to a rail, ⌘K to jump anywhere, favorites you drag to reorder, recent pages and a sliding highlight.
When to use
- A whole app layout: a resizable sidebar that snaps to a rail, ⌘K to jump anywhere, favorites and recent pages.
- Data-heavy apps people keep open all day, where layout and quick navigation matter.
When not to use
- Simple dashboards: Dashboard sidebar with Dashboard header is less to wire.
- Marketing pages and public sites.
Usage
App shell with your own data: every prop has the sample as its default, so pass only what you have.
import { AppShell } from "@/components/dashral/blocks/app-shell"
<AppShell
pages={pages}
boards={dashboards}
current={pageId}
onNavigate={(id) => router.push(`/${id}`)}
favorites={favorites}
onFavoritesChange={(ids) => api.user.saveFavorites(ids)}
user={user}
usage={{ label: "Storage", value: "7.2 of 10 GB", percent: 72 }}
>
{(page) => <Page id={page.id} />}
</AppShell>API reference
AppShell
A whole app shell: the sidebar, a header and the page. Drag the sidebar's edge to resize it — narrow enough and it snaps to a rail of icons; double-click the edge for the default width — or press [ to fold it. ⌘K (Ctrl+K) jumps to any page. Favorites reorder by dragging (or Alt+↑/↓), the star in the page header pins the page, Recent keeps what you visited, and a highlight slides to the current page. On phones the sidebar is a sheet. Your pages and dashboards, workspaces, user and usage card come in as props; the page you're on, the workspace and the favorites are controllable, each with its handler. children renders the page (the sample one without it).
| Prop | Type | Default |
|---|---|---|
pages | ShellItem[] | sample |
boards | ShellItem[]The second list, shown under Dashboards in the jump list; favorites and recent can hold either. | sample |
current | stringThe page you're on, by id (the shell keeps it without it); "settings" is the account's settings. | — |
onNavigate | (id: string) => void | — |
favorites | string[]Pinned page ids, in order. | — |
onFavoritesChange | (ids: string[]) => void | — |
recent | string[]The pages Recent starts with; it keeps what you visit after. | ["okrs", "customers"] |
workspaces | string[] | sample |
workspace | string | — |
onWorkspaceChange | (workspace: string) => void | — |
user | { name: string; email: string; src?: string } | sample |
usage | { label: string; value: string; percent: number } | nullThe meter above the account; null hides it. | sample |
unread | booleanThe dot on the bell. | true |
onAction | (action: ShellAction) => void | — |
className | stringClasses for the shell; it fills the screen's height (h-svh), so set another height to place it in a page. | — |
children | (page: ShellItem) => React.ReactNodeThe page for the current item; without it, a sample page that changes with each one. | — |
type ShellItem
A page in the sidebar: an icon, or a coloured dot (a class like "bg-success"), and a count.
| Prop | Type | Default |
|---|---|---|
id | string | — |
label | string | — |
icon | LucideIcon | — |
dot | string | — |
badge | number | — |
type ShellAction
The buttons that don't navigate.
= "new-workspace" | "notifications"