Skip to main content
Technology & EngineeringTailwind284 lines

Tailwind Animations

Animation utilities, transitions, custom keyframes, and motion patterns with Tailwind CSS

Quick Summary27 lines
You are an expert in implementing animations and transitions with Tailwind CSS.

## Key Points

- **Keep animations subtle and fast.** 150-300ms is the sweet spot for UI transitions. Anything over 500ms feels sluggish.
- **Use `ease-out` for entrances, `ease-in` for exits.** Elements entering view should decelerate; elements leaving should accelerate.
- **Always support `prefers-reduced-motion`.** Use the `motion-reduce:` variant to disable or simplify animations for users who need it.
- **Animate transforms and opacity.** These are GPU-accelerated and do not trigger layout recalculation. Avoid animating `width`, `height`, `top`, or `left`.
- **Use `will-change` sparingly.** Adding `will-change-transform` can help with jank, but overuse wastes GPU memory.
- **Prefer `transition` for state changes and `animate` for looping/entrance animations.**
- **Animating layout properties.** Transitioning `width`, `height`, `padding`, or `margin` causes expensive reflows. Use `transform: scale()` or `max-height` workarounds instead.
- **Forgetting `transform` base class.** In Tailwind v3, `hover:-translate-y-1` requires the `transform` class on the element (in v3.0-3.3). In later versions and v4, this is automatic.
- **Not testing on low-end devices.** Animations that are smooth on a fast laptop may stutter on budget phones. Always test on target devices.

## Quick Example

```html
<div class="animate-fade-in-up">Fades in from below</div>
<div class="animate-scale-in">Scales up into view</div>
```

```html
<!-- Opt-in to view transitions -->
<meta name="view-transition" content="same-origin" />
```
skilldb get tailwind-skills/Tailwind AnimationsFull skill: 284 lines
Paste into your CLAUDE.md or agent config

Animation Utilities and Custom Animations — Tailwind CSS

You are an expert in implementing animations and transitions with Tailwind CSS.

Overview

Tailwind provides built-in transition utilities, transform utilities, and a set of keyframe animations out of the box. For more complex animations, you define custom keyframes in tailwind.config.js and reference them as animation utilities. Combined with state variants like hover:, group-hover:, and JavaScript-driven class toggling, Tailwind handles everything from subtle micro-interactions to full entrance animations.

Core Philosophy

Animation in Tailwind should be invisible when done right. The goal is not to showcase motion for its own sake but to make interfaces feel responsive and alive. Every transition should serve a communicative purpose — confirming a user action, revealing new content, or establishing spatial relationships between elements. When a user clicks a button and it subtly depresses, or when a dropdown menu fades in rather than appearing from nowhere, the interface communicates that it is listening and responding.

The utility-first approach to animation means reaching for Tailwind's built-in transition and animation classes before writing custom CSS. Most UI motion needs are covered by combining transition-*, duration-*, and ease-* utilities with state variants like hover: and focus:. Custom keyframe animations belong in the config file, not scattered across component stylesheets, so the team shares a consistent motion vocabulary. This keeps animations discoverable and prevents the codebase from accumulating dozens of one-off CSS animations that subtly differ.

Performance is a non-negotiable constraint. Animating only transform and opacity ensures GPU acceleration and avoids layout thrashing. Accessibility is equally non-negotiable — every animation must degrade gracefully via motion-reduce: variants, because motion that delights one user can disorient another.

Core Concepts

Built-in Transition Utilities

Transitions control how properties animate when they change:

<!-- Transition all properties -->
<button class="bg-blue-600 transition-all duration-200 ease-in-out hover:bg-blue-700 hover:shadow-lg">
  Hover me
</button>

<!-- Transition specific properties -->
<div class="transition-colors duration-150">Color transitions only</div>
<div class="transition-transform duration-300">Transform transitions only</div>
<div class="transition-opacity duration-500">Opacity transitions only</div>
UtilityProperties
transition-noneNone
transition-allAll properties
transition-colorscolor, background-color, border-color, etc.
transition-opacityopacity
transition-shadowbox-shadow
transition-transformtransform
transitionDefault set (colors, opacity, shadow, transform)

Duration and Easing

<div class="transition duration-75">75ms</div>
<div class="transition duration-150">150ms</div>
<div class="transition duration-300">300ms (good default)</div>
<div class="transition duration-500">500ms</div>
<div class="transition duration-1000">1000ms</div>

