Early bird: 30% off Dashral ProSee pricing

Dashral

App ShellNew

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-shell

Orders

Everything your customers bought in the last 30 days.

Revenue

$48,210

Up +8.4%

Orders

1,284

Up +3.1%

Average order

$37.55

Down −1.2%

The page's content

Installation

$ pnpm dlx shadcn@latest add @dashral/app-shell

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

example.tsx
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

Narrowed to icons

defaultCollapsed starts the sidebar on its icons, named in tooltips; no page header, so the page starts under the bar.

API reference

AppShell

Also takes every prop of <div>(opens in a new tab).

PropTypeDefault
sidebarSidebarProps

The side navigation, as Sidebar's props: items, header (your logo), footer (the user), linkAs, aria-label. Required.

—
headerReactNode

The bar along the top, beside the menu button on a phone: a search, notifications, the user. It sticks as the page scrolls.

—
pagePageHeaderProps

The page's header under the bar, as PageHeader's props: title, description, breadcrumb, actions, tabs.

—
childrenReactNode

The page.

—
collapsed / defaultCollapsedboolean

The sidebar narrowed to its icons; or let its toggle change it.

false
onCollapsedChange(collapsed) => void

Called when the toggle narrows or widens it.

—
containedboolean

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

—
classNamestring

Extra classes, merged with cn() so yours win.

—

Keyboard

KeysAction
TabThe menu button (on a phone), the bar's controls, then the sidebar's links and the page
EscapeCloses 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.