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.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
tm-avatar-more is sized and overlapped exactly like a real avatar — only
its fill, ring and stacking order differ, and it sits behind the avatar
before it so that avatar’s circle overlaps its edge instead of the badge
clipping a real member.
Variables: --tm-avatar-overlap, --tm-avatar-lift, --tm-avatar-spring,
--tm-ring-width, --tm-avatar-more-bg.
Count reveal
Slot-machine style number reveal. Each character is its own child, staggered.--tm-count-stagger-step sets the delay between them.
To build the spans from a string, tailmotion/utils has a helper:
initCountRevealElement(element) does the DOM work.
Number swap
A number whose digits pop in together, and replay whenever the value changes — a live counter, a price, a score.<span>
per character on every update() call. Fresh nodes are what replay the pop:
there is no class to toggle. Indices are also generated for the first 10, so
plain markup without the helper still staggers if you split the characters
yourself. --tm-number-stagger-step sets the delay between them.
For React, Vue or Svelte, split the value into characters yourself and key
each digit’s <span> by its slot, e.g. key={`${value}-${i}`} — remounting
on value change is what replays the animation; the class only needs
--tm-stagger on each child.
Streaming text
Words that resolve in one by one, left to right — for text that should read as arriving rather than as a swap.<span> and keeps the whitespace between them as plain text, so the
paragraph still wraps normally. --tm-stream-text-stagger-step sets the gap
between words and --tm-stream-text-blur the resolve-in blur.
Text flip
Rotating words with a blur transition. CSS only — no JS required, as long as every word already exists in the markup. Render each word as a sibling*-word span and set [data-tm-count]
to the word count; the container cycles through them forever on its own.
Every word shares one grid cell (grid-area: 1 / 1), so they stack instead
of running together with no space between them, and an infinite animation —
phased per word by :nth-child — reveals one at a time. Nothing mounts,
ticks, or gets torn down:
words.map() — no helper needed at all.
2–6 words are supported out of the box.
For a vanilla project that would rather hand over a words array (or a
data-tm-words attribute) than write the spans itself, initTextFlipElement
renders that same markup once and gets out of the way — there is no
interval running afterward, CSS owns every cycle from there:
variant: "chars" is the one case that still genuinely needs JavaScript:
each word change re-splits fresh characters rather than swapping between two
fixed states, which CSS keyframes cannot express. It keeps its own
setInterval-driven controller (start/stop/next/prev/goTo).
For React, Vue or Svelte consumers who want runtime control over a “chars”
rotation — or any case with a word list that changes after mount — use
createTextRotator instead. It’s a headless state machine with no DOM
access, so your framework keeps ownership of rendering:
Shimmer text
A highlight that follows the shape of the glyphs. The window element sweeps one way and the copy inside counter-translates by the same amount, so the glyphs stay still on screen and the whole loop is one transform.aria-hidden, so the text is announced once. Writing
<p class="tm-shimmer-text"> with no sweep child still works and still
animates: that is the original zero-markup implementation, which repaints every
frame and is scheduled for removal in 1.0. See
Render cost.
Liquid button
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.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-duration, --tm-hold-color, --tm-hold-bg,
--tm-hold-success, --tm-hold-success-bg, --tm-hold-fg.