0Pricing
Tailwind CSS Academy · Lesson

Built-In Keyframe Animations

Apply Tailwind's built-in animate-spin, animate-ping, animate-pulse, and animate-bounce for common loading and attention-grabbing patterns.

Built-In Keyframe Animations is a free Tailwind CSS Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Tailwind CSS Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

CSS Keyframe Animations vs Transitions

CSS transitions animate between two states defined by a start and end value. CSS keyframe animations define a sequence of multiple states and can loop indefinitely. Tailwind includes four built-in keyframe animations via the animate-* utilities. These cover the most common UI animation patterns: loading spinners, pulsing skeletons, attention-grabbing pings, and bouncing indicators — all without any custom CSS.

animate-spin

animate-spin rotates an element continuously at a constant speed — one full 360-degree rotation per second. It is the standard utility for loading spinners. Apply it to a circular element or SVG icon to indicate an in-progress operation. The animation loops indefinitely until the class is removed. Most commonly paired with an SVG ring icon that has a gap to create the familiar spinner appearance.

<!-- Loading spinner using animate-spin -->
<svg class="animate-spin h-5 w-5 text-blue-600"
     xmlns="http://www.w3.org/2000/svg"
     fill="none" viewBox="0 0 24 24">
  <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
  <path class="opacity-75" fill="currentColor"
        d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
  </path>
</svg>

<!-- Button with spinner -->
<button class="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg" disabled>
  <svg class="animate-spin h-4 w-4" ...></svg>
  Loading...
</button>

animate-ping

animate-ping creates a ripple or radar-sweep effect — the element scales up and fades out in a loop. It is commonly used as a notification dot or activity indicator overlaid on top of a static element. The pinging element should be positioned absolutely on top of the real element using a wrapper with relative. The effect draws the eye to the notification without being overly distracting.

<!-- Notification badge with ping effect -->
<div class="relative inline-flex">
  <button class="p-2 rounded-full bg-gray-100">
    <!-- Bell icon -->
    <svg class="w-5 h-5 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
    </svg>
  </button>
  <!-- Ping indicator -->
  <span class="absolute top-0 right-0 -mt-0.5 -mr-0.5">
    <span class="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-red-400 opacity-75"></span>
    <span class="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
  </span>
</div>

animate-pulse

animate-pulse gently fades an element's opacity back and forth — from full opacity to 50% and back, on a 2-second loop. It is the perfect animation for skeleton loaders that show the shape of content while it is fetching. Apply it to gray placeholder blocks that match the layout of the content being loaded. The subtle pulsing communicates loading without being distracting.

<!-- Skeleton loader using animate-pulse -->
<div class="p-6 space-y-4">
  <!-- Avatar + title skeleton -->
  <div class="flex items-center gap-3">
    <div class="animate-pulse bg-gray-200 rounded-full h-10 w-10"></div>
    <div class="space-y-2 flex-1">
      <div class="animate-pulse bg-gray-200 rounded h-4 w-1/3"></div>
      <div class="animate-pulse bg-gray-200 rounded h-3 w-1/4"></div>
    </div>
  </div>
  <!-- Content lines skeleton -->
  <div class="space-y-2">
    <div class="animate-pulse bg-gray-200 rounded h-4 w-full"></div>
    <div class="animate-pulse bg-gray-200 rounded h-4 w-5/6"></div>
    <div class="animate-pulse bg-gray-200 rounded h-4 w-4/5"></div>
  </div>
</div>

animate-bounce

animate-bounce moves an element up and down in a bouncing motion. It uses a cubic-bezier timing function that creates a natural deceleration at the top of each bounce. Common use cases include a down-arrow indicator prompting users to scroll, a chat bubble typing indicator, or a notification icon drawing attention to new content. The bounce is continuous by default.

<!-- Scroll indicator using animate-bounce -->
<div class="flex flex-col items-center gap-2 mt-16">
  <p class="text-sm text-gray-500">Scroll to explore</p>
  <svg class="animate-bounce w-6 h-6 text-gray-400"
       fill="none" stroke="currentColor" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
  </svg>
</div>

<!-- Typing indicator (three bouncing dots) -->
<div class="flex gap-1 p-3 bg-gray-100 rounded-2xl rounded-tl-none w-16">
  <div class="animate-bounce h-2 w-2 bg-gray-400 rounded-full" style="animation-delay: 0ms"></div>
  <div class="animate-bounce h-2 w-2 bg-gray-400 rounded-full" style="animation-delay: 150ms"></div>
  <div class="animate-bounce h-2 w-2 bg-gray-400 rounded-full" style="animation-delay: 300ms"></div>
</div>

Using animate-none to Stop Animations

animate-none removes any active animation from an element. This is useful with responsive or state variants to disable an animation at certain screen sizes or conditions. For example, you might use motion-reduce:animate-none to disable all animations for users who prefer reduced motion, or stop a loading spinner once data has loaded by toggling the class with JavaScript.

<!-- Stop animation for reduced-motion users -->
<svg class="animate-spin motion-reduce:animate-none h-5 w-5 text-blue-600">
  ...
</svg>

<!-- Stop animation at large screens -->
<div class="animate-pulse lg:animate-none bg-gray-200 rounded h-4 w-48">
  <!-- Only pulses on mobile/tablet, static on desktop -->
</div>

<!-- JavaScript: remove animation when done -->
<!-- spinner.classList.remove('animate-spin'); -->

Combining animate with Conditional Rendering

In component-based frameworks, you typically show the spinning or pulsing state conditionally based on loading state. The animation class is part of the skeleton or loading component that renders while data fetches, then replaced with real content when the fetch completes. This approach keeps animation concerns in the loading state UI rather than adding/removing classes programmatically.

// React: conditional rendering for loading state
function UserCard({ user, isLoading }) {
  if (isLoading) {
    return (
      <div class="p-6 space-y-3">
        <div class="animate-pulse bg-gray-200 rounded-full h-12 w-12"></div>
        <div class="animate-pulse bg-gray-200 rounded h-4 w-32"></div>
        <div class="animate-pulse bg-gray-200 rounded h-3 w-24"></div>
      </div>
    );
  }

  return (
    <div class="p-6">
      <img src={user.avatar} class="h-12 w-12 rounded-full" />
      <p class="font-semibold mt-3">{user.name}</p>
      <p class="text-gray-500 text-sm">{user.role}</p>
    </div>
  );
}

Animation Iteration Count and Fill Mode

Tailwind's built-in animations loop infinitely by default. If you need to run an animation a specific number of times (once for an entrance animation, twice for an attention flash), you must use CSS custom properties or a custom animation class. Extend theme.extend.keyframes and theme.extend.animation in your config to add animations with controlled iteration counts and fill modes.

// tailwind.config.js — add a one-shot animation
module.exports = {
  theme: {
    extend: {
      keyframes: {
        'shake': {
          '0%, 100%': { transform: 'translateX(0)' },
          '20%, 60%': { transform: 'translateX(-6px)' },
          '40%, 80%': { transform: 'translateX(6px)' },
        },
      },
      animation: {
        'shake': 'shake 0.5s ease-in-out 1',  // runs once
      },
    },
  },
};

<!-- Usage -->
<input class="animate-shake border-red-500" type="text" placeholder="Shake on error" />

Staggering Built-In Animations

Staggering animation delays across sibling elements creates an engaging cascading effect. Tailwind does not have a built-in stagger utility, but you can use inline style attributes to set individual animation-delay values. For a list of items, offset each item's delay by 50–100ms. Keep the total stagger time under 300ms so users do not wait for the last item to appear.

<!-- Staggered skeleton loaders -->
<ul class="space-y-3">
  <li class="animate-pulse bg-gray-200 rounded h-12" style="animation-delay: 0ms"></li>
  <li class="animate-pulse bg-gray-200 rounded h-12" style="animation-delay: 75ms"></li>
  <li class="animate-pulse bg-gray-200 rounded h-12" style="animation-delay: 150ms"></li>
  <li class="animate-pulse bg-gray-200 rounded h-12" style="animation-delay: 225ms"></li>
