Skip to main content
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.