Purposeful CSS motion that speaks Tailwind.
Add tuned keyframes and interruptible transitions with composable classes and no runtime in the core stylesheet.
Documentation · Quickstart · Class reference · Live demo · Animation explorer · Feedback · Contributing
npm install tailmotion@import "tailmotion/css";<button class="tm-press rounded-lg bg-blue-600 px-4 py-2 text-white">
Save changes
</button>TailMotion gives product interfaces a tested motion language with good decisions already made. Tailwind owns layout, color, spacing, radius, shadows and typography; TailMotion owns how an element enters, exits, responds, repeats and reacts to state.
A keyframe collection answers "how do I fade this in?" TailMotion answers "how should this whole product move, and what happens when the state changes?"
📖 Full documentation → · 🎛️ Live capability demo →
<main class="tm-motion-calm">
<button class="tm-press">Save</button>
<div class="tm-slide-block-start">Saved</div>
</main>tm-motion-calm, tm-motion-productive and tm-motion-expressive retune every
TailMotion descendant. Duration is a factor, not a value, so each animation
keeps its own relative character — tm-pop stays livelier than tm-fade-in in
all three. Element-level utilities like tm-duration-300 still win.
<div class="tm-presence-slide-block" data-state="open">Product menu</div>Reads data-state, aria-expanded, aria-pressed, aria-checked or
.tm-open / .tm-closed. Built from transitions, so reversing mid-flight
retargets from the current position instead of restarting. Radix UI, Base UI,
Ark and Melt work with no adapter — the class is the integration.
<dialog class="tm-native-dialog">…</dialog>
<div popover class="tm-native-popover">…</div>
<details class="tm-native-disclosure">…</details>The browser keeps focus management, the top layer, light dismiss, Escape and every native semantic. TailMotion adds the movement, picks no color or size, and adds no JavaScript.
<div role="status" data-state="open" class="tm-toast">Changes saved</div>
<button data-state="loading" class="tm-feedback-button">
<span class="tm-feedback-idle">Save</span>
<span class="tm-feedback-loading">Saving</span>
<span class="tm-feedback-success">Saved</span>
</button>Menu, dialog, toast, tooltip, accordion, tabs, loading-to-success and hold-to-confirm. State selectors, timing, easing and transform origin — no colors, no dimensions, no components.
<section class="tm-scroll-reveal">Content</section>CSS view timelines, driven by the browser on the compositor. Entirely inside
@supports (animation-timeline: view()), so a browser without support renders
ordinary, fully visible content — a fallback an IntersectionObserver cannot
offer.
<ul data-state="open" class="tm-stagger tm-stagger-75 tm-stagger-from-end">
<li>Profile</li><li>Settings</li><li>Sign out</li>
</ul>Enter in document order, exit last-item-first, replay from a state change — with nothing reordered in the DOM, so reading order, tab order and the accessibility tree are untouched.
Every one of these is verified by npm run check, which fails the build if it
stops being true.
- Zero runtime in the CSS core. No JavaScript, no observer, no frame loop.
- Nothing modern can hide content. A hidden base state is only ever declared
inside the
@supportsblock that also says how it comes back. - Reduced motion preserves the state. Animations collapse to 1ms rather than being removed, with two deliberate, documented exceptions.
- Direction is logical. Inline-axis motion mirrors in RTL; block-axis motion does not.
- Transform-safe. 32 keyframes — every entrance, exit, presence and scroll
class — animate
translate/scale/rotate, so a Tailwindrotate-3on the same element survives. - Tailwind v3, Tailwind v4, or no Tailwind at all.
- Usage-generated CSS for the simple catalogue (experimental). Tailwind v3
(via the plugin) and Tailwind v4 (via
tailmotion/tailwind.css) can emit only the fade/pop/bounce-style utilities and interactions your markup actually uses, instead of the whole catalogue. See Installation.
| Quickstart | Install and ship the first four behaviours |
| Installation | Tailwind v3, v4, the CDN, the optional plugin |
| The motion model | Four token groups and one override order |
| Class reference | Every class, trigger, state, duration and contract |
| CSS variables | Every --tm-* property and its default |
| Browser support | Every guarded feature and its exact fallback |
| Accessibility | Reduced motion, RTL, focus, and what motion must never be alone |
| Framework integration | React, Vue, Svelte, HTML, headless UI |
| Migration | Upgrading to 0.8 |
| Support and non-goals | What is supported, and what will never be built |
The docs/ folder in this repository is the source for all of the above.
Motion personalities
| Profile | Use for | Duration | Travel | Overshoot |
|---|---|---|---|---|
tm-motion-calm |
Settings, finance, long-form reading | ×1.1 | 8px | none |
tm-motion-productive |
The recommended default for product UI | ×0.85 | 10px | ×0.6 |
tm-motion-expressive |
Onboarding, marketing, celebration | ×1.2 | 22px | ×1.3 |
Plus tm-speed-*, tm-emphasis-* and tm-no-overshoot for one axis at a time.
Presence and recipes
tm-presence-fade · tm-presence-scale · tm-presence-pop ·
tm-presence-slide-block · tm-presence-slide-inline
tm-menu · tm-dialog · tm-dialog-backdrop · tm-toast · tm-tooltip ·
tm-accordion-panel · tm-tab-panel · tm-tab-indicator ·
tm-feedback-button · tm-hold-confirm
tm-native-popover · tm-native-dialog · tm-native-disclosure
Entrances, exits and interactions
Entrances tm-fade-in · tm-scale-in · tm-slide-block-start ·
tm-slide-block-end · tm-slide-inline-start · tm-slide-inline-end ·
tm-blur-in · tm-pop · tm-drop · tm-zoom-in · tm-zoom-out ·
tm-rotate-in · tm-elastic · tm-reveal · tm-unfold · tm-glide ·
tm-scale-fade · tm-rise · tm-flip-x · tm-flip-y · tm-swing-in
Exits tm-fade-out · tm-scale-out · tm-slide-block-out ·
tm-slide-inline-out · tm-blur-out
Interactions tm-press · tm-hover-lift · tm-hover-scale ·
tm-rotate-hover · tm-rotate-press · tm-icon-swap
Continuous tm-spin · tm-pulse · tm-bounce · tm-float · tm-drift ·
tm-sway · tm-glow · tm-morph · tm-ripple · tm-shimmer · tm-sparkle
Timing tm-duration-* · tm-delay-* · tm-ease-* · tm-repeat-* ·
tm-distance-* · tm-stagger-*
Full details, including every duration and easing role, in the class reference.
Modular imports
@import "tailmotion/css"; /* everything — 31.6 KB gzipped */
@import "tailmotion/profiles.css"; /* 3.7 KB */
@import "tailmotion/presence.css"; /* 4.7 KB */
@import "tailmotion/native.css"; /* 5.0 KB */
@import "tailmotion/recipes.css"; /* 6.4 KB */
@import "tailmotion/scroll.css"; /* 4.1 KB */
@import "tailmotion/choreography.css"; /* 4.8 KB */
@import "tailmotion/animations/base.css"; /* or one family at a time */
@import "tailmotion/animations/fade.css";The full bundle is not tree-shaken, and each module repeats the shared token
layer — so if you need more than one, import tailmotion/css instead.
Not supported. TailMotion is CSS and DOM selectors. React Native Web can use it for its browser target only, where the output is real DOM and real CSS.
- Read CONTRIBUTING.md before opening a substantial change.
- Participation is governed by the TailMotion Code of Conduct.
- Report bugs and propose features through GitHub Issues.