Duck Dashboard
The application shell: sidebar that becomes a drawer, sticky top bar with search and theme control, an optional stat row, and your page as children.
Preview
Overview
Installs
12,480
+18% this week
Components
31
one theme, one hook
Registry uptime
99.98%
Open issues
4
median close: 2 days
Your page goes here
Everything under the stat row is children. The shell only owns the sidebar, the top bar and the spacing.
Installation
One command writes the section and every component it renders.
pnpm dlx shadcn@latest add @duck/duck-dashboardThe block lands in components/blocks/duck-dashboard.tsx. It builds on duck-mark, holo-avatar, holo-badge, sticker-card, sticker-kbd, sticker-progress and theme-switcher, which the CLI installs alongside it. Already installed items are skipped.
Usage
import { DuckDashboard } from "@/components/blocks/duck-dashboard"A block is a starting point, not a widget. The props exist so the example renders with real content — once the file is in your project, hard-code what never changes and delete the rest.
Props
Everything not listed here is forwarded to the root element.
| Prop | Type | Default | Description |
|---|---|---|---|
| nav | DuckDashboardNavItem[] | - | label, href, icon, active, badge, onSelect. Selecting one closes the mobile drawer. |
| footerNav | DuckDashboardNavItem[] | - | Second group pinned to the bottom of the sidebar: settings, help, sign out. |
| title | React.ReactNode | - | Page title in the top bar. |
| brand | React.ReactNode | duck mark + brandLabel | Sidebar identity. |
| brandLabel | string | "duck/ui" | Text next to the default mark. |
| user | { name: string; src?: string; fallback?: string } | - | Avatar at the right of the top bar. |
| onSearch | () => void | - | Renders the search control and binds the command key. Without it, no search button and no key handler. |
| searchLabel | string | "Search" | Label inside the search control. |
| stats | DuckDashboardStat[] | - | label, value, hint, progress. Rendered as a card row above the children. |
| actions | React.ReactNode | - | Extra top-bar controls, left of the theme switcher. |
| themeSwitcher | boolean | true | Show the theme control. Turn it off in apps with a single theme. |
| children | React.ReactNode | - | The page, under the stat row. |
Rules
What keeps this section from turning into noise.
- No holo in the chrome. The shell is on screen for the whole session; the one iridescent element belongs to the page inside it.
- The theme switcher needs a next-themes provider above the shell. Pass themeSwitcher={false} if the app has none.
- The shell measures itself, not the window: the sidebar is a drawer under 36rem of shell width and sticky above it, so an embedded shell behaves like a narrow one. Keep the nav to one screen — it does not scroll independently.
