A dashboard's frame in one tag: the sidebar, a bar that sticks, the page header and the page; on a phone the sidebar opens from a menu button.
$ pnpm dlx shadcn@latest add @dashral/app-shellInstallation
$ pnpm dlx shadcn@latest add @dashral/app-shellAdds the component, the components it builds on, its npm packages and the theme tokens it uses. Or by its full URL: https://dashral.com/r/app-shell.json
When to use
- The frame of an admin app or dashboard: its navigation, a bar along the top and each page's header.
When not to use
- A marketing page: Container and Section.
- The navigation alone, in a layout of your own: Sidebar.
Usage
Sidebar's props in sidebar, PageHeader's in page; on a phone the sidebar opens from a menu button.
import { Button } from "@/components/dashral/button"
import { Input } from "@/components/dashral/input"
import { LayoutDashboard, ShoppingCart } from "lucide-react"
import { AppShell } from "@/components/dashral/app-shell"
export default function Orders() {
return (
<AppShell
sidebar={{
header: <span className="font-semibold">Northwind</span>,
items: [
{ label: "Overview", icon: <LayoutDashboard />, href: "/" },
{ label: "Orders", icon: <ShoppingCart />, href: "/orders", active: true },
],
}}
header={<Input type="text" role="searchbox" aria-label="Search" placeholder="Search…" className="max-w-xs" />}
page={{ title: "Orders", actions: <Button>New order</Button> }}
>
<p>The page.</p>
</AppShell>
)
}Using the npm package? Import from @dashral/ui/components/app-shell instead, here and in the examples; the rest is the same.
Examples
API reference
AppShell
Also takes every prop of <div>(opens in a new tab).
| Prop | Type | Default |
|---|---|---|
sidebar | SidebarPropsThe side navigation, as Sidebar's props: items, header (your logo), footer (the user), linkAs, aria-label. Required. | — |
header | ReactNodeThe bar along the top, beside the menu button on a phone: a search, notifications, the user. It sticks as the page scrolls. | — |
page | PageHeaderPropsThe page's header under the bar, as PageHeader's props: title, description, breadcrumb, actions, tabs. | — |
children | ReactNodeThe page. | — |
collapsed / defaultCollapsed | booleanThe sidebar narrowed to its icons; or let its toggle change it. | false |
onCollapsedChange | (collapsed) => voidCalled when the toggle narrows or widens it. | — |
contained | booleanFills a box of your own (give it a height) and scrolls within it, for a preview or a panel. An app leaves it off and scrolls with the window. | false |
classNames | { sidebar, header, main, page }Classes for each part. | — |
className | stringExtra classes, merged with cn() so yours win. | — |
Keyboard
| Keys | Action |
|---|---|
| Tab | The menu button (on a phone), the bar's controls, then the sidebar's links and the page |
| Escape | Closes the sidebar's sheet on a phone |
Accessibility
- The sidebar is a navigation landmark (name it with sidebar's aria-label), the bar a <header> and the page a <main>.
- On a phone the sidebar opens in a sheet: focus moves into it and back to the menu button when it closes.
- The menu button is named "Open navigation" (translated with the kit's locale).
Motion
- The sidebar widens and narrows with its toggle; on a phone its sheet slides in from the side. The bar is see-through and blurs what scrolls under it.