shadcn/ui Dashboard Tutorial: Step-by-Step Guide (2026)
DesignRevision Editorial
· SaaS, frontend & developer tooling
Most dashboard tutorials stop at installing a component library and rendering a few cards. They skip the parts that matter: layout architecture, responsive sidebars, data tables that handle 10,000 rows, charts that match your brand, and dark mode that does not break at 2 AM.
This shadcn dashboard tutorial covers the full build. From project setup to deployment, you will build a production-ready dashboard using shadcn/ui and Next.js App Router. Every step uses real components, real patterns, and the same architecture that powers dashboards at companies running shadcn in production.
Key Takeaways
If you remember nothing else:
- A shadcn dashboard uses a nested App Router layout for consistent sidebar, header, and content structure across all pages
- The copy-paste component model means zero runtime dependencies and full code ownership. Your bundle stays lean
- DataTable with TanStack Table handles sorting, filtering, and pagination for 10,000+ rows out of the box
- Charts built on Recharts integrate with the shadcn theming system, so dark mode works automatically
- The responsive pattern: persistent sidebar on desktop, Sheet drawer on mobile. One layout, two experiences
Table of Contents
- Prerequisites and Tech Stack
- Step 1: Project Setup
- Step 2: Dashboard Layout Architecture
- Step 3: Build the Sidebar Navigation
- Step 4: Metric Cards and KPIs
- Step 5: Data Tables with Sorting and Filtering
- Step 6: Charts and Data Visualization
- Step 7: Dark Mode and Theming
- Step 8: Performance Optimization
- Deployment
- shadcn/ui vs Alternatives for Dashboards
Prerequisites and Tech Stack
Before starting your shadcn dashboard, make sure you have Node.js 18+ and a basic understanding of React and TypeScript. Familiarity with Tailwind CSS helps but is not required.
| Layer | Tool | Why |
|---|---|---|
| Framework | Next.js 15 (App Router) | Server Components, file-based routing, streaming |
| Components | shadcn/ui | Copy-paste model, Radix UI primitives, full code ownership |
| Styling | Tailwind CSS 4 | Utility-first, works with shadcn theming, tiny bundles |
| Data Tables | TanStack Table v8 | Headless, sorting, filtering, pagination, 10K+ row support |
| Charts | Recharts | Composable, lightweight, integrates with shadcn Chart component |
| Dark Mode | next-themes | System preference detection, instant toggle |
For a broader look at how these tools fit into a full SaaS stack, the SaaS tech stack guide covers database, auth, and deployment choices alongside your frontend.
Step 1: Project Setup
Create a Next.js project and initialize shadcn/ui:
# Create Next.js project with TypeScript and Tailwind
npx create-next-app@latest my-dashboard --typescript --tailwind --eslint --app
# Navigate into the project
cd my-dashboard
# Initialize shadcn/ui
npx shadcn-ui@latest init
During initialization, select the default style, CSS variables for colors, and the app directory. This creates a components.json config file and sets up your globals.css with the CSS variable system that powers theming.
Install the components you will need:
# Core dashboard components
npx shadcn-ui@latest add card table chart sidebar
npx shadcn-ui@latest add button input separator breadcrumb
npx shadcn-ui@latest add sheet tabs avatar dropdown-menu
npx shadcn-ui@latest add command badge tooltip
Each command copies the component source code into your components/ui/ directory. No hidden dependencies, no version lock-in. You own the code.
Project structure after setup:
app/
├── layout.tsx # Root layout with providers
├── page.tsx # Landing or redirect
├── dashboard/
│ ├── layout.tsx # Dashboard shell (sidebar + header)
│ ├── page.tsx # Overview with metrics and charts
│ ├── customers/
│ │ └── page.tsx # Data table page
│ └── settings/
│ └── page.tsx # Settings page
components/
├── ui/ # shadcn components (auto-generated)
├── dashboard/
│ ├── sidebar.tsx # Custom sidebar navigation
│ ├── header.tsx # Header with breadcrumbs
│ ├── metric-card.tsx # Reusable metric card
│ └── data-table.tsx # Table with sorting/filtering
lib/
├── utils.ts # cn() utility for class merging
Step 2: Dashboard Layout Architecture
The layout is the foundation of every shadcn dashboard. Use a nested App Router layout that wraps all dashboard pages with a consistent sidebar, header, and content area.
// app/dashboard/layout.tsx
import { Sidebar } from "@/components/dashboard/sidebar"
import { Header } from "@/components/dashboard/header"
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex min-h-screen bg-background">
<Sidebar />
<div className="flex flex-1 flex-col">
<Header />
<main className="flex-1 p-6">{children}</main>
</div>
</div>
)
}
This pattern gives you a persistent sidebar on the left, a header with breadcrumbs at the top, and a scrollable content area for each page. The bg-background class uses a CSS variable that automatically adapts to light and dark mode.
The key insight: the dashboard layout is a Server Component. It renders once on the server, and only the interactive children (charts, tables, toggles) hydrate on the client. This keeps your initial page load fast.
Step 3: Build the Sidebar Navigation
The sidebar is the backbone of any shadcn dashboard navigation. shadcn/ui provides a Sidebar component with built-in collapsible behavior and responsive support.
// components/dashboard/sidebar.tsx
"use client"
import { Sidebar, SidebarContent, SidebarGroup,
SidebarGroupLabel, SidebarMenu, SidebarMenuItem,
SidebarMenuButton } from "@/components/ui/sidebar"
import { LayoutDashboard, Users, Settings,
BarChart3 } from "lucide-react"
import Link from "next/link"
const navItems = [
{ title: "Overview", href: "/dashboard", icon: LayoutDashboard },
{ title: "Customers", href: "/dashboard/customers", icon: Users },
{ title: "Analytics", href: "/dashboard/analytics", icon: BarChart3 },
{ title: "Settings", href: "/dashboard/settings", icon: Settings },
]
export function DashboardSidebar() {
return (
<Sidebar className="hidden border-r md:block">
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Navigation</SidebarGroupLabel>
<SidebarMenu>
{navItems.map((item) => (
<SidebarMenuItem key={item.href}>
<SidebarMenuButton asChild>
<Link href={item.href}>
<item.icon className="h-4 w-4" />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
</Sidebar>
)
}
For mobile, use a Sheet component that slides in from the left when triggered by a hamburger button in the header:
// Mobile sidebar trigger in header
<Sheet>
<SheetTrigger asChild>
<Button variant="ghost" size="icon" className="md:hidden">
<Menu className="h-5 w-5" />
</Button>
</SheetTrigger>
<SheetContent side="left" className="w-64 p-0">
<DashboardSidebar />
</SheetContent>
</Sheet>
This gives you one sidebar component with two presentations: persistent on desktop (hidden md:block), Sheet drawer on mobile.
Step 4: Metric Cards and KPIs
Every shadcn dashboard needs a top-level metrics section. The shadcn Card component structures this cleanly.
// components/dashboard/metric-card.tsx
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
interface MetricCardProps {
title: string
value: string
change: string
trend: "up" | "down"
}
export function MetricCard({ title, value, change, trend }: MetricCardProps) {
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">
{title}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{value}</div>
<p className={`text-xs ${trend === "up" ? "text-green-600" : "text-red-600"}`}>
{change} from last month
</p>
</CardContent>
</Card>
)
}
Use a responsive grid to lay out the cards:
// app/dashboard/page.tsx
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<MetricCard title="Total Revenue" value="$45,231" change="+20.1%" trend="up" />
<MetricCard title="Subscriptions" value="+2,350" change="+180.1%" trend="up" />
<MetricCard title="Active Users" value="1,247" change="+19%" trend="up" />
<MetricCard title="Churn Rate" value="2.4%" change="-0.3%" trend="down" />
</div>
The grid-cols-4 on large screens, grid-cols-2 on medium, and single column on mobile gives you a responsive layout with zero custom CSS.
Step 5: Data Tables with Sorting and Filtering
Data tables are the workhorse of any dashboard. shadcn's DataTable combines the Table component with TanStack Table for headless sorting, filtering, and pagination.
Define your columns with type safety:
// components/dashboard/columns.tsx
"use client"
import { ColumnDef } from "@tanstack/react-table"
import { Badge } from "@/components/ui/badge"
export type Customer = {
id: string
name: string
email: string
status: "active" | "inactive" | "churned"
revenue: number
}
export const columns: ColumnDef<Customer>[] = [
{
accessorKey: "name",
header: "Name",
},
{
accessorKey: "email",
header: "Email",
},
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => (
<Badge variant={row.getValue("status") === "active" ? "default" : "secondary"}>
{row.getValue("status")}
</Badge>
),
},
{
accessorKey: "revenue",
header: "Revenue",
cell: ({ row }) => {
const amount = parseFloat(row.getValue("revenue"))
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount)
},
},
]
The DataTable component handles the rest: column sorting on click, a filter input for searching, and pagination controls at the bottom. TanStack Table renders this efficiently even with 10,000+ rows because it virtualizes the DOM.
For a deep dive into connecting your data table to a real database, the guide to building a SaaS with Next.js covers server actions, data fetching, and Supabase integration alongside shadcn components.
Step 6: Charts and Data Visualization
Install the shadcn Chart component, which wraps Recharts with theming support:
// components/dashboard/revenue-chart.tsx
"use client"
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"
import { AreaChart, Area, XAxis, YAxis, CartesianGrid } from "recharts"
const data = [
{ month: "Jan", revenue: 4000 },
{ month: "Feb", revenue: 3000 },
{ month: "Mar", revenue: 5000 },
{ month: "Apr", revenue: 4500 },
{ month: "May", revenue: 6000 },
{ month: "Jun", revenue: 5500 },
]
const chartConfig = {
revenue: { label: "Revenue", color: "hsl(var(--chart-1))" },
}
export function RevenueChart() {
return (
<ChartContainer config={chartConfig} className="h-[300px]">
<AreaChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<ChartTooltip content={<ChartTooltipContent />} />
<Area
type="monotone"
dataKey="revenue"
stroke="var(--color-revenue)"
fill="var(--color-revenue)"
fillOpacity={0.2}
/>
</AreaChart>
</ChartContainer>
)
}
The ChartContainer handles responsive sizing and the chartConfig maps data keys to theme-aware colors. When dark mode toggles, the chart colors update automatically because they reference CSS variables.
Place the chart alongside your metrics:
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
<Card className="col-span-4">
<CardHeader>
<CardTitle>Revenue Overview</CardTitle>
</CardHeader>
<CardContent>
<RevenueChart />
</CardContent>
</Card>
<Card className="col-span-3">
<CardHeader>
<CardTitle>Recent Activity</CardTitle>
</CardHeader>
<CardContent>
{/* Activity feed */}
</CardContent>
</Card>
</div>
The 4/3 column split gives the chart more room while keeping a secondary panel for activity feeds, recent signups, or quick actions.
Step 7: Dark Mode and Theming
shadcn/ui uses CSS variables for theming, which makes dark mode straightforward. Install next-themes and wrap your root layout:
// app/layout.tsx
import { ThemeProvider } from "next-themes"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
)
}
Add a theme toggle to your dashboard header:
"use client"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
import { Sun, Moon } from "lucide-react"
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
>
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
</Button>
)
}
Every shadcn component, including your charts, adapts automatically. The CSS variables in globals.css define both light and dark palettes, so you never need to write manual dark: overrides for UI components.
Custom Branding
To match your brand, edit the CSS variables in globals.css:
@layer base {
:root {
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
/* Override these with your brand colors */
}
}
Every component that uses bg-primary, text-primary-foreground, or similar classes updates globally. One file change, entire dashboard rebrand.
Step 8: Performance Optimization
A dashboard shadcn ui setup is inherently lean because you only install the components you use. But there are additional optimizations worth applying.
Server Components by default. Keep your layout, metric cards, and static content as Server Components. Only add "use client" to interactive elements: charts, table sorting, sidebar toggles, and theme switches. This minimizes the JavaScript sent to the browser.
Dynamic imports for charts. Charts are the heaviest client-side dependency. Load them lazily:
import dynamic from "next/dynamic"
const RevenueChart = dynamic(
() => import("@/components/dashboard/revenue-chart").then(mod => mod.RevenueChart),
{ ssr: false, loading: () => <div className="h-[300px] animate-pulse bg-muted rounded" /> }
)
Streaming with Suspense. Wrap data-dependent sections in Suspense boundaries so the dashboard shell renders immediately while data loads:
import { Suspense } from "react"
<Suspense fallback={<CardSkeleton />}>
<MetricCards />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<CustomerTable />
</Suspense>
Bundle impact. The shadcn copy-paste model means no runtime library dependency. A typical shadcn dashboard adds around 50KB gzipped to your bundle, compared to 100KB+ for Material UI or 500KB+ for Ant Design.
Deployment
Deploy your finished shadcn dashboard to any platform that supports Next.js. For most teams, Vercel is the fastest path to production with zero configuration.
# Build and verify locally
npm run build
npm start
# Deploy to Vercel
npx vercel --prod
Set your environment variables for database connections, API keys, and auth providers in your deployment platform's dashboard. If you are connecting to Supabase or another Postgres database, the Tailwind + Next.js setup guide covers deployment configuration alongside styling.
shadcn/ui vs Alternatives for Dashboards
Choosing the right component library for your shadcn dashboard is a one-way door for most projects. Here is how shadcn/ui compares for dashboard use cases.
| Criteria | shadcn/ui | Tremor | Material UI | Ant Design |
|---|---|---|---|---|
| Approach | Copy-paste, Tailwind primitives | Chart-focused, Tailwind | CSS-in-JS framework | Monolithic CSS |
| Bundle size | ~50KB gzipped | ~200KB gzipped | ~100KB+ gzipped | ~500KB+ gzipped |
| Customization | Full (you own the code) | Good (Tailwind) | Theme tokens | Override-heavy |
| Dashboard components | DataTable, Sidebar, Charts, Cards | 40+ pre-styled dashboard widgets | Full set, heavier | Full set, heaviest |
| App Router support | Native | Yes | Adapter needed | Partial |
| Dark mode | CSS variables, instant | Built-in | Built-in | Built-in |
| Best for | Custom, branded dashboards | Rapid dashboard prototyping | Design system consistency | Enterprise admin panels |
Use shadcn/ui when you want full control, minimal bundle size, and a shadcn dashboard that matches your brand exactly. It pairs naturally with Tailwind and the Next.js App Router ecosystem.
Use Tremor when you need a dashboard in hours with minimal customization. Its pre-built KPI cards, charts, and metric components ship ready to use.
Use Material UI when your team is already invested in the MUI ecosystem and needs design system consistency across a large application.
For a broader overview of component library options, the shadcn/ui guide and the Tailwind component library comparison cover the full landscape.
Conclusion
Building a shadcn dashboard follows a clear sequence: set up the project, architect the layout, add navigation, build out metrics and data tables, layer in charts, and polish with dark mode and performance optimizations.
The shadcn/ui approach gives you something that pre-built dashboard templates cannot: full ownership of every component. You can customize every pixel, swap out any dependency, and extend the dashboard without fighting framework constraints.
Start with the layout. Get the sidebar and header working first. Add metric cards for your top-level KPIs. Then layer in the data table for your primary entity (customers, orders, users). Charts come last because they depend on having real data to visualize.
If you want a head start, RevKit ships with a complete shadcn dashboard template, pre-built data tables, chart components, and a responsive sidebar layout that you can customize for your SaaS. For teams building from scratch, the best Next.js SaaS templates comparison covers starter kits that include shadcn/ui out of the box.
Related Resources
- shadcn UI: Complete Guide
- Best Tailwind Component Libraries: 12 Options Ranked
- Best Tailwind Templates: 15 Free and Paid Options
- Best Next.js SaaS Templates: 12 Boilerplates Ranked
- Tailwind + Next.js: The Complete Setup Guide
- Prisma vs Drizzle: Which ORM for Your Next.js Project?
- Vercel vs Railway: Best Deployment Platform for SaaS
- Best Shadcn UI Templates, Blocks & Themes
Frequently Asked Questions
-
Yes. shadcn/ui is one of the best options for building modern dashboards in 2026. Its copy-paste model means zero runtime dependencies, and components like DataTable, Sidebar, Card, and Chart cover every dashboard pattern. The official shadcn dashboard example demonstrates production-ready layouts with metrics, tables, and charts that you can customize with Tailwind CSS. Compared to heavier alternatives like Material UI or Ant Design, shadcn keeps your bundle lean while giving you full control over the code.
-
The essential components are Card for metric containers and KPIs, DataTable for tabular data with sorting and filtering, Chart components like AreaChart and BarChart for visualizations, Sidebar for navigation, Tabs for content sections, and Sheet or Drawer for mobile-responsive actions. Install them individually with the shadcn CLI. Most dashboards use 8 to 12 shadcn components total.
-
Run npx shadcn-ui at latest add chart to install the chart primitives built on Recharts. Import AreaChart, BarChart, or LineChart from your components directory. Wrap them in a ChartContainer with a config object that defines colors and labels. The charts integrate with the shadcn theming system, so dark mode works automatically. For real-time data, update the chart data array via React state.
-
Yes, shadcn/ui has full App Router support. The CLI auto-configures the app directory structure during initialization. Components use Server Components by default, with client boundaries added only where interactivity is needed. This means your dashboard layout and static content render on the server for fast initial loads, while interactive elements like charts, sidebar toggles, and table sorting hydrate on the client.
-
shadcn/ui wins for custom dashboards that need full Tailwind control and lighter bundle size. A typical shadcn dashboard adds around 50KB gzipped versus 200KB or more for Tremor. Tremor offers 40 or more pre-styled dashboard components out of the box, making it faster for prototyping. Use Tremor if you need a dashboard in hours with minimal customization. Use shadcn if you want a branded, production-grade UI that you fully own and control.
-
Install next-themes and wrap your app in a ThemeProvider with attribute set to class and defaultTheme set to system. All shadcn components use CSS variables like background and foreground that automatically adapt to light and dark modes. Add a toggle button using the useTheme hook. The transition is instant because it swaps CSS variables rather than reloading styles.
-
Yes. shadcn/ui is beginner-friendly because you copy each component code directly into your project instead of installing a black-box package, so you can read and edit every line. If you know basic React and Tailwind CSS, you can follow this tutorial end to end. The CLI handles the initial setup, and every component ships with sensible defaults you can use as-is and refine later.
Join 50k+ subscribers
Web dev, SaaS, growth & marketing. Weekly.
Keep Learning
More articles you might find interesting.