DEV Community

Cover image for Build an Admin Dashboard with Tailwind CSS - A Practical Tutorial
UI Junkie
UI Junkie

Posted on

Build an Admin Dashboard with Tailwind CSS - A Practical Tutorial

In this tutorial, we'll build a real-world admin dashboard component using Tailwind CSS, based on the AdminOS template. You'll learn essential Tailwind concepts while creating something useful.

What We're Building

A responsive metric cards section showing key business metrics (MRR, Churn Rate, New Customers, ARPU) - perfect for any SaaS dashboard.

Prerequisites

  • Basic HTML/CSS knowledge
  • Tailwind CSS CDN or installed via npm

Step 1: Set Up Tailwind CSS

Add Tailwind to your HTML file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tailwind Dashboard Tutorial</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- Customize theme -->
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#0050cb',
                    }
                }
            }
        }
    </script>
</head>
<body class="bg-gray-50">
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the Metrics Grid Layout

Tailwind's grid system makes responsive layouts effortless:

<div class="max-w-7xl mx-auto px-4 py-8">
    <!-- Grid: 1 column on mobile, 2 on tablet, 4 on desktop -->
    <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
        <!-- Metric cards will go here -->
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Key Concepts:

  • grid-cols-1 → 1 column on mobile
  • sm:grid-cols-2 → 2 columns on tablets (640px+)
  • lg:grid-cols-4 → 4 columns on desktop (1024px+)
  • gap-6 → 24px spacing between cards

Step 3: Build a Metric Card

Here's the first card showing MRR (Monthly Recurring Revenue):

<div class="bg-white rounded-xl border border-gray-200 p-6 shadow-sm hover:shadow-md transition-shadow">
    <!-- Card header with icon -->
    <div class="flex justify-between items-start mb-4">
        <p class="text-sm font-medium text-gray-500 uppercase tracking-wide">MRR</p>
        <div class="p-2 bg-blue-50 rounded-lg text-blue-600">
            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
            </svg>
        </div>
    </div>

    <!-- Metric value -->
    <div class="flex flex-col">
        <h3 class="text-2xl font-bold text-gray-900">$1.2M</h3>

        <!-- Trend indicator -->
        <div class="flex items-center gap-1 text-sm font-semibold text-emerald-600 mt-1">
            <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
            </svg>
            <span>+12%</span>
            <span class="text-gray-400 font-normal ml-1">vs last month</span>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

What we learned:

  • bg-white + rounded-xl + border → card styling
  • shadow-sm + hover:shadow-md + transition-shadow → interactive effects
  • flex justify-between items-start → horizontal layout with space between
  • text-2xl font-bold → typography scale
  • text-emerald-600 → color coding for positive trends

Step 4: Create a Churn Rate Card (Negative Trend)

<div class="bg-white rounded-xl border border-gray-200 p-6 shadow-sm">
    <div class="flex justify-between items-start mb-4">
        <p class="text-sm font-medium text-gray-500 uppercase tracking-wide">Churn Rate</p>
        <div class="p-2 bg-red-50 rounded-lg text-red-600">
            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
            </svg>
        </div>
    </div>
    <div class="flex flex-col">
        <h3 class="text-2xl font-bold text-gray-900">2.4%</h3>
        <div class="flex items-center gap-1 text-sm font-semibold text-red-600 mt-1">
            <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 17h8m0 0V9m0 8l-8-8-4 4-6-6"></path>
            </svg>
            <span>-0.5%</span>
            <span class="text-gray-400 font-normal ml-1">vs last month</span>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Key Takeaway: Same card structure, different icon color (bg-red-50 + text-red-600) for negative metrics.

Step 5: Add Interactive Hover Effects

Let's enhance the cards with better interactivity:

<div class="group bg-white rounded-xl border border-gray-200 p-6 shadow-sm hover:shadow-lg transition-all duration-300 hover:-translate-y-1 cursor-pointer">
    <div class="flex justify-between items-start mb-4">
        <p class="text-sm font-medium text-gray-500 uppercase tracking-wide group-hover:text-blue-600 transition-colors">
            New Customers
        </p>
        <div class="p-2 bg-purple-50 rounded-lg text-purple-600 group-hover:scale-110 transition-transform">
            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"></path>
            </svg>
        </div>
    </div>
    <div class="flex flex-col">
        <h3 class="text-2xl font-bold text-gray-900">450</h3>
        <div class="flex items-center gap-1 text-sm font-semibold text-emerald-600 mt-1">
            <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path>
            </svg>
            <span>82</span>
            <span class="text-gray-400 font-normal ml-1">this week</span>
        </div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

