Skip to main content
Vue.js for Beginners to Advanced
CHAPTER 24 Beginner

Vue with TailwindCSS

Updated: May 18, 2026
5 min read

# CHAPTER 24

Vue with TailwindCSS

1. Chapter Introduction

TailwindCSS and Vue.js are a perfect pair — Tailwind's utility classes compose beautifully with Vue's dynamic class binding. This combination eliminates context-switching between CSS files and templates, creating faster, more consistent UI development.

2. Learning Objectives

  • Install and configure TailwindCSS in a Vue 3 project.
  • Use Vue's dynamic class binding with Tailwind utilities.
  • Build responsive layouts with Tailwind breakpoints.
  • Implement dark mode with Vue state.
  • Build a complete landing page.

3. Installation

bash
12
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.js
1234567891011121314151617181920
export default {
  content: ['./index.html', './src/**/*.{vue,js,ts}'],
  darkMode: 'class',  // 'media' for system preference, 'class' for manual toggle
  theme: {
    extend: {
      colors: {
        brand: {
          50: '#f0f0ff',
          500: '#6366f1',
          600: '#4f46e5',
          900: '#1e1b4b'
        }
      },
      fontFamily: {
        sans: ['Inter', 'sans-serif']
      }
    }
  },
  plugins: []
}
css
123456789101112131415161718192021
/* src/assets/main.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Custom component classes */
@layer components {
  .btn-primary {
    @apply px-4 py-2 bg-brand-500 text-white font-semibold rounded-lg
           hover:bg-brand-600 focus:outline-none focus:ring-2
           focus:ring-brand-500 focus:ring-offset-2 transition-colors;
  }
  .card {
    @apply bg-white rounded-xl shadow-sm border border-gray-100 p-6;
  }
  .input-field {
    @apply w-full px-4 py-2.5 border border-gray-300 rounded-lg
           focus:outline-none focus:ring-2 focus:ring-brand-500
           focus:border-transparent transition;
  }
}

4. Dynamic Classes with Vue

vue
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
<script setup>
import { ref } from &#039;vue'