</ul>

<!-- Staggered bounce dots -->
<div class="flex gap-1">
  <div class="animate-bounce w-2 h-2 bg-blue-500 rounded-full" style="animation-delay: 0ms"></div>
  <div class="animate-bounce w-2 h-2 bg-blue-500 rounded-full" style="animation-delay: 150ms"></div>
  <div class="animate-bounce w-2 h-2 bg-blue-500 rounded-full" style="animation-delay: 300ms"></div>
</div>

Applying Animations Responsively

Like all Tailwind utilities, animate classes work with responsive prefixes. You can disable an animation on large screens where the loading state might be less necessary, or apply different animations at different breakpoints. Use sm:, md:, lg: prefixes with animate-none to progressively disable animations as the viewport grows, keeping the experience lean on desktop.

<!-- Animate on mobile, static on desktop -->
<div class="animate-pulse md:animate-none bg-gray-200 rounded h-12 w-full">
  Pulses on mobile, static on md+ screens
</div>

<!-- Spinner only visible on small screens -->
<svg class="animate-spin sm:hidden h-5 w-5 text-blue-600" ...></svg>

<!-- Use different animations at different breakpoints -->
<div class="animate-bounce md:animate-ping lg:animate-spin h-4 w-4 rounded-full bg-green-500">
  Different animation per breakpoint
</div>

Real-World Loading Pattern

A production-quality loading experience combines multiple animation utilities. The main content area shows a skeleton with animate-pulse on placeholder blocks. A status indicator uses animate-ping. If there is a visible spinner in a button or header, animate-spin is applied to the spinner SVG. All animations should stop once the content loads, replaced by the real content without any jarring jump.

<!-- Full loading state with multiple animations -->
<div class="border border-gray-200 rounded-xl p-6">
  <!-- Status indicator -->
  <div class="flex items-center gap-2 mb-4">
    <div class="relative">
      <span class="animate-ping absolute h-3 w-3 rounded-full bg-blue-400 opacity-75"></span>
      <span class="h-3 w-3 rounded-full bg-blue-600 block relative"></span>
    </div>
    <span class="text-sm text-gray-500">Fetching data...</span>
    <svg class="animate-spin h-4 w-4 text-blue-500 ml-auto" fill="none" viewBox="0 0 24 24">
      <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
    </svg>
  </div>
  <!-- Skeleton content -->
  <div class="space-y-3">
    <div class="animate-pulse h-4 bg-gray-200 rounded w-3/4"></div>
    <div class="animate-pulse h-4 bg-gray-200 rounded w-1/2"></div>
    <div class="animate-pulse h-4 bg-gray-200 rounded w-5/6"></div>
  </div>
</div>

Quick Check

Test your understanding of Tailwind's built-in keyframe animations.

Lesson Recap

In this lesson you learned: animate-spin creates loading spinners, animate-ping creates notification ripples, animate-pulse creates skeleton loaders, and animate-bounce creates vertical bouncing indicators. Use motion-reduce:animate-none to disable animations for users who prefer reduced motion. Next up we create custom keyframe animations via the Tailwind config.

Frequently asked questions

Is the “Built-In Keyframe Animations” lesson free?

Yes — the full text of “Built-In Keyframe Animations” is free to read here on the web, and the Tailwind CSS Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Tailwind CSS Academy course, upgrade to CoddyKit PRO.

What will I learn in “Built-In Keyframe Animations”?

Apply Tailwind's built-in animate-spin, animate-ping, animate-pulse, and animate-bounce for common loading and attention-grabbing patterns. You practise Tailwind CSS Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Tailwind CSS Academy?

No prior experience is required. Tailwind CSS Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Built-In Keyframe Animations” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Tailwind CSS Academy lesson?

Yes. Every Tailwind CSS Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Transition Utilities
  2. Duration and Easing
  3. Built-In Keyframe Animations
  4. Custom Animations in Config
← Back to Tailwind CSS Academy