Tailwind CSS Project — Build a Complete Responsive Landing Page
In this tutorial, you will learn about Tailwind CSS Project. We cover key concepts, practical examples, and best practices to help you master this topic.
Build a complete landing page using every Tailwind CSS concept covered in this course: responsive layout, dark mode, custom components, forms, animations, filters, and production optimization.
What You'll Learn
You will build a full landing page from scratch, applying responsive grid, Flexbox, colors, typography, spacing, positioning, transforms, filters, forms, dark mode, and custom components.
Why It Matters
A capstone project solidifies everything learned. DodaTech's developers build portfolio projects using these exact patterns to demonstrate their Tailwind skills to clients.
Real-World Use
This landing page mirrors the structure used for DodaZIP's marketing site, Doda Browser's extension page, and Durga Antivirus Pro's product landing -- all built with Tailwind utility classes.
flowchart LR
A[Optimization] --> B[Project]
B --> C[Setup]
B --> D[Layout]
B --> E[Components]
B --> F[Dark Mode]
B --> G[Deploy]
style B fill:#38bdf8,stroke:#0284c7,color:#fff
style C fill:#22c55e,stroke:#16a34a,color:#fff
Project Setup
# Create project
mkdir landing-page && cd landing-page
npm init -y
npm install tailwindcss @tailwindcss/cli @tailwindcss/forms @tailwindcss/typography
npx @tailwindcss/cli -i src/input.css -o dist/output.css --watch
/* src/input.css */
@import "tailwindcss";
// tailwind.config.js
module.exports = {
content: ['./index.html', './src/**/*.{js,html}'],
darkMode: 'class',
theme: {
extend: {
colors: {
brand: { 50: '#f5f3ff', 100: '#ede9fe', 500: '#7c3aed', 600: '#6d28d9', 700: '#5b21b6' },
},
fontFamily: { sans: ['Inter', 'sans-serif'], display: ['Playfair Display', 'serif'] },
},
},
plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')],
}
Expected output: A Tailwind project with forms and typography plugins, custom brand colors, and custom fonts ready for development.
Page Layout Structure
<!-- index.html skeleton -->
<div class="min-h-screen bg-white dark:bg-gray-900 transition-colors duration-300">
<!-- Navigation -->
<nav class="sticky top-0 z-50 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-gray-200 dark:border-gray-700">
<!-- Logo, links, dark mode toggle -->
</nav>
<!-- Hero Section -->
<section class="relative overflow-hidden bg-gradient-to-br from-brand-50 to-white dark:from-gray-900 dark:to-gray-800">
<!-- Hero content -->
</section>
<!-- Features Grid -->
<section class="py-20 px-4">
<!-- Feature cards -->
</section>
<!-- Testimonials -->
<section class="py-20 bg-gray-50 dark:bg-gray-800">
<!-- Testimonial cards -->
</section>
<!-- Pricing Section -->
<section class="py-20 px-4">
<!-- Pricing cards -->
</section>
<!-- Contact Form -->
<section class="py-20 bg-gray-50 dark:bg-gray-800">
<!-- Form -->
</section>
<!-- Footer -->
<footer class="bg-gray-900 dark:bg-black text-gray-400">
<!-- Footer content -->
</footer>
</div>
Expected output: A complete page structure with 7 sections: nav, hero, features, testimonials, pricing, contact, footer -- all with dark mode support.
Navigation Component
<nav class="sticky top-0 z-50 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-gray-200 dark:border-gray-700">
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<!-- Logo -->
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-brand-500 rounded-lg"></div>
<span class="font-bold text-xl text-gray-900 dark:text-white">Brand</span>
</div>
<!-- Desktop Nav -->
<div class="hidden md:flex items-center gap-8">
<a href="#features" class="text-gray-600 dark:text-gray-300 hover:text-brand-600 dark:hover:text-brand-400 transition-colors">Features</a>
<a href="#pricing" class="text-gray-600 dark:text-gray-300 hover:text-brand-600 dark:hover:text-brand-400 transition-colors">Pricing</a>
<a href="#contact" class="text-gray-600 dark:text-gray-300 hover:text-brand-600 dark:hover:text-brand-400 transition-colors">Contact</a>
<button class="bg-brand-600 hover:bg-brand-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors">
Get Started
</button>
<button id="darkToggle" class="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors">
<svg class="w-5 h-5 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"/></svg>
<svg class="w-5 h-5 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.707"/></svg>
</button>
</div>
</div>
</div>
</nav>
<script>
document.getElementById('darkToggle').addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
});
</script>
Expected output: A sticky nav with backdrop blur, logo, links, CTA button, and dark mode toggle. Responsive: hidden md:flex for desktop links.
Hero Section
<section class="relative overflow-hidden bg-gradient-to-br from-brand-50 to-white dark:from-gray-900 dark:to-gray-800 py-20 lg:py-32">
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-col lg:flex-row items-center gap-12">
<!-- Text Content -->
<div class="flex-1 text-center lg:text-left">
<div class="inline-block bg-brand-100 dark:bg-brand-900/30 text-brand-700 dark:text-brand-300 px-4 py-1 rounded-full text-sm font-medium mb-6">
New: Version 3.0 Released
</div>
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-white leading-tight">
Build Faster with
<span class="text-brand-600 dark:text-brand-400">Tailwind CSS</span>
</h1>
<p class="mt-6 text-lg sm:text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto lg:mx-0 leading-relaxed">
Create beautiful, responsive interfaces in record time. No custom CSS, no context switching, no complexity.
</p>
<div class="mt-8 flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
<a href="#" class="bg-brand-600 hover:bg-brand-700 text-white px-8 py-4 rounded-xl text-lg font-medium transition-all hover:scale-105 active:scale-95 text-center">
Start Building
</a>
<a href="#" class="border-2 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:border-brand-500 px-8 py-4 rounded-xl text-lg font-medium transition-all text-center">
Learn More
</a>
</div>
</div>
<!-- Hero Image -->
<div class="flex-1 relative">
<div class="w-full aspect-square max-w-md mx-auto bg-gradient-to-br from-brand-400 to-purple-600 rounded-2xl shadow-2xl
hover:scale-105 transition-transform duration-300 motion-reduce:transform-none
flex items-center justify-center text-white text-6xl font-bold opacity-80">
TW
</div>
<div class="absolute -bottom-4 -left-4 w-24 h-24 bg-yellow-400 rounded-2xl -rotate-12
hidden lg:block animate-bounce motion-reduce:animate-none"></div>
</div>
</div>
</div>
</section>
Expected output: A hero with badge, heading, subtitle, two buttons (with hover/active effects), decorative image box, and floating accent shape.
Features Grid
<section id="features" class="py-20 px-4">
<div class="max-w-6xl mx-auto">
<div class="text-center mb-16">
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white">Everything You Need</h2>
<p class="mt-4 text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">Built with Tailwind's utility classes for maximum flexibility.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<!-- Feature Card 1 -->
<div class="group bg-white dark:bg-gray-800 p-8 rounded-xl border border-gray-200 dark:border-gray-700
hover:shadow-xl hover:border-brand-200 dark:hover:border-brand-700
transition-all duration-300 motion-reduce:transition-none">
<div class="w-12 h-12 bg-brand-100 dark:bg-brand-900/50 rounded-lg flex items-center justify-center mb-4
group-hover:scale-110 transition-transform motion-reduce:transform-none">
<svg class="w-6 h-6 text-brand-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white group-hover:text-brand-600 dark:group-hover:text-brand-400 transition-colors">Lightning Fast</h3>
<p class="mt-2 text-gray-600 dark:text-gray-400">Built with the JIT engine for near-instant build times and tiny production bundles.</p>
</div>
<!-- Feature Card 2 -->
<div class="group bg-white dark:bg-gray-800 p-8 rounded-xl border border-gray-200 dark:border-gray-700
hover:shadow-xl hover:border-brand-200 dark:hover:border-brand-700
transition-all duration-300">
<div class="w-12 h-12 bg-green-100 dark:bg-green-900/50 rounded-lg flex items-center justify-center mb-4
group-hover:scale-110 transition-transform">
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white group-hover:text-green-600 dark:group-hover:text-green-400 transition-colors">Secure by Default</h3>
<p class="mt-2 text-gray-600 dark:text-gray-400">Used by Durga Antivirus Pro for production dashboards handling sensitive data.</p>
</div>
<!-- Feature Card 3 -->
<div class="group bg-white dark:bg-gray-800 p-8 rounded-xl border border-gray-200 dark:border-gray-700
hover:shadow-xl hover:border-brand-200 dark:hover:border-brand-700
transition-all duration-300">
<div class="w-12 h-12 bg-purple-100 dark:bg-purple-900/50 rounded-lg flex items-center justify-center mb-4
group-hover:scale-110 transition-transform">
<svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zm10 0a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/>
</svg>
</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white group-hover:text-purple-600 dark:group-hover:text-purple-400 transition-colors">Fully Responsive</h3>
<p class="mt-2 text-gray-600 dark:text-gray-400">Mobile-first responsive utilities make every layout adaptable without extra effort.</p>
</div>
</div>
</div>
</section>
Expected output: A 3-column responsive feature grid with cards that have hover effects, icon animations, and dark mode variants.
Dark Mode Toggle and Motion Respect
<!-- Dark mode toggle JavaScript -->
<script>
// Use class strategy with localStorage persistence
const darkToggle = document.getElementById('darkToggle');
// Check for saved preference
if (localStorage.getItem('darkMode') === 'true' ||
(!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
}
darkToggle.addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
localStorage.setItem('darkMode', document.documentElement.classList.contains('dark'));
});
// Respect prefers-reduced-motion
const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
if (motionQuery.matches) {
document.documentElement.classList.add('motion-reduce');
}
</script>
Expected output: Dark mode persists across page loads via localStorage. Motion-reduce is respected for users with vestibular disorders.
Contact Form (Using Forms Plugin)
<section id="contact" class="py-20 bg-gray-50 dark:bg-gray-800 px-4">
<div class="max-w-xl mx-auto">
<h2 class="text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white text-center">Get In Touch</h2>
<p class="mt-4 text-lg text-gray-600 dark:text-gray-300 text-center mb-12">Have questions? We'd love to hear from you.</p>
<form class="space-y-6">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">First Name</label>
<input type="text" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white
focus:border-brand-500 focus:ring-brand-500 placeholder:text-gray-400">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Last Name</label>
<input type="text" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white
focus:border-brand-500 focus:ring-brand-500">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Email</label>
<input type="email" required class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white
focus:border-brand-500 focus:ring-brand-500
valid:border-green-500 invalid:border-red-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Message</label>
<textarea rows="4" class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white
focus:border-brand-500 focus:ring-brand-500"></textarea>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" class="rounded border-gray-300 dark:border-gray-600 text-brand-600 focus:ring-brand-500">
<label class="text-sm text-gray-600 dark:text-gray-400">I agree to the privacy policy</label>
</div>
<button type="submit" class="w-full bg-brand-600 hover:bg-brand-700 text-white py-3 rounded-lg font-medium
transition-all hover:scale-[1.02] active:scale-[0.98] motion-reduce:transition-none">
Send Message
</button>
</form>
</div>
</section>
Expected output: A complete contact form with validation, dark mode, focus rings, and interactive button.
Building and Deploying
# Build for production (minified)
npx @tailwindcss/cli -i src/input.css -o dist/output.css --minify
# Check bundle size
gzip -c dist/output.css | wc -c
# Optimize further with CSSNano (optional)
npm install -D cssnano
Expected output: A minified production CSS file. The landing page is ready for deployment with all Tailwind features applied.
Common Mistakes
1. Not Building for Production
Serving the development CSS (500KB+) instead of the production build (under 10KB) drastically impacts performance.
2. Forgetting Dark Mode Initialization
Without checking localStorage and system preference on page load, dark mode resets on page refresh.
3. Skipping motion-reduce Variants
Animations and transforms without motion-reduce fallbacks can cause discomfort for users with motion sensitivity.
4. Not Testing Responsiveness at Real Breakpoints
Check the layout at 320px, 640px, 768px, 1024px, and 1280px. Do not rely on emulators alone.
5. Missing Content Paths in Production
Ensure the content array in tailwind.config.js covers all template files used in the production build.
Practice Questions
What is the recommended file structure for a Tailwind landing page? Single HTML file with inline utilities, tailwind.config.js for customization, and a small input.css with @import "tailwindcss".
How do you persist dark mode preference? Save to localStorage:
localStorage.setItem('darkMode', 'true')and check on page load.What is the best way to handle responsive images? Use object-cover and object-center with responsive width/height via w-full and aspect-*.
How do you ensure Accessibility in forms? Use labels with for attributes, focus-visible for keyboard, sr-only for hidden labels, and aria-* attributes.
What is the final build step?
npx @tailwindcss/cli -i input.css -o output.css --minifyand verify the gzipped size.
Challenge
Add a pricing section with 3 tiers (Free, Pro, Enterprise) using responsive cards. Each card has group-hover effects, dark mode, a featured highlight for the Pro tier, and a CTA button with hover/active states.
FAQ
Mini Project
Build and deploy a complete landing page with: sticky nav with dark mode toggle, hero with animation, 3 feature cards, 3 pricing cards, testimonials carousel, contact form, and footer. Optimize to under 10KB gzipped CSS.
What's Next
Congratulations on completing Tailwind CSS! Explore Tailwind CSS v4 for the latest features including CSS-first configuration, unified variants, and container queries.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro