Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

dashboard-01

A ready-to-pull AI dashboard with sidebar navigation, stat cards, a usage chart, and recent activity built from VegaStack components.

Last updated

Skip to content
Active agents
12812.4%
Tasks completed today
9464.1%
API calls (24h)
18,4522.3%
Avg. response time
284 ms6.8%
Usage over time
Requests and errors for the last 7 days.
TaskStatusStartedDuration
Summarize Q2 investor report
done1m 24s
Sync CRM contacts
In progress41s
Draft onboarding email sequence
done22s
Classify inbound support tickets
blocked9s
Generate weekly usage digest
done2m 13s

What it is

dashboard-01 is a registry block, not a component — a starter dashboard PAGE you pull once with shadcn add and then own (edit freely; it is not hash-tracked/re-pulled the way app-shell is). It composes AppShell + AppShellSidebar + AppShellHeader + AppShellContent (the hash-tracked shared layout) with an AI-platform-flavored sample page: a nav rail (Dashboard / Agents / Tasks / Usage / Settings + a user menu), a four-up stat-card row, a "usage over time" chart, and a recent-activity list — plus a full-page empty state and per-region loading/error handling.

This mirrors shadcn/ui's own sidebar (component) ↔ dashboard-01 (block) split: the shell mechanics live in app-shell/sidebar (pulled and updated forever), the page-specific nav items, stat cards, and sample data live here (expected to diverge per app from day one).

Install

npx shadcn@latest add @vegastack/dashboard-01
Registry setup required
Run this command only after configuring the Base UI shadcn project, the @vegastack registry namespace, and the Cloudflare Access service-token headers in Install from the VegaStack registry.

This installs the block's own files (below) plus every registryDependencies component it composes (app-shell, sidebar, breadcrumb, card, badge, animated-number, chart, data-list, truncated-text, status-icon, relative-time, empty, avatar, dropdown-menu, button) that your project doesn't already have.

Usage

import { DashboardPage } from "@/app/dashboard/page"; // after shadcn add, this is your page

export default function Page() {
  return <DashboardPage />;
}

DashboardPage accepts optional stats / usage / activity (each defaults to the bundled data.json), plus loading / error (per-region) and isEmpty (full-page). Wire these to your real data fetching once the block is installed:

<DashboardPage
  stats={realStats}
  usage={realUsage}
  activity={realActivity}
  loading={{ activity: isActivityLoading }}
  error={{ chart: chartError?.message }}
  isEmpty={workspace.agentCount === 0}
/>

Examples

Skip to content
Active agents
12812.4%
Tasks completed today
9464.1%
API calls (24h)
18,4522.3%
Avg. response time
284 ms6.8%
Usage over time
Requests and errors for the last 7 days.
TaskStatusStartedDuration
Summarize Q2 investor report
done1m 24s
Sync CRM contacts
In progress41s
Draft onboarding email sequence
done22s
Classify inbound support tickets
blocked9s
Generate weekly usage digest
done2m 13s

Mobile navigation

Switch the preview's width toggle (the toolbar's phone icon) to mobile to send the block through the same AppShell breakpoint branch a narrow viewport triggers. The rail collapses; the persistent header trigger opens navigation as a focus-trapped Sheet over the page while the dashboard content stays mounted underneath. It's viewport-driven in a real app — the toggle constrains a container, so this demo forces the branch to make it visible without resizing your window.

Skip to content
Active agents
12812.4%
Tasks completed today
9464.1%
API calls (24h)
18,4522.3%
Avg. response time
284 ms6.8%
Usage over time
Requests and errors for the last 7 days.
TaskStatusStartedDuration
Summarize Q2 investor report
done1m 24s
Sync CRM contacts
In progress41s
Draft onboarding email sequence
done22s
Classify inbound support tickets
blocked9s
Generate weekly usage digest
done2m 13s

Full-page empty state

