DEV Community

Cover image for Mantine Text Animate v3 - The Text Animation Toolkit Gets a Major Upgrade
Giovambattista Fazioli
Giovambattista Fazioli

Posted on

Mantine Text Animate v3 - The Text Animation Toolkit Gets a Major Upgrade

Five new compound components, loop animations, viewport triggers, sound effects, and full accessibility support — all in one release.

Introduction

Version 3.0.0 of @gfazioli/mantine-text-animate is the biggest release yet. It doubles the number of compound components from five to ten, introduces powerful new animation modes like looping and viewport-triggered animations, and brings first-class accessibility with prefers-reduced-motion support across every component. Whether you need a retro airport departure board, a smooth text morphing transition, or an animated gradient heading, this release has you covered.

What's New

Five New Compound Components

TextAnimate.Gradient — Animated Gradient Text

Gradient

Apply an animated gradient effect to any text, with full support for Mantine theme colors. Choose from 8 animation directions and pass your own color palette.

<TextAnimate.Gradient colors={['blue', 'cyan', 'green']} speed={1.5} direction="to-right">
  Gradient Text
</TextAnimate.Gradient>
Enter fullscreen mode Exit fullscreen mode

The component resolves Mantine color tokens like 'blue.5' via parseThemeColor, so your gradients stay consistent with your theme.

TextAnimate.Highlight — Animated Highlighter Marker

Highlight

A CSS-only highlighter effect that sweeps a colored marker across your text. No JavaScript animation loop needed — it's all CSS keyframes.

<TextAnimate.Highlight color="yellow" highlightHeight="40%" speed={1}>
  Important Text
</TextAnimate.Highlight>
Enter fullscreen mode Exit fullscreen mode

TextAnimate.SplitFlap — Airport Departure Board

SplitFlap

Inspired by the classic Solari boards found in train stations and airports, this component flips through a character set with 3D CSS perspective transforms until each character reaches its target.

<TextAnimate.SplitFlap value="DEPARTING 14:30" speed={1.5} />
Enter fullscreen mode Exit fullscreen mode

Each character animates independently with configurable stagger timing. Customize the characterSet, flipDuration, radius, and dividerColor. The companion useSplitFlap hook gives you full programmatic control.

TextAnimate.Morphing — Fluid Text Transitions

Smoothly morph between two strings. The component uses the Longest Common Subsequence (LCS) algorithm to identify shared characters — they stay in place while new characters fade in and old ones fade out.

const [text, setText] = useState('Hello World');

<TextAnimate.Morphing value={text} speed={1} />
<Button onClick={() => setText('Hello Mantine')}>Change</Button>
Enter fullscreen mode Exit fullscreen mode

TextAnimate.RotatingText — Text Carousel

Cycle through an array of strings with animated transitions. Six built-in transition styles: slideUp, slideDown, fade, blur, blurUp, blurDown.

<Text>
  I speak{' '}
  <TextAnimate.RotatingText
    values={['English', 'French', 'Italian', 'Spanish']}
    animation="blurUp"
    interval={3000}
  />
</Text>
Enter fullscreen mode Exit fullscreen mode

The component renders inline (<span>) so it flows naturally within a sentence. The useRotatingText hook is available for headless usage.


Loop Mode

The base TextAnimate component now supports continuous looping. Set animate="loop" and the animation will cycle: animate in, pause, animate out, pause, and repeat.

<TextAnimate animate="loop" animation="blurDown" loopDelay={2000}>
  This text loops forever
</TextAnimate>
Enter fullscreen mode Exit fullscreen mode

The loopDelay prop controls the pause between phases (default: 2000ms).

Viewport-Triggered Animations

Delay animations until the element scrolls into view using IntersectionObserver:

<TextAnimate trigger="inView" triggerOptions={{ threshold: 0.5 }}>
  I animate when you can see me
</TextAnimate>
Enter fullscreen mode Exit fullscreen mode

Three trigger modes are available: "mount" (default, same as v2), "inView", and "manual" for full programmatic control.

The useTextAnimate Hook

A new hook for controlling TextAnimate from the outside:

const { animate, setAnimate, replay, isAnimating, key, onAnimationComplete } = useTextAnimate();

<TextAnimate animate={animate} key={key} onAnimationComplete={onAnimationComplete}>
  Controlled animation
</TextAnimate>

<Button onClick={replay}>Replay</Button>
<Button onClick={() => setAnimate('out')}>Animate Out</Button>
Enter fullscreen mode Exit fullscreen mode

onAnimationComplete Callback

Unlike onAnimationEnd (which fires per segment), onAnimationComplete fires once when all segments have finished animating — essential for orchestrating sequential animations.

<TextAnimate
  by="character"
  onAnimationComplete={(direction) => {
    if (direction === 'in') showNextElement();
  }}
>
  Staggered characters
