Class, Style, and Transitions
Introduction
Vue binds CSS classes and inline styles to reactive data—so UI state (active tab, error, loading) drives appearance without manual DOM classList calls. This chapter deepens :class and :style, pairs them with scoped styles, and introduces <Transition> for enter/leave animations.
Prerequisites
Dynamic Class Binding
Object syntax—class name maps to boolean:
Array syntax—mix static and conditional strings:
<p :class="['badge', isActive ? 'badge--on' : 'badge--off']">Status</p>Array + object combined:
<div :class="[{ open: isOpen }, sizeClass]"></div>Code explanation:
- Prefer object syntax when several flags toggle classes
- Use kebab-case class names in strings:
'card--active'
Scoped Styles in SFC
scoped adds data attributes so rules apply only to this component’s template.
Deep selector for child component roots:
<style scoped>
.wrapper :deep(.child-title) {
font-weight: bold;
}
</style>CSS Modules alternative:
<template>
<p :class="$style.title">Hello</p>
</template>
<style module>
.title {
color: #35495e;
}
</style>See CSS architecture and naming for BEM-style class names like card--active.
Inline Style Binding
Object:
<script setup lang="ts">
const theme = ref({ color: "#2c3e50", fontSize: "16px" });
</script>
<template>
<p :style="theme">Themed text</p>
</template>Array of style objects:
<p :style="[baseStyles, highlightStyles]">Combined</p>CSS custom properties—great for theming:
<div :style="{ '--accent': accentColor }" class="panel">
<slot />
</div><style scoped>
.panel {
border-left: 4px solid var(--accent);
padding-left: 1rem;
}
</style>Binding Class from Props
Preview of parent/child flow—details in Components:
<!-- ChildButton.vue -->
<script setup lang="ts">
defineProps<{
variant?: "primary" | "ghost";
}>();
</script>
<template>
<button class="btn" :class="`btn--${variant ?? 'primary'}`">
<slot />
</button>
</template>Transition Component
Fade a single element in and out:
Vue 3 class names:
| Phase | Class |
|---|---|
| Enter start | fade-enter-from |
| Enter end | fade-enter-to |
| Leave start | fade-leave-from |
| Leave end | fade-leave-to |
name="fade" prefixes generated classes.
Transition Modes and JS Hooks
Out-then-in for swapping content:
<Transition name="fade" mode="out-in">
<component :is="currentView" :key="viewKey" />
</Transition>mode="out-in" — old element leaves before new enters.
TransitionGroup for Lists
Animate v-for items:
tag="ul" renders a real <ul> wrapper; each child needs :key.
Tip
Keep animations subtle in admin UIs—respect prefers-reduced-motion in production CSS when possible.
Practice: Status Badge
Build StatusBadge.vue:
- Props:
status: 'ok' | 'warn' | 'error' :classmaps tobadge--ok, etc.- Optional
<Transition>when status changes
FAQ
string vs object?
String for one dynamic class; object for multiple toggles.
scoped break child styling?
Use :deep(), ::v-deep, or pass classes via props.
Transition without CSS?
@enter / @leave JS hooks—advanced; CSS is enough for most cases.
v-show vs Transition?
Wrap v-if inside <Transition>; v-show toggles display—transitions need element mount/unmount or custom handling.
Same as CSS transition property?
Vue adds/removes classes at the right lifecycle points—you still write CSS rules.
Animation libraries?
@vueuse/motion, GSAP—optional; not required for this track.