# shadcn/ui Dashboard Tutorial: Step-by-Step Guide (2026)

> A step-by-step shadcn/ui dashboard tutorial: setup, sidebar, data tables, charts, dark mode, and deploy — build a production dashboard with Next.js.

Source: https://designrevision.com/blog/shadcn-dashboard-tutorial

---

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 <a href="https://ui.shadcn.com" rel="nofollow">shadcn/ui</a> 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

1. [Prerequisites and Tech Stack](#prerequisites-and-tech-stack)
2. [Step 1: Project Setup](#step-1-project-setup)
3. [Step 2: Dashboard Layout Architecture](#step-2-dashboard-layout-architecture)
4. [Step 3: Build the Sidebar Navigation](#step-3-build-the-sidebar-navigation)
5. [Step 4: Metric Cards and KPIs](#step-4-metric-cards-and-kpis)
6. [Step 5: Data Tables with Sorting and Filtering](#step-5-data-tables-with-sorting-and-filtering)
7. [Step 6: Charts and Data Visualization](#step-6-charts-and-data-visualization)
8. [Step 7: Dark Mode and Theming](#step-7-dark-mode-and-theming)
9. [Step 8: Performance Optimization](#step-8-performance-optimization)
10. [Deployment](#deployment)
11. [shadcn/ui vs Alternatives for Dashboards](#shadcnui-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:

```bash
# 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:

```bash
# 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.

```tsx
// 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.

```tsx
// 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:

```tsx
// 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.

```tsx
// 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:

```tsx
// 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 <a href="https://tanstack.com/table" rel="nofollow">TanStack Table</a> for headless sorting, filtering, and pagination.

Define your columns with type safety:

```tsx
// 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 <a href="https://recharts.org" rel="nofollow">Recharts</a> with theming support:

```tsx
// 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:

```tsx
<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 <a href="https://github.com/pacocoursey/next-themes" rel="nofollow">next-themes</a> and wrap your root layout:

```tsx
// 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:

```tsx
"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`:

```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:

```tsx
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:

```tsx
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](/blog/vercel-vs-railway) is the fastest path to production with zero configuration.

```bash
# 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](/blog/tailwind-nextjs-setup) 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 <a href="https://tremor.so" rel="nofollow">Tremor</a> when** you need a dashboard in hours with minimal customization. Its pre-built KPI cards, charts, and metric components ship ready to use.

**Use <a href="https://mui.com" rel="nofollow">Material UI</a> 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](/blog/shadcn-ui-guide) and the [Tailwind component library comparison](/blog/best-tailwind-component-libraries) 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](/blog/best-nextjs-saas-templates) comparison covers starter kits that include shadcn/ui out of the box.

---

## Related Resources

- [shadcn UI: Complete Guide](/blog/shadcn-ui-guide)
- [Best Tailwind Component Libraries: 12 Options Ranked](/blog/best-tailwind-component-libraries)
- [Best Tailwind Templates: 15 Free and Paid Options](/blog/best-tailwind-templates)
- [Best Next.js SaaS Templates: 12 Boilerplates Ranked](/blog/best-nextjs-saas-templates)
- [Tailwind + Next.js: The Complete Setup Guide](/blog/tailwind-nextjs-setup)
- [Prisma vs Drizzle: Which ORM for Your Next.js Project?](/blog/prisma-vs-drizzle)
- [Vercel vs Railway: Best Deployment Platform for SaaS](/blog/vercel-vs-railway)
- [Best Shadcn UI Templates, Blocks & Themes](/blog/best-shadcn-templates)

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "HowTo",
  "name": "How to Build a Dashboard with shadcn/ui",
  "description": "A step-by-step guide to building a production-ready dashboard with shadcn/ui, Next.js App Router, TanStack Table, and Recharts.",
  "totalTime": "PT4H",
  "estimatedCost": {
    "@type": "MonetaryAmount",
    "currency": "USD",
    "value": "0"
  },
  "tool": [
    { "@type": "HowToTool", "name": "Node.js 18+" },
    { "@type": "HowToTool", "name": "Next.js 15" },
    { "@type": "HowToTool", "name": "shadcn/ui" },
    { "@type": "HowToTool", "name": "Tailwind CSS" },
    { "@type": "HowToTool", "name": "TanStack Table" },
    { "@type": "HowToTool", "name": "Recharts" }
  ],
  "step": [
    {
      "@type": "HowToStep",
      "name": "Project Setup",
      "text": "Create a Next.js project with TypeScript and Tailwind, then initialize shadcn/ui and install core dashboard components.",
      "position": 1
    },
    {
      "@type": "HowToStep",
      "name": "Dashboard Layout Architecture",
      "text": "Build a nested App Router layout with persistent sidebar, header with breadcrumbs, and scrollable content area.",
      "position": 2
    },
    {
      "@type": "HowToStep",
      "name": "Build the Sidebar Navigation",
      "text": "Create a responsive sidebar using shadcn Sidebar component for desktop and Sheet drawer for mobile.",
      "position": 3
    },
    {
      "@type": "HowToStep",
      "name": "Metric Cards and KPIs",
      "text": "Build reusable metric cards using shadcn Card components in a responsive grid layout.",
      "position": 4
    },
    {
      "@type": "HowToStep",
      "name": "Data Tables with Sorting and Filtering",
      "text": "Implement a DataTable with TanStack Table for type-safe columns, sorting, filtering, and pagination.",
      "position": 5
    },
    {
      "@type": "HowToStep",
      "name": "Charts and Data Visualization",
      "text": "Add theme-aware area, bar, and line charts using the shadcn Chart component built on Recharts.",
      "position": 6
    },
    {
      "@type": "HowToStep",
      "name": "Dark Mode and Theming",
      "text": "Configure dark mode with next-themes and customize brand colors through CSS variables.",
      "position": 7
    },
    {
      "@type": "HowToStep",
      "name": "Performance Optimization",
      "text": "Apply Server Components, dynamic imports for charts, and Suspense streaming for fast dashboard loads.",
      "position": 8
    }
  ]
}
</script>