</TextAnimate>
Enter fullscreen mode Exit fullscreen mode

TextTicker Scramble Mode

The existing TextTicker component gains a new "hacker terminal" mode. Set scrambleDuration to make each character cycle through random characters for a fixed duration before settling on the target.

<TextAnimate.TextTicker
  value="ACCESS GRANTED"
  scrambleDuration={800}
  staggerDelay={50}
  revealDirection="left-to-right"
/>
Enter fullscreen mode Exit fullscreen mode

Typewriter Enhancements

Three new capabilities for the Typewriter component:

Sound effects — Synthesized mechanical keyboard clicks via Web Audio API:

<TextAnimate.Typewriter value="Click clack..." withSound soundVolume={0.3} />
Enter fullscreen mode Exit fullscreen mode

Per-character callbacks — React to each character as it's typed:

<TextAnimate.Typewriter
  value="Hello"
  onCharType={(char, index) => console.log(`Typed: ${char}`)}
/>
Enter fullscreen mode Exit fullscreen mode

Custom pauses — Insert dramatic pauses at specific character positions:

<TextAnimate.Typewriter
  value="Hello, world!"
  pauseAt={{ 5: 1000, 6: 500 }}  // Pause 1s after comma, 500ms after space
/>
Enter fullscreen mode Exit fullscreen mode

NumberTicker Custom Formatting

Override the default Intl.NumberFormat with your own formatting function:

<TextAnimate.NumberTicker
  value={1234.56}
  formatValue={(v) => `$${v.toFixed(2)}`}
/>
Enter fullscreen mode Exit fullscreen mode

Breaking Changes

speed Prop Semantics Standardized

The speed prop now works as a multiplier on all components: speed={1} is normal speed, speed={2} is twice as fast. This is a breaking change for Typewriter, Gradient, and Spinner, which previously used different units.

Component v2 Default v3 Default Migration
Typewriter 0.03 (sec/char) 1 (multiplier) Use speed={1} for normal typing speed
Gradient 3 (seconds) 1 (multiplier) Use speed={1} for a 3s cycle
Spinner 10 (sec/rotation) 2 (multiplier) Use speed={2} for a 10s rotation
// Before (v2)
<TextAnimate.Typewriter value="Hello" speed={0.03} />
<TextAnimate.Gradient speed={3}>Hello</TextAnimate.Gradient>
<TextAnimate.Spinner speed={10}>Hello</TextAnimate.Spinner>

// After (v3)
<TextAnimate.Typewriter value="Hello" speed={1} />
<TextAnimate.Gradient speed={1}>Hello</TextAnimate.Gradient>
<TextAnimate.Spinner speed={2}>Hello</TextAnimate.Spinner>
Enter fullscreen mode Exit fullscreen mode

Spinner Children Type Expanded

Spinner now accepts string | ReactNode[]. If you relied on TypeScript enforcing children: string, the type is now wider. Existing string usage works identically.


Improvements

Full Accessibility Support

Every component now respects prefers-reduced-motion:

  • TextAnimate detects reduced motion in JS to properly fire onAnimationComplete and advance loop phases even when CSS animations are disabled
  • Typewriter shows the final text immediately
  • NumberTicker shows the target value instantly
  • SplitFlap displays the target text without flipping

ARIA attributes have been added throughout: aria-live="polite" on TextAnimate hidden/static states, automatic role="img" and aria-label on Spinner for string children.

Safety and Robustness

  • All speed divisions are clamped to a minimum of 0.1, preventing Infinity or NaN timeouts
  • AudioContext creation/resume is wrapped in try/catch for restricted environments
  • Loop timer accumulation is prevented by clearing previous timers
  • NumberTicker uses epsilon-based float comparison to reliably detect animation completion

Test Coverage

Expanded from 1 test to 42 tests across 5 suites, covering rendering, props, ARIA attributes, animation directions, text splitting, trigger modes, and more.


Getting Started

# Install or update
npm install @gfazioli/mantine-text-animate@3

# or with yarn
yarn add @gfazioli/mantine-text-animate@3
Enter fullscreen mode Exit fullscreen mode

All new components are accessed as compound components on TextAnimate:

import { TextAnimate } from '@gfazioli/mantine-text-animate';

// New components
<TextAnimate.Gradient>...</TextAnimate.Gradient>
<TextAnimate.Highlight>...</TextAnimate.Highlight>
<TextAnimate.SplitFlap value="..." />
<TextAnimate.Morphing value="..." />
<TextAnimate.RotatingText values={[...]} />
Enter fullscreen mode Exit fullscreen mode

Hooks are exported directly:

import {
  useTextAnimate,
  useSplitFlap,
  useMorphing,
  useRotatingText,
} from '@gfazioli/mantine-text-animate';
Enter fullscreen mode Exit fullscreen mode

Links

Top comments (0)