Rendered when the workspace genuinely has no agents/tasks yet (isEmpty prop) — a standard SaaS zero-state, in place of the stat/chart/activity regions (audit §e item 5). This is the block's own responsibility, not AppShell's — AppShell has no opinion on page content.

Skip to content

No agents yet

Create your first agent to start seeing usage, tasks, and activity here.

Per-region loading

Each region — the stat row, the chart, and the recent-activity list — carries its OWN loading flag, so one slow region never blocks the others from rendering. RecentActivity reuses DataList's built-in skeleton rows; StatCards/DashboardChart render their own Skeleton placeholders shaped like the real content.

Skip to content
Usage over time
Requests and errors for the last 7 days.
Loading rows
TaskStatusStartedDuration

File manifest

FileRegistry typeTarget
page.tsxregistry:pageapp/dashboard/page.tsx
loading.tsxregistry:pageapp/dashboard/loading.tsx
data.jsonregistry:fileapp/dashboard/data.json
components/app-sidebar.tsxregistry:component— (resolves to your configured components dir)
components/stat-cards.tsxregistry:component
components/dashboard-chart.tsxregistry:component
components/recent-activity.tsxregistry:component

page.tsx is server-safe (no hooks, no 'use client') — the interactive pieces (AppSidebar's nav-user menu, StatCards' AnimatedNumber, DashboardChart's Recharts composition, RecentActivity's DataList/RelativeTime) are client leaves it imports as JSX, per the same Server/Client Component boundary rule AppShellSkeleton documents for SidebarMenuSkeleton. The block carries zero Next.js imports — nav links and the breadcrumb render as plain <a>; swap in your router's Link via each render prop at the call site without touching the block's structure.

View transitions

What was verified. Next.js 16.2.9 (the version installed in this workspace) documents a single opt-in mechanism for animating <Link> navigations with the browser's native View Transitions API: the experimental.viewTransition flag in next.config.js (source), which wraps every <Link> navigation in document.startViewTransition and unlocks React's <ViewTransition> component + transitionTypes prop on next/link. This is a purely client-side mechanism (it calls a browser API in response to a link click) — nothing about it requires server rendering, so it is compatible with this workspace's output: 'export' static export (source confirms static export has no such restriction).

What this block wires. The flag itself is apps/docs/next.config.mjs config this block's files cannot carry (out of a registry block's file scope — and, in THIS repository, editing that file was explicitly out of scope for this change), so per the brief this is documented, not hacked in. What the block DOES wire, inside its own files: AppShellHeader and AppSidebar's AppShellSidebar each carry a [view-transition-name:dashboard-shell-header] / [view-transition-name:dashboard-shell-sidebar] arbitrary CSS property (Tailwind v4 native arbitrary-property syntax — a plain CSS custom-ident, not a hex/px literal, so it stays clean under design-lint's arbitrary-value contract). Once a consuming app enables the flag, this is the MINIMAL, honest mechanism for "shell stays stable while content crossfades": giving the header/sidebar a view-transition-name pulls them out of the default whole-page ::view-transition-old(root)/::view-transition-new(root) crossfade and into their OWN stable group — since their content is pixel-identical before/after a same-shell navigation, that group reads as "not animating" while the rest of the page (still under the root pseudo) crossfades. This works with pure CSS + the Next config flag alone; it does not require React's <ViewTransition> component.

