Reduced motion
Underprefers-reduced-motion: reduce, every TailMotion animation and
transition collapses to 1ms rather than being removed.
That choice matters. animation: none would leave tm-fade-out fully visible —
the class would stop communicating “this is leaving” and the element would just
sit there. Collapsing to 1ms means the state a class communicates still lands; it
simply lands instantly.
Coverage includes pseudo-elements and the unclassed children of container
recipes:
::backdrop is handled in its own rule, because one unparsed part of a selector
list discards the whole list in an older engine.
The two exceptions
Opting out entirely
For motion that carries no information at all, use Tailwind’smotion-safe::
motion-reduce: to hide something that only makes sense in motion:
Testing it
/capabilities/ also has a simulated toggle, which mirrors the real
media query including both exceptions above.
Right-to-left
Logical classes read--tm-inline-flip, which TailMotion sets to -1 under
[dir="rtl"], so inline-axis motion mirrors itself with no extra markup:
The two fills use
[dir="rtl"] plus :dir(rtl) behind an @supports guard,
because transform-origin accepts no logical keywords.
The physical aliases
tm-slide-left and tm-slide-right mirror too, since they
point at the same keyframes — which is exactly why their names are misleading in
an RTL context, and why the logical names are worth preferring in new markup.dir="auto" is supported via :dir() where the browser has it.
Motion is never the only signal
Every control in this library’s own demo pairs its motion with a color, an icon or a text change. Do the same:focus-visible:variants must be paired with a visible focus ring. A glow that only exists in motion disappears under reduced motion, and never existed for a user who cannot perceive it.- A toast needs
role="status". The animation is not an announcement. - A dialog needs
role="dialog"and a label, or use<dialog>and let the browser do it. - A tooltip needs
role="tooltip"andaria-describedbyon its trigger. tm-hold-confirmneeds a key handler. Withoutdata-state="holding"on keydown the interaction is pointer-only.
What presence does for you
The closed state of every presence class and panel recipe ends onvisibility: hidden, not just opacity: 0. A fully transparent element still
swallows clicks and is still read aloud; a hidden one is neither.
visibility interpolates discretely at the end of a transition, so the element
stays visible for the whole exit and only leaves the accessibility tree once the
animation is finished.
What choreography does not break
tm-stagger-from-end reverses the sequence, not the DOM. TailMotion never sets
flex-direction: row-reverse, order, or a negative delay — three tricks that
buy a visual effect by breaking keyboard navigation or screen-reader order.
Reading order, tab order and the accessibility tree are always exactly what your
markup says.
Native elements keep their semantics
[popover], <dialog> and <details> keep focus management, the top layer,
light dismiss, Escape and their native roles. TailMotion animates them and
intercepts nothing. That is the main reason to prefer
tm-native-* over a div-based equivalent.