Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
16 minLesson 23 of 40
Tailwind CSS

Dark Mode & Responsive Tailwind

Dark Mode and Responsive Design with Tailwind

Two of the most practical things you need to build are responsive layouts and dark mode. Tailwind makes both straightforward with prefix-based variants — no separate stylesheets, no media query management. Just prefixes.

Responsive Design in Tailwind

Tailwind uses a mobile-first approach. Classes without a prefix apply at all screen sizes. Add a breakpoint prefix to override at larger screens:

PrefixMin-widthTypical use
(none)0pxMobile — base styles
sm:640pxLarge phones
md:768pxTablets
lg:1024pxLaptops
xl:1280pxDesktops
2xl:1536pxWide screens

The Mobile-First Pattern

Start with mobile styles, then add breakpoint overrides:

<!-- Single column → two columns → three columns -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
    <div class="bg-white p-6 rounded-xl shadow-sm">Card 1</div>
    <div class="bg-white p-6 rounded-xl shadow-sm">Card 2</div>
    <div class="bg-white p-6 rounded-xl shadow-sm">Card 3</div>
</div>

<!-- Text size scales up on larger screens -->
<h1 class="text-2xl md:text-4xl lg:text-5xl font-bold">
    Build Something Great
</h1>

<!-- Hidden on mobile, visible on desktop -->
<aside class="hidden lg:block w-64 flex-shrink-0">
    Sidebar content
</aside>

<!-- Visible on mobile only (hamburger menu) -->
<button class="lg:hidden p-2 text-gray-600">
    Menu
</button>

Responsive Layout Example

A common layout: stacked on mobile, side-by-side on desktop:

<!-- Hero section -->
<section class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 lg:py-24">
    <div class="flex flex-col lg:flex-row items-center gap-12">
        
        <!-- Text content — full width mobile, half on desktop -->
        <div class="flex-1 text-center lg:text-left">
            <h1 class="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 leading-tight">
                Learn to build<br/>
                <span class="text-blue-600">real-world apps</span>
            </h1>
            <p class="mt-4 text-gray-600 text-lg leading-relaxed max-w-xl mx-auto lg:mx-0">
                Practical courses that take you from zero to hired. 
                Join 50,000 developers learning with us.
            </p>
            <div class="mt-8 flex flex-col sm:flex-row gap-3 justify-center lg:justify-start">
                <button class="bg-blue-600 hover:bg-blue-700 text-white font-semibold px-6 py-3 rounded-xl transition-colors">
                    Start Learning Free
                </button>
                <button class="border border-gray-300 text-gray-700 hover:bg-gray-50 font-semibold px-6 py-3 rounded-xl transition-colors">
                    See Courses
                </button>
            </div>
        </div>
        
        <!-- Image — hidden on small mobile, shown on sm+ -->
        <div class="hidden sm:block flex-1">
            <img src="/hero.png" alt="Dashboard" class="w-full rounded-2xl shadow-2xl"/>
        </div>
        
    </div>
</section>

<!-- Responsive stats bar -->
<div class="border-y border-gray-200 py-8">
    <div class="max-w-7xl mx-auto px-4 grid grid-cols-2 md:grid-cols-4 gap-8 text-center">
        <div>
            <p class="text-3xl font-bold text-gray-900">50k+</p>
            <p class="text-gray-500 text-sm mt-1">Students</p>
        </div>
        <div>
            <p class="text-3xl font-bold text-gray-900">9</p>
            <p class="text-gray-500 text-sm mt-1">Courses</p>
        </div>
        <div>
            <p class="text-3xl font-bold text-gray-900">200+</p>
            <p class="text-gray-500 text-sm mt-1">Lessons</p>
        </div>
        <div>
            <p class="text-3xl font-bold text-gray-900">4.9★</p>
            <p class="text-gray-500 text-sm mt-1">Rating</p>
        </div>
    </div>
</div>

Dark Mode Setup

Enable dark mode in tailwind.config.js:

export default {
    darkMode: "class",  // Toggle dark mode by adding class="dark" to <html>
    // ...
};

With "class" mode, you control dark mode explicitly by toggling the dark class on <html>. This is the most reliable approach — it respects user preference and allows manual toggle.

The alternative is "media" which always follows prefers-color-scheme and can't be overridden.

Using Dark Mode Variants

Prefix any utility with dark: to apply it only in dark mode:

<!-- Background switches -->
<body class="bg-white dark:bg-gray-950">

<!-- Text color switches -->
<h1 class="text-gray-900 dark:text-gray-100">Title</h1>
<p class="text-gray-600 dark:text-gray-400">Body text</p>

<!-- Border color switches -->
<div class="border border-gray-200 dark:border-gray-800">

<!-- Card with dark mode -->
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-xl p-6 shadow-sm">
    <h2 class="text-gray-900 dark:text-white font-semibold">Card Title</h2>
    <p class="text-gray-600 dark:text-gray-400 mt-2 text-sm">Description text</p>
</div>