Consumer setup required (this block cannot do this for you):

  1. Enable the flag:

    // next.config.ts
    const nextConfig: NextConfig = { experimental: { viewTransition: true } };
  2. Hoist the shell into a layout, not a page. This block's file manifest mirrors shadcn's dashboard-01 shape — a single app/dashboard/page.tsx that renders the ENTIRE AppShell (sidebar + header + content) itself. That is correct for a single-route starter, but it means the sidebar/header literally unmount and remount on every navigation to a DIFFERENT route, same as any other page-scoped tree. For the "shell stays visually stable across route changes" effect to read as true continuity (not just an inert same-position crossfade), move the AppShell + AppSidebar + AppShellHeader composition into a shared app/dashboard/layout.tsx that wraps {children} inside AppShellContent, and keep each route's page.tsx to just its own content (the stat/chart/activity regions here, for example). This is a real, deliberate restructuring the block does not do for you — the file manifest above is intentionally the single-page shadcn-mirroring shape; layout-hoisting is an app-specific decision left to the consumer once there is more than one route to navigate between.

  3. Keep the token base stylesheet installed. @vegastack/design-tokens/base.css includes a dedicated reduced-motion kill switch for ::view-transition-group(*), ::view-transition-old(*), and ::view-transition-new(*), because the normal universal reset cannot reach the browser's snapshot tree. Consumers following the standard installation path already have this protection; if an app intentionally omits the base stylesheet, it must provide an equivalent rule before enabling route transitions.

This workspace's own docs app does not have the flag enabled (apps/docs/next.config.mjs was out of scope for this change), so the crossfade cannot be interactively demonstrated on this page — the view-transition-name styling above is inert CSS until a consuming app opts in.

Determinism (VRT)

Every value this block renders is static (data.json) or derived from it — no Date.now() or Math.random() anywhere in the render path, so a screenshot is pixel-identical on every run. The one place this needed real investigation: RecentActivity's "started" column uses RelativeTime, which defaults to the LIVE clock. Its API exposes a documented deterministic mode — pass now (a fixed epoch-ms reference instant) together with refresh={false} (which also suppresses its internal re-render timer) — and recent-activity.tsx uses exactly that, pinned to data.json's own generatedAt timestamp.

API Reference

PropTypeDefaultDescription
activityActivityRow[]bundled sample `data.json`Recent-activity rows.
classNamestringClass name forwarded to the root AppShell (useful when embedding the block in a bounded preview).
error{ stats?: string; chart?: string; activity?: string; }{}Per-region error messages — each region shows an inline error Empty instead of its content.
isEmptybooleanfalseTrue when the workspace genuinely has no agents/tasks yet — renders a full-page Empty zero-state instead of the stat/chart/activity regions (audit §e item 5, the block's own responsibility, not AppShell's).
loading{ stats?: boolean; chart?: boolean; activity?: boolean; }{}Per-region loading flags — each region shows its own skeleton independently.
mobileBreakpointnumber768Viewport width below which navigation uses AppShell's modal Sheet.
statsStatCardDatum[]bundled sample `data.json`Stat-card row data.
usageUsagePoint[]bundled sample `data.json`"Usage over time" chart series.
PropTypeDefaultDescription
activeKey"agents" | "dashboard" | "settings" | "tasks" | "usage"'dashboard'Which nav item is current — highlights it and sets aria-current="page".
onLogout(() => void)Called when "Log out" is selected — presentational only, no session logic here.
userAppSidebarUserbundled sample userThe signed-in user shown in the footer menu.

Accessibility

  • The block composes AppShell's skip link and banner/navigation/main landmark trio; preserve those landmarks when adapting the starter into a shared layout.
  • Sidebar links, the user menu, chart summary, activity table, empty state, loading states, and error regions retain the accessibility contracts of their underlying VegaStack components.
  • Navigation labels and status text remain visible; icons and chart decoration never carry meaning alone.
  • At 320px, the responsive sidebar collapses while data regions contain their own overflow rather than forcing page-level horizontal scrolling.
KeyAction
TabMove through the skip link, navigation, user menu, and dashboard controls.
Enter / SpaceActivate the focused link, menu trigger, or button.
EscapeClose the open user menu and restore focus to its trigger.

Do / Don't

Do
Use the block as a one-time starter, then replace its bundled data and links while preserving AppShell landmarks and regional states.
Don't
Treat the block as an update-tracked component or duplicate its sidebar/header/main shell instead of composing AppShell.

On this page