Chapter 14: Vue CSS Binding
Vue CSS Binding — one of the most practical and frequently used parts of Vue 3. This is how you make your UI look dynamic: change colors, sizes, visibility classes, themes, active states, error styles — all based on your data, without writing tons of JavaScript DOM manipulation.
In Vue 3 (2026 standard), CSS binding happens mainly through the v-bind directive (shorthand 🙂 on two special attributes:
- :class (or v-bind:class) → for adding/removing/toggling CSS classes
- :style (or v-bind:style) → for inline styles (CSS properties directly on the element)
Vue gives these two superpowers — they understand objects, arrays, and even strings — so you don’t have to concatenate strings like ‘btn ‘ + (isActive ? ‘btn-primary’ : ‘btn-secondary’) (which gets messy fast).
There’s also a bonus feature in Vue 3: v-bind() inside <style> tags (SFC CSS function) — lets you use reactive state directly in CSS rules.
We’ll cover all three today with modern <script setup> examples.
1. Binding Classes with :class (Most Common Way)
A. Object Syntax – Toggle classes conditionally (favorite pattern)
Pass an object where:
- Key = class name you want to apply
- Value = truthy/falsy expression (true → add class, false → remove)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<template> <div class="card base-card" :class="{ 'active': isActive, 'error': hasError, 'disabled': isLoading, 'premium': user.isPremium }" > <h3>Status Card</h3> <p>Click to toggle states</p> </div> <div class="controls"> <button @click="isActive = !isActive">Toggle Active</button> <button @click="hasError = !hasError">Toggle Error</button> <button @click="isLoading = !isLoading">Toggle Loading</button> </div> </template> <script setup> import { ref } from 'vue' const isActive = ref(false) const hasError = ref(false) const isLoading = ref(false) const user = ref({ isPremium: true }) </script> <style scoped> .base-card { padding: 1.5rem; border: 2px solid #6b7280; border-radius: 12px; transition: all 0.3s ease; } .active { border-color: #3b82f6; box-shadow: 0 0 15px rgba(59,130,246,0.5); } .error { border-color: #ef4444; background: #fee2e2; } .disabled { opacity: 0.6; pointer-events: none; } .premium { background: linear-gradient(135deg, #fef3c7, #fbbf24); } .controls { margin-top: 1.5rem; display: flex; gap: 1rem; flex-wrap: wrap; } </style> |
→ Toggle buttons → classes appear/disappear instantly, smoothly thanks to transition
B. Array Syntax – Mix static + conditional classes
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
<div :class="[ 'btn', size === 'large' ? 'btn-large' : 'btn-small', { 'btn-primary': variant === 'primary', 'btn-danger': variant === 'danger' } ]"> Click Me </div> |
C. String + Object/Array combo (flexible)
|
0 1 2 3 4 5 6 |
<div :class="['text-base', { 'font-bold': isBold, 'italic': isItalic }]"> |
2. Binding Inline Styles with :style
Two main ways: object or array.
A. Object Syntax (most readable)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<div :style="{ color: textColor, fontSize: fontSize + 'px', backgroundColor: bgColor, '--my-shadow': shadowValue // even custom properties! }" > Styled Content </div> |
|
0 1 2 3 4 5 6 7 8 9 |
const textColor = ref('#1e40af') const fontSize = ref(20) const bgColor = ref('#eff6ff') const shadowValue = ref('0 10px 25px rgba(0,0,0,0.1)') |
→ Change refs → styles update live
B. Array Syntax – Merge multiple style objects
|
0 1 2 3 4 5 6 |
<div :style="[baseStyles, overrideStyles, { opacity: isVisible ? 1 : 0.4 }]"> |
|
0 1 2 3 4 5 6 7 |
const baseStyles = { padding: '1rem', borderRadius: '8px' } const overrideStyles = ref({ backgroundColor: '#dbeafe' }) |
3. Vue 3 Magic: v-bind() in <style> – Reactive CSS Variables (SFC Feature)
Since Vue 3, you can use component state directly inside CSS using the v-bind() CSS function. Vue compiles it to a hashed CSS custom property → scoped & safe.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<template> <div class="theme-box"> <p class="title">Dynamic Theme</p> <button @click="toggleTheme">Toggle Dark/Light</button> </div> <input type="color" v-model="primaryColor" /> </template> <script setup> import { ref } from 'vue' const isDark = ref(false) const primaryColor = ref('#3b82f6') const toggleTheme = () => { isDark.value = !isDark.value } </script> <style scoped> .theme-box { padding: 2rem; border-radius: 12px; transition: all 0.4s ease; background: v-bind(isDark ? '#1f2937' : '#f3f4f6'); color: v-bind(isDark ? '#f3f4f6' : '#111827'); border: 1px solid v-bind(isDark ? '#4b5563' : '#d1d5db'); } .title { color: v-bind(primaryColor); font-size: v-bind('isDark ? "2.25rem" : "1.875rem"'); font-weight: bold; } </style> |
→ Change color picker or toggle button → entire theme reacts, without inline styles cluttering HTML
Pro tip: For complex themes → use CSS variables + v-bind() on :root or component root.
|
0 1 2 3 4 5 6 7 8 9 10 11 |
:root { --primary: v-bind(primaryColor); } button { background: var(--primary); } |
Quick Decision Table – When to Use What
| Goal | Best Approach | Why? / When |
|---|---|---|
| Toggle active/error/success class | :class object { active: flag } | Clean, readable, common |
| Mix static + conditional classes | :class array | When you have base classes + conditions |
| Dynamic font-size/color/padding | :style object | Precise control over properties |
| Theme colors, shadows, gradients | v-bind() in <style> | Scoped, no HTML pollution, great for design systems |
| Multiple style sources | :style array | Merging defaults + overrides |
| Very complex dynamic CSS | Combination + CSS vars | Scalable themes (Tailwind + custom vars) |
Pro Tips from 2026 Hyderabad Projects
- Prefer Tailwind? Use :class with object syntax → class=”{ ‘bg-blue-600 text-white’: isActive, ‘bg-gray-200’: !isActive }”
- Avoid long inline :style — move complex logic to computed or CSS vars
- Performance: Vue is smart — only updates changed classes/styles
- Scoped styles + v-bind() in <style scoped> = perfect encapsulation
Practice challenge: Build a pricing card that:
- Changes border/background on hover/active
- Shows “Recommended” badge with different color
- Uses v-bind() in style for dynamic accent color from a color picker
Any part confusing? Want a full Tailwind + :class example? Or dark mode toggle with v-bind() + prefers-color-scheme? Or how to debug when classes don’t apply?
Just tell me — we’ll debug it together over virtual chai ☕🚀
Happy styling from Hyderabad! 💙