<div class="transition ease-linear">Linear</div>
<div class="transition ease-in">Ease in (slow start)</div>
<div class="transition ease-out">Ease out (slow end)</div>
<div class="transition ease-in-out">Ease in-out</div>

Built-in Keyframe Animations

Tailwind ships with four keyframe animations:

<!-- Spinner -->
<svg class="animate-spin h-5 w-5 text-blue-600" viewBox="0 0 24 24">...</svg>

<!-- Pulsing skeleton loader -->
<div class="animate-pulse rounded-md bg-gray-200 h-4 w-3/4"></div>

<!-- Notification ping -->
<span class="relative flex h-3 w-3">
  <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-sky-400 opacity-75"></span>
  <span class="relative inline-flex h-3 w-3 rounded-full bg-sky-500"></span>
</span>

<!-- Bounce indicator -->
<svg class="animate-bounce h-6 w-6 text-gray-400" viewBox="0 0 24 24">...</svg>

Implementation Patterns

Custom Keyframe Animations

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      keyframes: {
        'fade-in': {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
        'fade-in-up': {
          '0%': { opacity: '0', transform: 'translateY(16px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
        'fade-in-down': {
          '0%': { opacity: '0', transform: 'translateY(-16px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
        'slide-in-right': {
          '0%': { transform: 'translateX(100%)' },
          '100%': { transform: 'translateX(0)' },
        },
        'slide-in-left': {
          '0%': { transform: 'translateX(-100%)' },
          '100%': { transform: 'translateX(0)' },
        },
        'scale-in': {
          '0%': { opacity: '0', transform: 'scale(0.95)' },
          '100%': { opacity: '1', transform: 'scale(1)' },
        },
        'accordion-down': {
          '0%': { height: '0', opacity: '0' },
          '100%': { height: 'var(--radix-accordion-content-height)', opacity: '1' },
        },
        'accordion-up': {
          '0%': { height: 'var(--radix-accordion-content-height)', opacity: '1' },
          '100%': { height: '0', opacity: '0' },
        },
      },
      animation: {
        'fade-in': 'fade-in 0.3s ease-out',
        'fade-in-up': 'fade-in-up 0.4s ease-out',
        'fade-in-down': 'fade-in-down 0.4s ease-out',
        'slide-in-right': 'slide-in-right 0.3s ease-out',
        'slide-in-left': 'slide-in-left 0.3s ease-out',
        'scale-in': 'scale-in 0.2s ease-out',
        'accordion-down': 'accordion-down 0.2s ease-out',
        'accordion-up': 'accordion-up 0.2s ease-out',
      },
    },
  },
}

Usage:

<div class="animate-fade-in-up">Fades in from below</div>
<div class="animate-scale-in">Scales up into view</div>

Hover Micro-Interactions

<!-- Lift on hover -->
<div class="rounded-lg bg-white p-6 shadow-md transition-all duration-200 hover:-translate-y-1 hover:shadow-xl">
  Card that lifts on hover
</div>

<!-- Scale button -->
<button class="transform rounded-md bg-indigo-600 px-4 py-2 text-white transition-transform duration-150 hover:scale-105 active:scale-95">
  Press me
</button>

<!-- Icon rotation -->
<button class="group flex items-center gap-2">
  <span>Settings</span>
  <svg class="h-4 w-4 transition-transform duration-300 group-hover:rotate-90">...</svg>
</button>

<!-- Underline slide-in -->
<a href="#" class="relative text-gray-700 hover:text-gray-900">
  <span>Link text</span>
  <span class="absolute bottom-0 left-0 h-0.5 w-0 bg-indigo-600 transition-all duration-300 group-hover:w-full"></span>
</a>

Skeleton Loading Screen

<div class="animate-pulse space-y-4">
  <!-- Avatar + name -->
  <div class="flex items-center gap-4">
    <div class="h-12 w-12 rounded-full bg-gray-200"></div>
    <div class="space-y-2">
      <div class="h-4 w-32 rounded bg-gray-200"></div>
      <div class="h-3 w-24 rounded bg-gray-200"></div>
    </div>
  </div>
  <!-- Content lines -->
  <div class="space-y-2">
    <div class="h-4 w-full rounded bg-gray-200"></div>
    <div class="h-4 w-5/6 rounded bg-gray-200"></div>
    <div class="h-4 w-4/6 rounded bg-gray-200"></div>
  </div>
</div>

Staggered Entrance Animations

Use inline animation-delay with a custom utility or arbitrary values:

<div class="space-y-4">
  <div class="animate-fade-in-up opacity-0" style="animation-delay: 0ms; animation-fill-mode: forwards">Item 1</div>
  <div class="animate-fade-in-up opacity-0" style="animation-delay: 100ms; animation-fill-mode: forwards">Item 2</div>
  <div class="animate-fade-in-up opacity-0" style="animation-delay: 200ms; animation-fill-mode: forwards">Item 3</div>
  <div class="animate-fade-in-up opacity-0" style="animation-delay: 300ms; animation-fill-mode: forwards">Item 4</div>
</div>

Or add delay utilities in config:

// tailwind.config.js
extend: {
  animationDelay: {
    '100': '100ms',
    '200': '200ms',
    '300': '300ms',
    '500': '500ms',
  },
}

Page Transitions with View Transitions API

<!-- Opt-in to view transitions -->
<meta name="view-transition" content="same-origin" />
/* Customize with Tailwind-compatible CSS */
::view-transition-old(root) {
  animation: fade-out 0.2s ease-in;
}
::view-transition-new(root) {
  animation: fade-in 0.3s ease-out;
}

Reduced Motion Accessibility

<!-- Respect prefers-reduced-motion -->
<div class="animate-fade-in-up motion-reduce:animate-none motion-reduce:opacity-100">
  Animated unless user prefers reduced motion
</div>

<!-- Alternative: only animate if user is OK with motion -->
<div class="opacity-0 motion-safe:animate-fade-in-up motion-safe:opacity-100">
  Only animates if motion is acceptable
</div>

Best Practices

  • Keep animations subtle and fast. 150-300ms is the sweet spot for UI transitions. Anything over 500ms feels sluggish.
  • Use ease-out for entrances, ease-in for exits. Elements entering view should decelerate; elements leaving should accelerate.
  • Always support prefers-reduced-motion. Use the motion-reduce: variant to disable or simplify animations for users who need it.
  • Animate transforms and opacity. These are GPU-accelerated and do not trigger layout recalculation. Avoid animating width, height, top, or left.
  • Use will-change sparingly. Adding will-change-transform can help with jank, but overuse wastes GPU memory.
  • Prefer transition for state changes and animate for looping/entrance animations.

Common Pitfalls

  • Animating layout properties. Transitioning width, height, padding, or margin causes expensive reflows. Use transform: scale() or max-height workarounds instead.
  • Missing animation-fill-mode: forwards. Without it, elements revert to their pre-animation state after the animation completes. Set opacity-0 as initial and use fill-mode: forwards to keep the final frame.
  • Forgetting transform base class. In Tailwind v3, hover:-translate-y-1 requires the transform class on the element (in v3.0-3.3). In later versions and v4, this is automatic.
  • Stacking too many transitions. Applying transition-all on complex elements with many changing properties can cause performance issues. Be specific with transition-colors or transition-transform.
  • Not testing on low-end devices. Animations that are smooth on a fast laptop may stutter on budget phones. Always test on target devices.

Anti-Patterns

  • Animation soup. Applying entrance animations, hover effects, and infinite loops to multiple elements on the same page creates a chaotic, distracting experience. Limit simultaneous animations and let the primary content hold the user's attention.

  • CSS transition: all on complex components. This animates every changing property including layout-triggering ones like width and padding, causing jank and unintended visual artifacts. Always specify exactly which properties to transition.

  • Ignoring prefers-reduced-motion entirely. Shipping animations without motion-reduce: or motion-safe: variants excludes users with vestibular disorders. This is not an edge case — it is an accessibility failure.

  • Recreating JavaScript animation libraries in Tailwind. Tailwind handles CSS transitions and keyframe animations well, but complex orchestrated sequences, spring physics, or scroll-linked animations belong in dedicated libraries like Framer Motion or GSAP. Forcing these into utility classes produces unmaintainable markup.

  • Staggered animations without animation-fill-mode: forwards. Elements flash to their pre-animation state after the animation completes, creating a jarring flicker. Always pair staggered entrances with fill-mode: forwards and an initial hidden state like opacity-0.

Install this skill directly: skilldb add tailwind-skills

Get CLI access →