Skip to content

Repository files navigation

TailMotion

TailMotion

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 version npm downloads MIT license

TailMotion — Motion that speaks Tailwind

Add motion without adding a runtime

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 →

What it does that a keyframe collection cannot

One class changes the motion personality of a whole interface

<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.

Motion personalities

Your existing data-state animates, with no runtime

<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.

State-driven presence

Native dialog, popover and disclosure get real exits

<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.

Native elements

Product patterns as motion only

<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.

Product recipes

Scroll reveals with no observer, that fail safely

<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.

Scroll-driven motion

Stagger that can also leave

<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.

Choreography

What is guaranteed

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 @supports block 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 Tailwind rotate-3 on 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.

Documentation

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.

Quick reference

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.

Imports and bundle size

React Native

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.

Community

License

MIT © Moumen Soliman

About

Purposeful CSS motion that speaks Tailwind.

Topics

Resources

Code of conduct

Contributing

Stars

35 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages