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:

vue
<p :class="['badge', isActive ? 'badge--on' : 'badge--off']">Status</p>

Array + object combined:

vue
<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:

vue
<style scoped>
.wrapper :deep(.child-title) {
  font-weight: bold;
}
</style>

CSS Modules alternative:

vue
<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:

vue
<script setup lang="ts">
const theme = ref({ color: "#2c3e50", fontSize: "16px" });
</script>
 
<template>
  <p :style="theme">Themed text</p>
</template>

Array of style objects:

vue
<p :style="[baseStyles, highlightStyles]">Combined</p>

CSS custom properties—great for theming:

vue
<div :style="{ '--accent': accentColor }" class="panel">
  <slot />
</div>
vue
<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:

vue
<!-- 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:

PhaseClass
Enter startfade-enter-from
Enter endfade-enter-to
Leave startfade-leave-from
Leave endfade-leave-to

name="fade" prefixes generated classes.

Transition Modes and JS Hooks

Out-then-in for swapping content:

vue
<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'
  • :class maps to badge--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.