const isActive = ref(false)
const size = ref(&#039;md')
const variant = ref(&#039;primary')
const isDark = ref(false)

const sizeClasses = {
  sm: &#039;px-3 py-1.5 text-sm',
  md: &#039;px-4 py-2 text-base',
  lg: &#039;px-6 py-3 text-lg'
}

const variantClasses = {
  primary: &#039;bg-indigo-600 text-white hover:bg-indigo-700',
  secondary: &#039;bg-gray-100 text-gray-900 hover:bg-gray-200',
  danger: &#039;bg-red-600 text-white hover:bg-red-700',
  ghost: &#039;border border-gray-300 text-gray-700 hover:bg-gray-50'
}
</script>

<template>
  <!-- Toggle active with Tailwind -->
  <div
    @click="isActive = !isActive"
    :class="[
      &#039;px-4 py-2 rounded-lg cursor-pointer transition-all',
      isActive
        ? &#039;bg-indigo-600 text-white shadow-lg scale-105'
        : &#039;bg-gray-100 text-gray-700 hover:bg-gray-200'
    ]"
  >
    {{ isActive ? &#039;Active' : 'Inactive' }}
  </div>

  <!-- Dynamic button component -->
  <button
    :class="[
      &#039;font-semibold rounded-lg transition-all focus:outline-none focus:ring-2',
      sizeClasses[size],
      variantClasses[variant]
    ]"
  >
    Dynamic Button
  </button>

  <!-- Dark mode toggle -->
  <div :class="[&#039;min-h-screen p-8 transition-colors', isDark ? 'bg-gray-900 text-white' : 'bg-white text-gray-900']">
    <button @click="isDark = !isDark" :class="[&#039;px-4 py-2 rounded-lg', isDark ? 'bg-white text-gray-900' : 'bg-gray-900 text-white']">
      {{ isDark ? &#039;☀️ Light' : '🌙 Dark' }}
    </button>
  </div>
</template>

5. Mini Project: Landing Page

vue
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
<!-- LandingPage.vue -->
<script setup>
import { ref } from &#039;vue'

const isDark = ref(false)
const email = ref(&#039;')
const subscribed = ref(false)

const features = [
  { icon: &#039;⚡', title: 'Lightning Fast', desc: 'Built with Vite for instant HMR and blazing production builds.' },
  { icon: &#039;🔒', title: 'Secure by Default', desc: 'Enterprise-grade security baked into every layer.' },
  { icon: &#039;📱', title: 'Fully Responsive', desc: 'Works beautifully on every device from mobile to 4K.' },
  { icon: &#039;🌍', title: 'Global CDN', desc: 'Deployed across 200+ edge locations worldwide.' },
  { icon: &#039;🧩', title: 'Component-Driven', desc: 'Build UIs from reusable, testable Vue components.' },
  { icon: &#039;📊', title: 'Real-time Analytics', desc: 'Monitor usage with live dashboards and alerts.' }
]

const stats = [
  { value: &#039;50K+', label: 'Active Users' },
  { value: &#039;99.9%', label: 'Uptime' },
  { value: &#039;200ms', label: 'Avg Response' },
  { value: &#039;4.9★', label: 'User Rating' }
]

function subscribe() {
  if (email.value) subscribed.value = true
}
</script>

<template>
  <div :class="isDark ? &#039;dark' : ''">
    <div class="min-h-screen bg-white dark:bg-gray-950 font-sans">

      <!-- Navbar -->
      <nav class="fixed top-0 inset-x-0 z-50 bg-white/80 dark:bg-gray-950/80 backdrop-blur border-b border-gray-200 dark:border-gray-800">
        <div class="max-w-6xl mx-auto px-6 h-16 flex items-center justify-between">
          <span class="text-xl font-black text-indigo-600">⚡ VueApp</span>
          <div class="hidden md:flex items-center gap-8 text-sm font-medium text-gray-600 dark:text-gray-300">
            <a href="#features" class="hover:text-indigo-600 transition-colors">Features</a>
            <a href="#pricing" class="hover:text-indigo-600 transition-colors">Pricing</a>
            <a href="#" class="hover:text-indigo-600 transition-colors">Docs</a>
          </div>
          <div class="flex items-center gap-3">
            <button @click="isDark = !isDark" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors text-gray-600 dark:text-gray-300">
              {{ isDark ? &#039;☀️' : '🌙' }}
            </button>
            <a href="#" class="px-4 py-2 bg-indigo-600 text-white font-semibold rounded-lg hover:bg-indigo-700 transition-colors text-sm">
              Get Started →
            </a>
          </div>
        </div>
      </nav>

      <!-- Hero -->
      <section class="pt-32 pb-20 px-6">
        <div class="max-w-4xl mx-auto text-center">
          <span class="inline-block px-4 py-1.5 bg-indigo-50 dark:bg-indigo-950 text-indigo-600 dark:text-indigo-400 text-sm font-semibold rounded-full mb-6">
            🚀 Now with Vue 3 + Vite
          </span>
          <h1 class="text-5xl md:text-7xl font-black text-gray-900 dark:text-white leading-tight mb-6">
            Build Better<br/>
            <span class="bg-gradient-to-r from-indigo-600 to-purple-600 bg-clip-text text-transparent">
              Vue Apps
            </span>
          </h1>
          <p class="text-xl text-gray-500 dark:text-gray-400 max-w-2xl mx-auto mb-10">
            The complete Vue 3 toolkit. From prototype to production in hours, not weeks.
          </p>
          <div class="flex flex-col sm:flex-row gap-4 justify-center">
            <a href="#" class="px-8 py-4 bg-indigo-600 text-white font-bold rounded-xl hover:bg-indigo-700 transition-all hover:shadow-lg hover:shadow-indigo-200 dark:hover:shadow-indigo-900/50 text-lg">
              Start Free Trial
            </a>
            <a href="#" class="px-8 py-4 border-2 border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300 font-bold rounded-xl hover:border-indigo-300 transition-all text-lg">
              View Demo →
            </a>
          </div>
        </div>
      </section>

      <!-- Stats -->
      <section class="py-16 bg-indigo-600">
        <div class="max-w-4xl mx-auto px-6 grid grid-cols-2 md:grid-cols-4 gap-8 text-center text-white">
          <div v-for="stat in stats" :key="stat.label">
            <div class="text-4xl font-black mb-1">{{ stat.value }}</div>
            <div class="text-indigo-200 text-sm">{{ stat.label }}</div>
          </div>
        </div>
      </section>

      <!-- Features -->
      <section id="features" class="py-24 px-6">
        <div class="max-w-6xl mx-auto">
          <div class="text-center mb-16">
            <h2 class="text-4xl font-black text-gray-900 dark:text-white mb-4">Everything you need</h2>
            <p class="text-gray-500 dark:text-gray-400 text-lg">All the tools to build modern Vue applications</p>
          </div>
          <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
            <div
              v-for="feature in features"
              :key="feature.title"
              class="p-6 bg-gray-50 dark:bg-gray-900 rounded-2xl hover:bg-white dark:hover:bg-gray-800 hover:shadow-lg transition-all group"
            >
              <div class="text-3xl mb-4">{{ feature.icon }}</div>
              <h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2">{{ feature.title }}</h3>
              <p class="text-gray-500 dark:text-gray-400 text-sm leading-relaxed">{{ feature.desc }}</p>
            </div>
          </div>
        </div>
      </section>

      <!-- CTA -->
      <section class="py-24 px-6 bg-gradient-to-br from-indigo-600 to-purple-700 text-white text-center">
        <div class="max-w-2xl mx-auto">
          <h2 class="text-4xl font-black mb-4">Start building today</h2>
          <p class="text-indigo-200 mb-8 text-lg">Join 50,000+ developers building with Vue</p>
          <div v-if="!subscribed" class="flex flex-col sm:flex-row gap-3 max-w-md mx-auto">
            <input v-model="email" type="email" placeholder="Enter your email" class="flex-1 px-4 py-3 rounded-xl text-gray-900 outline-none" />
            <button @click="subscribe" class="px-6 py-3 bg-white text-indigo-600 font-bold rounded-xl hover:bg-gray-100 transition-colors whitespace-nowrap">
              Get Started Free
            </button>
          </div>
          <p v-else class="text-xl font-bold">🎉 You&#039;re in! Check your email.</p>
        </div>
      </section>

    </div>
  </div>
</template>

6. Common Mistakes

  • Using style attribute with Tailwind: Don't mix style="color: red" with Tailwind — use text-red-500 instead for consistency.
  • Content path misconfiguration: If tailwind.config.js content doesn't include ./src//*.vue, Tailwind purges all classes in production.

7. MCQs

Question 1

Tailwind content config must include?

Question 2

Dark mode darkMode: 'class' works by?

Question 3

@apply in CSS with Tailwind?

Question 4

Dynamic Tailwind classes with Vue?

Question 5

Tailwind responsive prefix?

Question 6

@layer components is for?

Question 7

bg-clip-text text-transparent creates?

Question 8

backdrop-blur applies to?

Question 9

transition-all vs transition-colors?

Question 10

group class on parent with group-hover: on child?

8. Interview Questions

  • Q: How do you conditionally apply Tailwind classes in Vue?
  • Q: What is the purpose of @layer components in Tailwind?

9. Summary

Vue + Tailwind is the dominant modern frontend stack. Vue's :class binding with arrays/objects makes dynamic Tailwind styling clean and readable. Custom @layer components classes extract repeated patterns. Dark mode is a simple darkMode: 'class' config + Vue ref toggle.

10. Next Chapter Recommendation

In
Chapter 25: Performance Optimization in Vue**, we learn lazy loading, code splitting, virtual lists, and techniques to build blazing-fast Vue applications.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·