the --animate-* namespace: theme var → animation utility
In Tailwind v4, every animation is just a CSS custom property.
The --animate-* namespace maps directly to the CSS
animation: shorthand. Add --animate-wiggle: wiggle 1s ease-in-out infinite
to @theme, define @keyframes wiggle, and the utility
animate-wiggle exists automatically. No plugin, no tailwind.config.js
theme.extend.animation function — v4 collapsed all of that into theme variables.
| piece | where it lives | what it does |
|---|---|---|
--animate-wiggle |
@theme { ... } block |
registers the utility animate-wiggle; its value becomes the animation: declaration |
@keyframes wiggle |
top-level CSS (or nested in @theme) |
defines the named keyframe timeline the animation references |
animate-wiggle |
markup class | applies animation: var(--animate-wiggle) to the element |
animate-[wiggle_1s_linear] |
arbitrary value | one-off animation without registering a theme token |
animate-(--my-anim) |
shorthand | reads any CSS var: equivalent to animate-[var(--my-anim)] |
1 · custom animations — toggle each on/off
Three animations defined in <style type="text/tailwindcss"> above:
--animate-wiggle (rotation), --animate-float (vertical bob),
--animate-gradient (background-position loop for gradient text).
Click a button to toggle the utility class — the gold-check re-runs and proves
animationName flips between the keyframe name and none.
animate-wiggle
wiggle 1s ease-in-out infinite · rotate(-3deg) ↔ rotate(3deg)
animate-float
float 3s ease-in-out infinite · translateY(0) ↔ translateY(-14px)
animate-gradient
gradient 3s ease infinite · background-position 0% ↔ 100% (needs bg-[length:200%_auto])
wiggle.animationName: —
2 · built-in animations (shipped in v4's default theme)
These five ship out of the box. Each is itself just a
--animate-* token + @keyframes — the same mechanism
your custom animations use. animate-none sets animation: none
to kill inherited/looping animations.
animate-spin
animate-ping
animate-pulse
animate-bounce
| utility | --animate-* value | typical use |
|---|---|---|
animate-spin | spin 1s linear infinite | loading spinners |
animate-ping | ping 1s cubic-bezier(0,0,.2,1) infinite | notification ripples, radar pings |
animate-pulse | pulse 2s cubic-bezier(.4,0,.6,1) infinite | skeleton loaders, opacity breathing |
animate-bounce | bounce 1s infinite | "scroll down" arrows, attention |
animate-none | none | disable animation (overrides) |
animation shorthand & intent → pattern
The --animate-* value IS the CSS animation: shorthand, so
any of these 8 sub-properties can appear in it, space-separated, in any order
(time values are parsed as duration then delay).
| shorthand slot | example value | notes |
|---|---|---|
| name | wiggle | must match an @keyframes name |
| duration | 1s / 300ms | first time value |
| timing-function | ease-in-out, linear, cubic-bezier(0,0,.2,1) | easing per the whole animation |
| delay | 0s, 200ms | second time value |
| iteration-count | infinite, 3 | how many times to run |
| direction | normal, alternate, reverse | alternate ping-pongs each iteration |
| fill-mode | none, forwards, backwards, both | forwards holds the last keyframe |
| play-state | running, paused | pause on hover: hover:[animation-play-state:paused] |
| intent | pattern |
|---|---|
| register a reusable animation | @theme { --animate-x: x 1s ...; } @keyframes x { ... } |
| one-off animation (no theme token) | class="animate-[wiggle_1s_ease-in-out_infinite]" |
| read animation from any CSS var | class="animate-(--my-anim)" |
| pause on hover | hover:[animation-play-state:paused] |
| respect reduced-motion preference | motion-safe:animate-spin / motion-reduce:animate-none |
| run N times then stop | --animate-pop: pop .3s ease 0s 2 forwards; |