VOOZH about

URL: https://dev.to/uijunkie/build-an-admin-dashboard-with-tailwind-css-a-practical-tutorial-2ba2

⇱ Build an Admin Dashboard with Tailwind CSS - A Practical Tutorial - DEV Community


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">

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>

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>

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>

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>

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>

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>

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>

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! 🚀