← Back to articles
December 202510 min read

How to Build a Clean Admin Dashboard UI with Next.js and Tailwind CSS

Admin dashboard layout with sidebar, navbar, KPI cards, tables, and charts. Responsive design, reusable components, theming, and accessibility patterns.

AdminNext.jsTailwind CSSUI

Admin dashboard layout structure

A clean admin dashboard has a consistent shell: sidebar navigation, top navbar, content area with cards/tables/charts, and responsive behavior that collapses the sidebar on mobile. The layout should feel familiar — users have seen hundreds of admin panels and expect the same patterns.

Sidebar, navbar, and content area

// app/admin/layout.tsx
import { Sidebar } from '@/components/admin/Sidebar';
import { Navbar } from '@/components/admin/Navbar';

export default function AdminLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex min-h-screen bg-gray-950">
      <Sidebar className="hidden w-64 shrink-0 border-r border-white/10 lg:block" />
      <div className="flex flex-1 flex-col">
        <Navbar />
        <main className="flex-1 overflow-y-auto p-4 md:p-6 lg:p-8">
          {children}
        </main>
      </div>
    </div>
  );
}
// components/admin/Sidebar.tsx
const navItems = [
  { label: 'Overview', href: '/admin', icon: HomeIcon },
  { label: 'Users', href: '/admin/users', icon: UsersIcon },
  { label: 'Transactions', href: '/admin/transactions', icon: ArrowsIcon },
  { label: 'Analytics', href: '/admin/analytics', icon: ChartIcon },
  { label: 'Settings', href: '/admin/settings', icon: CogIcon },
];

export function Sidebar({ className }: { className?: string }) {
  const pathname = usePathname();

  return (
    <aside className={className}>
      <div className="p-4">
        <h2 className="text-lg font-bold text-white">Admin</h2>
      </div>
      <nav className="space-y-1 px-3">
        {navItems.map((item) => (
          <Link
            key={item.href}
            href={item.href}
            className={`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm transition-colors ${
              pathname === item.href
                ? 'bg-white/10 text-white'
                : 'text-gray-400 hover:bg-white/5 hover:text-white'
            }`}
          >
            <item.icon className="h-5 w-5" />
            {item.label}
          </Link>
        ))}
      </nav>
    </aside>
  );
}

Cards, tables, and charts

Dashboard overview page with KPI cards and data panels:

// app/admin/page.tsx
export default function AdminOverview() {
  return (
    <div className="space-y-6">
      <h1 className="text-2xl font-bold text-white">Overview</h1>

      <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
        <StatCard label="Total Users" value="12,847" change="+12.5%" />
        <StatCard label="Revenue" value="$48,290" change="+8.2%" />
        <StatCard label="Active Wallets" value="3,421" change="+5.1%" />
        <StatCard label="Transactions" value="89,104" change="+15.3%" />
      </div>

      <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
        <Panel title="Revenue Over Time">
          <RevenueChart />
        </Panel>
        <Panel title="Recent Transactions">
          <TransactionTable limit={10} />
        </Panel>
      </div>
    </div>
  );
}

function StatCard({ label, value, change }: { label: string; value: string; change: string }) {
  const isPositive = change.startsWith('+');
  return (
    <div className="rounded-xl border border-white/10 bg-white/5 p-5">
      <p className="text-sm text-gray-400">{label}</p>
      <p className="mt-1 text-2xl font-bold text-white">{value}</p>
      <p className={`mt-1 text-xs ${isPositive ? 'text-emerald-400' : 'text-red-400'}`}>{change}</p>
    </div>
  );
}

Responsive design

Collapse sidebar to a hamburger menu on mobile. Stack KPI cards vertically. Tables scroll horizontally. Charts go full width.

// Mobile sidebar toggle
'use client';
import { useState } from 'react';

export function MobileNav() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)} className="lg:hidden">
        <MenuIcon className="h-6 w-6" />
      </button>
      {open && (
        <div className="fixed inset-0 z-50 lg:hidden">
          <div className="absolute inset-0 bg-black/50" onClick={() => setOpen(false)} />
          <Sidebar className="relative w-64 bg-gray-950" />
        </div>
      )}
    </>
  );
}

Reusable components

Build a small design system: Panel, StatCard, DataTable, Badge, Button, Input, Select, Modal, Skeleton. Consistent spacing (4px grid), border radius (rounded-xl), and color tokens.

Theme and typography

// tailwind.config.ts
theme: {
  extend: {
    colors: {
      surface: { DEFAULT: '#0a0a0f', card: '#12121a', elevated: '#1a1a24' },
    },
    fontFamily: {
      sans: ['var(--font-inter)'],
      mono: ['var(--font-mono)'],
    },
  },
}

Accessibility and performance

Semantic HTML for navigation (nav, main, aside). Keyboard-navigable sidebar. aria-current="page" on active nav items. Lazy-load charts. Virtualize large tables. Server-render KPI data.

FAQ

**Should I use a UI library?** shadcn/ui + Radix primitives is the best starting point for admin dashboards. Custom Tailwind for full control.

**Dark or light mode?** Dark by default for admin panels. Most developers and crypto users prefer dark interfaces.

**How do I handle permissions?** Role-based nav items. Hide sidebar links the user cannot access. Server-side route protection for admin pages.

**Can I use this for Web3 admin panels?** Yes. The layout patterns are identical. Add wallet-gated access and on-chain data panels as needed.

Want to work together? I build Web3 dashboards and DeFi interfaces.