Skip to main content
Most of TailMotion works on any element. These do not: each one animates children, so it needs the markup described here.
Treat these as optional recipes. TailMotion provides the movement; your Tailwind classes provide dimensions, colors and typography. Two of them — tm-liquid-btn and tm-hold-delete — pre-date that rule and choose their own appearance, which is noted where it applies.

View morph

One surface that reshapes between several mounted views — the Dynamic Island shape.
Child contract: every view stays mounted and carries data-tm-panel. Move data-tm-active when your component state changes, and keep aria-hidden in step with it. Supply the active view’s dimensions through --tm-view-width and --tm-view-height. Setting a fixed --tm-view-stage-width and --tm-view-stage-height makes the reshape animate a clip-path: inset() instead of layout, which is dramatically cheaper on phones. Tune the outgoing view with --tm-view-exit-scale, --tm-view-exit-scale-x and --tm-view-exit-y. The full variable list is in CSS variables.
The live blur on the outgoing panel is skipped on touch-first devices (@media (hover: none) and (pointer: coarse)), because Safari rasterizes it even for hidden panels. The spatial cross-fade is unchanged.

Avatar group

Variables: --tm-avatar-overlap, --tm-avatar-gap, --tm-avatar-lift, --tm-avatar-spring, --tm-ring-width.

Count reveal

Slot-machine style number reveal. Each character is its own child, staggered.
Child contract: one element per character. Indices are generated for the first 10; --tm-count-stagger-step sets the delay between them. To build the spans from a string, tailmotion/utils has a helper:
That is data, not DOM — render it with your framework. For vanilla JS, initCountRevealElement(element) does the DOM work.

Text flip

Rotating words with a blur transition. This one genuinely needs JavaScript, because CSS cannot hold a list of strings.
For React, Vue or Svelte, use createTextRotator instead — it is a headless state machine with no DOM access, so your framework keeps ownership of rendering:

Liquid button

tm-liquid-btn chooses its own fill color and, in some variants, its own line height. It pre-dates the motion-only rule and is kept for compatibility.
Direction variants: tm-liquid-btn-left, -right, -top, -center. Speed: tm-liquid-fast, tm-liquid-slow. Color presets: tm-liquid-blue, -purple, -emerald, -amber, -rose, -cyan. Using a preset is opt-in. tm-liquid-underline and tm-liquid-wave are the non-button variants.

Icon swap

Cross-fade between two glyphs, driven by real component state.
Child contract: exactly two element children. Both stay in the DOM, so the outgoing icon animates out as cleanly as the incoming one animates in. The swapped state is read from aria-pressed, aria-expanded, aria-checked, [data-tm-swap="on"] or .tm-swapped — never a parallel flag. Grid stacking is structural, not styling: the two children share one cell so neither affects layout.

Hold to delete

tm-hold-delete chooses its own colors, padding and radius. It is kept working and unchanged, and superseded by tm-hold-confirm, which owns no appearance at all.
Variables: --tm-hold-duration, --tm-hold-color, --tm-hold-bg, --tm-hold-success, --tm-hold-success-bg, --tm-hold-fg.