<!-- Button in dark mode -->
<button class="bg-blue-600 hover:bg-blue-500 dark:bg-blue-500 dark:hover:bg-blue-400 text-white font-semibold px-5 py-2.5 rounded-lg transition-colors">
    Get Started
</button>

<!-- Input in dark mode -->
<input 
    type="text" 
    class="bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 rounded-lg px-3 py-2.5 focus:border-blue-500 outline-none"
    placeholder="Search..."
/>

Dark Mode Toggle in JavaScript

// Toggle dark mode
function toggleDarkMode() {
    document.documentElement.classList.toggle("dark");
    
    // Save preference
    const isDark = document.documentElement.classList.contains("dark");
    localStorage.setItem("theme", isDark ? "dark" : "light");
}

// Restore preference on load (put this before your app renders to avoid flash)
const saved = localStorage.getItem("theme");
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;

if (saved === "dark" || (!saved && prefersDark)) {
    document.documentElement.classList.add("dark");
}

In React:

function ThemeToggle() {
    const [isDark, setIsDark] = useState(() => {
        const saved = localStorage.getItem("theme");
        if (saved) return saved === "dark";
        return window.matchMedia("(prefers-color-scheme: dark)").matches;
    });
    
    useEffect(() => {
        if (isDark) {
            document.documentElement.classList.add("dark");
            localStorage.setItem("theme", "dark");
        } else {
            document.documentElement.classList.remove("dark");
            localStorage.setItem("theme", "light");
        }
    }, [isDark]);
    
    return (
        <button
            onClick={() => setIsDark(prev => !prev)}
            className="p-2 rounded-lg text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
            aria-label="Toggle dark mode"
        >
            {isDark ? (
                <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
                    <path fillRule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clipRule="evenodd"/>
                </svg>
            ) : (
                <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
                    <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
                </svg>
            )}
        </button>
    );
}

Full Dark Mode Card

A practical component with full light/dark support:

<!-- Pricing card -->
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl p-8 shadow-sm hover:shadow-md transition-shadow">
    
    <div class="flex items-center justify-between mb-6">
        <h3 class="text-lg font-semibold text-gray-900 dark:text-white">Pro Plan</h3>
        <span class="bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-400 text-xs font-semibold px-2.5 py-1 rounded-full">
            Most Popular
        </span>
    </div>
    
    <div class="mb-6">
        <span class="text-4xl font-bold text-gray-900 dark:text-white">$49</span>
        <span class="text-gray-500 dark:text-gray-400">/month</span>
    </div>
    
    <ul class="space-y-3 mb-8">
        <li class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
            <svg class="w-4 h-4 text-green-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
                <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
            </svg>
            All 9 courses
        </li>
        <li class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
            <svg class="w-4 h-4 text-green-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
                <path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
            </svg>
            Certificate of completion
        </li>
    </ul>
    
    <button class="w-full bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-400 text-white font-semibold py-3 rounded-xl transition-colors">
        Start Free Trial
    </button>
    
</div>

Responsive Navigation with Dark Mode

<nav class="bg-white dark:bg-gray-950 border-b border-gray-200 dark:border-gray-800 sticky top-0 z-50">
    <div class="max-w-7xl mx-auto px-4 sm:px-6">
        <div class="flex items-center justify-between h-16">
            
            <a href="/" class="text-xl font-bold text-gray-900 dark:text-white">
                AiTechWorlds
            </a>
            
            <div class="hidden md:flex items-center gap-1">
                <a href="/courses" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium transition-colors">
                    Courses
                </a>
                <a href="/pricing" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium transition-colors">
                    Pricing
                </a>
            </div>
            
            <div class="flex items-center gap-3">
                <!-- Dark mode toggle -->
                <button id="theme-toggle" class="p-2 text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors">
                    <!-- Sun/Moon icon here -->
                </button>
                
                <a href="/signup" class="hidden md:block bg-blue-600 hover:bg-blue-700 text-white text-sm font-semibold px-4 py-2 rounded-lg transition-colors">
                    Get Started
                </a>
            </div>
            
        </div>
    </div>
</nav>

Container Queries (New in Tailwind v3.2+)

Container queries let components respond to their own width rather than the viewport. Great for reusable components used in different contexts:

// tailwind.config.js — enable the plugin
import containerQueries from "@tailwindcss/container-queries";
export default { plugins: [containerQueries] };
<!-- Mark the container -->
<div class="@container">
    <!-- Styles based on the container's width, not the viewport -->
    <div class="flex flex-col @md:flex-row gap-4">
        <img class="w-full @md:w-32 rounded-lg" src="/img.jpg" alt=""/>
        <div>
            <h3 class="font-semibold @md:text-lg">Course Title</h3>
            <p class="text-gray-500 text-sm">Description</p>
        </div>
    </div>
</div>

This card stacks vertically when in a narrow sidebar, but goes horizontal when placed in a wide main area — without any global media query.

Next lesson: Next.js setup and routing — moving from React to full-stack development.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!