TailMotion is a stylesheet. Everything below is about getting that stylesheet
into your project in the right order.
The one rule
Import tailmotion/css after Tailwind. TailMotion’s rules live in a native
@layer utilities, and it expects Tailwind’s output to already be in the
document.
Standalone CSS
No Tailwind, no build step, no plugin. Every class in the library works.
Pin the CDN URL to the release you tested. In an application with a bundler,
prefer the CSS or JavaScript import — you get the same file with a hash your
deploy controls.
Tailwind CSS v4
Tailwind v4 emits its own utilities into a native @layer utilities, the same
layer TailMotion uses, so source order decides conflicts. Importing TailMotion
last means a motion class wins over a Tailwind utility that touches the same
property.
Tailwind CSS v3
Then import the stylesheet after Tailwind’s directives:
Tailwind v3 emits preflight and its utilities unlayered, and unlayered CSS
beats every layered rule regardless of specificity. TailMotion’s rules live in
@layer utilities, so anything preflight sets wins.In practice this only bites the two legacy classes that own their own
appearance. On a <button>, preflight’s
button { color: inherit; padding: 0 } and
button { background-color: transparent } strip tm-hold-delete and
tm-liquid-btn of their colour, background and padding — the animation still
runs, so the result looks broken rather than missing:Give those two classes their appearance with Tailwind instead, which is what the
rest of the library expects anyway:Or use tm-hold-confirm, which sets no
colour, background or padding at all and is immune by design.Every other class in the library only animates transform, translate,
scale, opacity and filter, none of which preflight touches. The one other
case worth knowing is a Tailwind utility fighting a TailMotion class over the
same property — opacity-0 on a tm-presence-* panel — where the Tailwind
utility wins in v3 and TailMotion wins in v4. Let TailMotion own opacity,
translate, scale and visibility on an element it is animating and the
question never comes up.
Usage-generated CSS (experimental)
For the “simple” catalogue — one class, one matching keyframe: fade, pop,
bounce, pulse, spin, float, drift, shake, wiggle, glow, morph, sway, ripple,
elastic, blur, rotate-in, the slide/drop/scale/zoom entrances and exits, and
the plain-transition interactions (tm-press, tm-hover-lift,
tm-hover-scale, tm-rotate-hover, tm-rotate-press) — both Tailwind majors
can generate only the utilities and recipes your markup actually uses,
instead of shipping the whole catalogue. Keyframes and the token/reduced-motion
base layer still ship unconditionally either way: neither Tailwind major
prunes a @keyframes block for a custom-named utility, and the base layer
(color/easing tokens, the prefers-reduced-motion collapse) is a real
dependency regardless of which classes are used. The fixed cost of both is
small — well under 5KB gzip for the full Phase 1 catalogue.
Both paths also include the fixed-value token modifiers (tm-duration-*,
tm-delay-*, tm-ease-*, tm-repeat-*, tm-stagger-*, tm-speed-*,
tm-emphasis-*, tm-overshoot-*, tm-hold-*, tm-distance-*) at their
shipped values, so tailmotion/tailwind.css works standalone in Tailwind v4
with no JS plugin required. Arbitrary values (tm-duration-[420ms]) still
need the plugin’s matchUtilities, which resolves them dynamically — pair
@plugin "tailmotion/plugin"; alongside tailmotion/tailwind.css for those;
it prunes correctly too, since matchUtilities-registered utilities (unlike
addUtilities/addBase) are tree-shaken by Tailwind v4’s JS-plugin
compatibility layer.
Use this instead of tailmotion/css, not alongside it. Both paths define
the same classes; importing both ships everything twice.
For Tailwind v3, add the usual @tailwind directives (no tailmotion/css
import needed). For Tailwind v4, tailmotion/tailwind.css is a real,
natively tree-shaken @utility entry — it is not the same as loading
tailmotion/plugin through @plugin. Tailwind v4’s compatibility layer for
legacy JS plugins does not tree-shake addBase/addUtilities content by
usage at all, so usageGenerated: true only prunes correctly under Tailwind
v3’s JIT; under v4 it would ship the whole Phase 1 catalogue unconditionally
with no pruning benefit. Use tailmotion/tailwind.css for v4 instead.
This currently covers the catalogue described above. Recipes with child or
state selectors (tm-icon-swap, presence, native, scroll, choreography,
profiles, and the larger animation files) are not part of it yet and still
need the complete or modular stylesheets.
The Tailwind plugin is optional
The prebuilt stylesheet already contains every timing, easing, distance and
stagger token at its shipped value. Add the plugin only when you want to
change or extend those tokens, or use arbitrary values like
tm-duration-[420ms].
Nothing in tailmotion.css requires the plugin, and nothing breaks without it.
See the plugin reference for every utility it
generates and every option it accepts.
Module entries
The convenient entry is the whole library and is not tree-shaken. Each
focused entry below is a real bundle boundary — you can take presence or scroll
without the rest — and carries the shared token layer it needs, so it works on
its own.
Sizes and the trade-off are in Imports and bundle size.
Per-animation files
For the smallest possible stylesheet, import the base plus only the animations
you use:
Keep base.css first: it pulls in the token layer and the motion profiles, and
provides RTL mirroring and reduced-motion behaviour. Per-animation imports do
not include the prebuilt hover: / focus: / responsive selectors from
variants.css.
TypeScript
Types ship with the package. The ones worth knowing about:
TailMotionVars is the useful one — it makes an inline style object
autocomplete every variable TailMotion reads:
Verifying an install
Inside this repository that builds the stylesheets and runs the static checks.
In your own project the equivalent smoke test is one element:
If it appears without moving, the stylesheet did not load, or
prefers-reduced-motion: reduce is on — which is the correct behaviour, not a
bug.