What a keyframe collection cannot do
Fading something in is the easy part. The hard part is everything around it: making a whole surface feel calmer without editing 200 class names, animating thedata-state your headless UI library already emits, giving <dialog> and
[popover] an exit, and shipping a scroll reveal that fails safely in a browser
that has never heard of view timelines.
Scoped motion personalities
One class on an ancestor retunes the feel of everything below it. The
animation classes never change.
State-driven presence
Animates the
data-state and aria-expanded your application already sets.
Interruptible by construction.Native HTML motion
Real entrances and exits for
[popover], <dialog> and <details>, with
the browser keeping focus and semantics.Product recipes
Menu, dialog, toast, tooltip, accordion, tabs, loading-to-success and
hold-to-confirm — as motion only.
Scroll-driven motion
CSS view timelines. No observer, no listener, and content stays visible where
the browser has none.
Choreography
Stagger that can enter, exit and reverse — without reordering the DOM.
The shape of the library
Seven categories, each with one job.
Interaction and presence are built from transitions because only they need to
survive a change of mind mid-flight — a replayed keyframe would snap back to its
start. Personality and choreography add no rules of their own: they are
inherited custom properties every other category already reads.
See the motion model for how the four token
groups fit together, and which one do I want?
for picking between them.
What is guaranteed
Every one of these is verified by
npm run check, which fails the build if a
class points at missing keyframes, a rule hides content outside an @supports
guard, :root picks up a timing token, or a module ships without a
reduced-motion reset.- Zero runtime in the CSS core. Importing the stylesheet adds no JavaScript.
The optional helpers in
tailmotion/utilsare opt-in and no class requires them. - 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, so what a class communicates still lands — it just lands instantly. Two classes are deliberate exceptions, for good reasons.
- Direction is logical. Inline-axis motion mirrors in right-to-left contexts. Block-axis motion does not, because up is up in every writing direction.
- Tailwind v3, Tailwind v4, and no Tailwind at all are all supported. The prebuilt stylesheet needs no build step and no plugin.
Non-goals
TailMotion will not grow into these, and it is worth saying so plainly.- No JavaScript animation runtime. If a behaviour needs one, it belongs in your application, not here.
- No styled components for React, Vue or Svelte. One CSS API, four identical integrations.
- No colors, sizes, spacing or radius in a motion class. That is Tailwind’s job, and mixing the two is how motion libraries become design systems nobody asked for.
- No React Native. TailMotion is CSS and DOM selectors. See Support.
Where to go next
Quickstart
Install, import, and ship the first four behaviours.
Class reference
Every class, its trigger, its states, its timing and its required markup.