New Concepts:

  • group + group-hover: → parent-triggered child animations
  • hover:-translate-y-1 → lift effect on hover
  • transition-all duration-300 → smooth animations
  • group-hover:scale-110 → icon pops on card hover

Step 6: Add Dark Mode Support

Tailwind makes dark mode incredibly easy:

<html lang="en" class="dark">
<body class="bg-gray-50 dark:bg-gray-900">
    <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6 shadow-sm">
        <div class="flex justify-between items-start mb-4">
            <p class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">ARPU</p>
            <div class="p-2 bg-indigo-50 dark:bg-indigo-900/30 rounded-lg text-indigo-600 dark:text-indigo-400">
                <!-- Icon SVG -->
            </div>
        </div>
        <div class="flex flex-col">
            <h3 class="text-2xl font-bold text-gray-900 dark:text-white">$85</h3>
            <div class="flex items-center gap-1 text-sm font-semibold text-gray-500 dark:text-gray-400 mt-1">
                <span>Stable</span>
                <span class="text-gray-400 dark:text-gray-500 ml-1">per user</span>
            </div>
        </div>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Dark Mode Pattern:

  • dark:bg-gray-800 → dark background
  • dark:text-white → light text on dark
  • dark:border-gray-700 → subtle borders
  • Always provide both light and dark variants

Step 7: Make It Responsive with Different Layouts

Here's how the grid adapts across all devices:

<!-- Responsive grid system -->
<div class="grid grid-cols-1 
             sm:grid-cols-2 
             lg:grid-cols-4 
             gap-4 sm:gap-6 
             p-4 sm:p-6 lg:p-8">

    <!-- Cards maintain consistent internal spacing -->
    <div class="bg-white rounded-xl p-4 sm:p-6">
        <!-- `p-4` on mobile, `p-6` on desktop -->
    </div>
</div>

<!-- Sidebar + Main layout pattern (like AdminOS) -->
<div class="flex flex-col md:flex-row min-h-screen">
    <!-- Sidebar - hidden on mobile, shown on desktop -->
    <aside class="md:w-64 bg-white dark:bg-gray-800 fixed md:static 
                  inset-y-0 left-0 transform -translate-x-full 
                  md:translate-x-0 transition-transform duration-300">
        <!-- Sidebar content -->
    </aside>

    <!-- Main content - full width on mobile, with margin on desktop -->
    <main class="flex-1 md:ml-64 p-4 md:p-6">
        <!-- Dashboard content -->
    </main>
</div>
Enter fullscreen mode Exit fullscreen mode

Complete Working Example

Here's the complete HTML for our metric cards dashboard:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tailwind Dashboard Tutorial</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script>
        tailwind.config = {
            darkMode: 'class',
            theme: {
                extend: {
                    colors: {
                        primary: '#0050cb',
                    }
                }
            }
        }
    </script>
</head>
<body class="bg-gray-50 dark:bg-gray-900">
    <div class="max-w-7xl mx-auto px-4 py-8">
        <!-- Header -->
        <div class="mb-8">
            <h1 class="text-3xl font-bold text-gray-900 dark:text-white">Analytics Dashboard</h1>
            <p class="text-gray-600 dark:text-gray-400 mt-2">Key metrics for Q4 2024</p>
        </div>

        <!-- Metrics Grid -->
        <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
            <!-- MRR Card -->
            <div class="group bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6 shadow-sm hover:shadow-lg transition-all duration-300 hover:-translate-y-1">
                <div class="flex justify-between items-start mb-4">
                    <p class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">MRR</p>
                    <div class="p-2 bg-blue-50 dark:bg-blue-900/30 rounded-lg text-blue-600 dark:text-blue-400 group-hover:scale-110 transition-transform">
                        <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
                        </svg>
                    </div>
                </div>
                <div>
                    <h3 class="text-2xl font-bold text-gray-900 dark:text-white">$1.2M</h3>
                    <div class="flex items-center gap-1 text-sm font-semibold text-emerald-600 dark:text-emerald-400 mt-1">
                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path>
                        </svg>
                        <span>+12%</span>
                        <span class="text-gray-400 dark:text-gray-500 font-normal ml-1">vs last month</span>
                    </div>
                </div>
            </div>

            <!-- Churn Rate Card -->
            <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6 shadow-sm">
                <div class="flex justify-between items-start mb-4">
                    <p class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">Churn Rate</p>
                    <div class="p-2 bg-red-50 dark:bg-red-900/30 rounded-lg text-red-600 dark:text-red-400">
                        <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
                        </svg>
                    </div>
                </div>
                <div>
                    <h3 class="text-2xl font-bold text-gray-900 dark:text-white">2.4%</h3>
                    <div class="flex items-center gap-1 text-sm font-semibold text-red-600 dark:text-red-400 mt-1">
                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 17h8m0 0V9m0 8l-8-8-4 4-6-6"></path>
                        </svg>
                        <span>-0.5%</span>
                        <span class="text-gray-400 dark:text-gray-500 font-normal ml-1">vs last month</span>
                    </div>
                </div>
            </div>

            <!-- New Customers Card -->
            <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6 shadow-sm">
                <div class="flex justify-between items-start mb-4">
                    <p class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">New Customers</p>
                    <div class="p-2 bg-purple-50 dark:bg-purple-900/30 rounded-lg text-purple-600 dark:text-purple-400">
                        <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"></path>
                        </svg>
                    </div>
                </div>
                <div>
                    <h3 class="text-2xl font-bold text-gray-900 dark:text-white">450</h3>
                    <div class="flex items-center gap-1 text-sm font-semibold text-emerald-600 dark:text-emerald-400 mt-1">
                        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path>
                        </svg>
                        <span>82</span>
                        <span class="text-gray-400 dark:text-gray-500 font-normal ml-1">this week</span>
                    </div>
                </div>
            </div>

            <!-- ARPU Card -->
            <div class="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6 shadow-sm">
                <div class="flex justify-between items-start mb-4">
                    <p class="text-sm font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">ARPU</p>
                    <div class="p-2 bg-indigo-50 dark:bg-indigo-900/30 rounded-lg text-indigo-600 dark:text-indigo-400">
                        <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
                        </svg>
                    </div>
                </div>
                <div>
                    <h3 class="text-2xl font-bold text-gray-900 dark:text-white">$85</h3>
                    <div class="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400 mt-1">
                        <span>Stable</span>
                        <span class="text-gray-400 dark:text-gray-500 ml-1">per user</span>
                    </div>
                </div>
            </div>
        </div>

        <!-- Theme Toggle Button (Bonus) -->
        <div class="fixed bottom-6 right-6">
            <button onclick="document.documentElement.classList.toggle('dark')" 
                    class="bg-white dark:bg-gray-800 p-3 rounded-full shadow-lg border border-gray-200 dark:border-gray-700">
                <svg class="w-6 h-6 text-gray-800 dark:text-white dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
                </svg>
                <svg class="w-6 h-6 text-yellow-500 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path>
                </svg>
            </button>
        </div>
    </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Key Takeaways from This Tutorial

  1. Utility-First Philosophy - Each class does one thing, combine them to build complex UIs
  2. Responsive Prefixes - sm:, md:, lg: control breakpoints
  3. Dark Mode - Just add dark: variants anywhere
  4. State Variants - hover:, focus:, group-hover: for interactivity
  5. Spacing Scale - p-4, m-2, gap-6 use a consistent 0.25rem ratio
  6. Color System - Pre-defined palette with shades (gray-50 to gray-900)

What's Next?

  • Add charts using Chart.js or Recharts
  • Implement real data fetching with JavaScript
  • Add more components: tables, forms, modals
  • Build a complete dashboard like AdminOS

This tutorial was inspired by the AdminOS dashboard template from TemplatesJungle. For more complete templates and examples, visit templatesjungle.com/tailwind-css/.

Happy coding! 🚀

Top comments (0)