Tailwind CSS Handbook v4.3

Transitions

Animate property changes with transition utilities — which properties to transition, durations that feel right, easing curves, and respecting reduced motion.

A transition smoothly interpolates a property when its value changes. In Tailwind, that means adding one class to the base state, not the hover state.

Overview#

Loading preview…

What to transition#

UtilityTransitions
transitionColours, opacity, shadow, transform, filter, backdrop-filter
transition-allEvery animatable property
transition-colorsColour properties only
transition-opacityOpacity
transition-shadowBox shadow
transition-transformTransform
transition-noneDisable
transition-[width,height]An explicit list

Duration#

UtilityTimeUse for
duration-7575msMicro-feedback
duration-150150msColour and opacity changes
duration-200200msButtons, links — the default sweet spot
duration-300300msDropdowns, small panels
duration-500500msModals, drawers
duration-700+700ms+Decorative only

Easing#

UtilityCurveFeels like
ease-linearConstantMechanical — for spinners and progress
ease-inSlow startExits
ease-outFast start, slow endEntrances — the most useful default
ease-in-outSlow both endsMovement between two states
Custom curves
@theme {
  --ease-fluid: cubic-bezier(0.3, 0, 0, 1);
  --ease-snappy: cubic-bezier(0.2, 0, 0, 1);
}
HTML
<div class="transition duration-300 ease-snappy">…</div>

Delay#

HTML
<div class="transition delay-150 duration-300">…</div>

Delays are most useful for staggering:

HTML
<li class="transition delay-0">…</li>
<li class="transition delay-75">…</li>
<li class="transition delay-150">…</li>

Performance#

Only two properties are cheap to animate, because the compositor can handle them without touching layout or paint:

PropertyCostNotes
transformCheapMove, scale, rotate
opacityCheapFade
color, background-colorModerateRepaints
box-shadowExpensiveRepaints a large area
filter, backdrop-filterExpensiveRe-composites every frame
width, height, top, marginVery expensiveReflows the whole document
HTML
<!-- ❌ Triggers layout on every frame -->
<div class="w-32 transition-all hover:w-40">…</div>

<!-- ✅ Compositor-only -->
<div class="transition-transform hover:scale-110">…</div>

Reduced motion#

Some people get motion sickness from animation, and the OS exposes that preference. Honour it.

HTML
<div class="transition duration-300 motion-reduce:transition-none">…</div>

For a project-wide default, disable non-essential motion in your base layer:

CSS
@layer base {
  @media (prefers-reduced-motion: reduce) {
    *,
    ::before,
    ::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }
}

Entry animations with starting#

v4 The starting variant maps to @starting-style, which lets an element transition in when it first appears — including from display: none.

HTML
<div popover id="menu"
  class="opacity-100 transition-all transition-discrete duration-200
         starting:open:opacity-0 starting:open:scale-95">
  …
</div>

transition-discrete (transition-behavior: allow-discrete) is what makes display itself animatable, so the element can fade out before it disappears.

Common patterns#

Button#

HTML
<button class="rounded-md bg-indigo-600 px-4 py-2 font-medium text-white transition duration-200 hover:bg-indigo-500 active:scale-95">
  Save
</button>

Card lift#

HTML
<a class="block rounded-xl border p-6 transition duration-200 hover:-translate-y-1 hover:shadow-lg">…</a>

Reveal on group hover#

Loading preview…

Note focus-visible:opacity-100 — without it the button is unreachable by keyboard.

Accordion height#

Animating height: auto is not possible, but grid-template-rows is:

HTML
<div class="grid grid-rows-[0fr] transition-all duration-300 data-[open=true]:grid-rows-[1fr]">
  <div class="overflow-hidden">…content…</div>
</div>

Underline that grows from the left#

Loading preview…

Scaling a full-width bar is cheaper than animating width.

Common mistakes#

  • hover:transition. Put transition on the base state.
  • transition with no duration. The default is 150ms, which is fine — but be explicit when it matters.
  • transition-all everywhere. It picks up layout properties and gets slow.
  • Animating width, height, top or margin. Use transform.
  • Ignoring prefers-reduced-motion. It is an accessibility requirement, not a nicety.
  • Hover-only reveals. Anything revealed on hover must also appear on focus, or keyboard users cannot reach it.
  • Transitioning an element that also has hidden. display is discrete; you need transition-discrete and starting:.