Mantine Text Animate

Undolog

@gfazioli/mantine-text-animate

A Mantine component that allows you to animate text with various effects. Additionally, it provides other sub components such as TextAnimate.TextTicker, TextAnimate.Typewriter, TextAnimate.NumberTicker, and TextAnimate.Spinner. You can also use three useful hooks: useTextTicker, useTypewriter, and useNumberTicker.

Installation

yarn add @gfazioli/mantine-text-animate

After installation import package styles at the root of your application:

import '@gfazioli/mantine-text-animate/styles.css';

You can import styles within a layer @layer mantine-text-animate by importing @gfazioli/mantine-text-animate/styles.layer.css file.

import '@gfazioli/mantine-text-animate/styles.layer.css';

Usage

The TextAnimate component allows you to animate text with various effects. The animate prop controls the initial display and the direction of the animation. In the example below, try selecting none to display nothing. With static, the text will appear static. Selecting Animate In will display the entering animation, and with Animate Out, the exiting animation will be shown.

Mantine TextAnimate component
Duration
Delay
Segment delay
import { TextAnimate } from '@gfazioli/mantine-text-animate';

function Demo() {
  return (
    <TextAnimate animate="in" animation="slideUp" by="character">
      Mantine TextAnimate component
    </TextAnimate>
  );
}

animateProps

The animateProps prop allows you to pass additional props to the animation.

interface AnimateProps {
  /**
   * Controls the distance for slide animations (in pixels)
   * @default 20
   */
  translateDistance?: MantineSize;

  /**
   * Controls the scale factor for scale animations
   * For scaleUp: initial scale = 1 - scaleAmount (e.g., 0.8 means start at 0.2)
   * For scaleDown: initial scale = 1 + scaleAmount (e.g., 0.8 means start at 1.8)
   * @default 0.8
   */
  scaleAmount?: number;

  /**
   * Controls the blur amount for blur animations (in pixels)
   * @default 10
   */
  blurAmount?: MantineSize;
}
Mantine TextAnimate component
import { TextAnimate } from '@gfazioli/mantine-text-animate';
import { Stack, Switch } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';

function Demo() {
  const [animated, { open, close, toggle }] = useDisclosure();

  return (
    <Stack>
      <Switch size="xl" checked={animated} onLabel="ON" offLabel="OFF" onChange={toggle} />
      <TextAnimate
        animate={animated ? 'in' : 'static'}
        by="character"
        animation="scale"
        animateProps={{
          scaleAmount: 34,
        }}
      >
        Mantine TextAnimate component
      </TextAnimate>
    </Stack>
  );
}

by

The by prop allows you to animate text by the entire text, character by character, per word or line by line. By using the by="line" prop, you can animate text line by line.

import { TextAnimate } from '@gfazioli/mantine-text-animate';
import { Stack, Switch } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';

function Demo() {
  const [animated, { open, close, toggle }] = useDisclosure();

  return (
    <Stack h={150}>
      <Switch size="xl" checked={animated} onLabel="ON" offLabel="OFF" onChange={toggle} />
      <TextAnimate
        animate={animated ? 'in' : 'none'}
        by="line"
        animation="scale"
        duration={1}
        segmentDelay={0.5}
        animateProps={{
          scaleAmount: 2,
        }}
      >
        {`
          Mantine TextAnimate component\n
          Can be used for multiline text\n
          This is the third line\n
          That's all!
          `}
      </TextAnimate>
    </Stack>
  );
}

Styling

Of course, you can use the component with your own styles.

import { TextAnimate } from '@gfazioli/mantine-text-animate';
import { MantineSize, Stack, Switch } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';

function Demo() {
  const [animated, { open, close, toggle }] = useDisclosure();

  return (
    <Stack>
      <Switch size="xl" checked={animated} onLabel="ON" offLabel="OFF" onChange={toggle} />
      <TextAnimate
        fz={48}
        fw={600}
        color="violet"
        animate={animated ? 'in' : 'none'}
        by="character"
        animation="blur"
        duration={0.5}
        animateProps={{
          blurAmount: '20px' as MantineSize,
        }}
      >
        Mantine TextAnimate component
      </TextAnimate>
    </Stack>
  );
}

onAnimationStart and onAnimationEnd

You can use the onAnimationStart and onAnimationEnd props to handle animation events.

Mantine TextAnimate component
import { useState } from 'react';
import { TextAnimate } from '@gfazioli/mantine-text-animate';
import { Badge, Group, MantineSize, Stack, Switch } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';

function Demo() {
  const [animated, { open, close, toggle }] = useDisclosure();

  return (
    <Stack>
      <Group>
        <Switch size="xl" checked={started} onLabel="OUT" offLabel="IN" onChange={toggle} />
        <Badge color="lime" size="xl">
          {event}
        </Badge>
      </Group>
      <TextAnimate
        fz={48}
        fw={600}
        color="violet"
        animate={started ? 'in' : 'out'}
        by="character"
        animation="slideRight"
        onAnimationStart={(animate) => setEvent(`Start ${animate}`)}
        onAnimationEnd={(animate) => setEvent(`End ${animate}`)}
        duration={0.5}
        animateProps={{
          translateDistance: '820px' as MantineSize,
        }}
      >
        Mantine TextAnimate component
      </TextAnimate>
    </Stack>
  );
}

TextAnimate.Typewriter component

The TextAnimate.Typewriter component allows you to animate text with a typewriter effect.

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

function Demo() {
  return (
    <TextAnimate.Typewriter value="Hello, World! Say Hello to Mantine Typewriter component" loop={false} />
  );
}

multiline

By using the multiline prop, you can animate text line by line.

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

function Demo() {
  return (
    <TextAnimate.Typewriter
      multiline
      value={[
        'Hello, World! Mantine Typewriter component',
        'That was a long time ago',
        'But it was fun',
      ]}
    />
  );
}

leftSection

You may use the leftSection prop to display a section before the text.

>

|
👉|
import { TextAnimate, type TypewriterProps } from '@gfazioli/mantine-text-animate';
import { Center, Stack, Text } from '@mantine/core';

function Demo() {
  return (
    <Center miw={400} h={150}>
      <Stack w="100%">
        <TextAnimate.Typewriter
          multiline
          speed={0.05}
          leftSection={
            <Text c="red" mr={4}>
              &gt;{' '}
            </Text>
          }
          value={[
            'Hello, World! Mantine Typewriter component',
            'That was a long time ago',
            'But it was fun',
          ]}
        />

        <TextAnimate.Typewriter multiline leftSection={'👉'} value={['Another left section']} />
      </Stack>
    </Center>
  );
}

Styling

Of course, you can use the component with your own styles.

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

function Demo() {
  return (
    <TextAnimate.Typewriter
      multiline
      fz={24}
      c="red"
      ff="monospace"
      value={[
        'Hello, World! Mantine Typewriter component',
        'That was a long time ago',
        'But it was fun',
      ]}
    />
  );
}

onTypeEnd and onTypeLoop

You can use the onTypeEnd and onTypeLoop props to handle typewriter events.

onTypeEnd: 0
onTypeLoop: 0

|
import { useState } from 'react';
import { TextAnimate, type TypewriterProps } from '@gfazioli/mantine-text-animate';
import { Badge, Center, Stack } from '@mantine/core'

function Demo() {

  const [isTypeEnd, setIsTypeEnd] = useState(0);
  const [isTypeLoop, setIsTypeLoop] = useState(0);

  return (
    <Stack>
      <Badge color="red">onTypeEnd: {isTypeEnd}</Badge>
      <Badge color="lime">onTypeLoop: {isTypeLoop}</Badge>
      <TextAnimate.Typewriter
        onTypeEnd={() => {
          setIsTypeEnd((prev) => prev + 1);
        }}
        onTypeLoop={() => {
          setIsTypeLoop((prev) => prev + 1);
        }}
        value={[
          'Hello, World! Mantine Typewriter component',
          'That was a long time ago',
          'But it was fun',
        ]}
      />
    </Stack>
  );
}

useTypewriter() hook

The TypeWriter is build by using the useTypewriter hook.

Done

import { useTypewriter, type TypewriterProps } from '@gfazioli/mantine-text-animate';
import { Badge, Button, Center, Divider, Group, Stack, Text } from '@mantine/c

function Demo() {
    const { text, start, stop, reset, isTyping } = useTypewriter({
    value: ['Hello', 'From', 'Mantine useTypewriter() hook'],
  });

  return (
    <Stack>
      <Text>{isTyping ? 'Typing...' : 'Done'}</Text>
      <Group>
        <Button size="xs" onClick={start}>
          Start
        </Button>
        <Button size="xs" onClick={stop}>
          Stop
        </Button>
        <Button size="xs" onClick={reset}>
          Reset
        </Button>
      </Group>
      <Divider />
      <Badge color="violet" size="xl">{text}</Badge>
    </Stack>
  );
}

The interface of the useTypewriter hook is the following:

export interface TypewriterBaseProps {
  /**
   * The text or array of texts to display in typewriter effect
   */
  value: string | string[];

  /**
   * Controls if the animation is running (true) or reset (false)
   * @default true
   */
  animate?: boolean;

  /**
   * Whether to display text in multiple lines
   * @default false
   */
  multiline?: boolean;

  /**
   * The typing speed in seconds per character
   * @default 0.03
   */
  speed?: number;

  /**
   * The delay between text changes in milliseconds (when using multiple texts)
   * @default 2000
   */
  delay?: number;

  /**
   * Whether to loop through the texts
   * @default true
   */
  loop?: boolean;

  /**
   * Callback function to be called when the typing animation ends
   */
  onTypeEnd?: () => void;

  /**
   * Callback function to be called when the typing animation is looped
   * and the animation is about to start again
   */
  onTypeLoop?: () => void;
}

And the return value of the useTypewriter hook is the following:

export interface UseTypewriterResult {
  /**
   * The current text being displayed
   * If withMultiLine is true, this will be an array of strings
   */
  text: string | string[];

  /**
   * Whether the typewriter is currently typing
   */
  isTyping: boolean;

  /**
   * Start the typewriter animation
   */
  start: () => void;

  /**
   * Stop the typewriter animation
   */
  stop: () => void;

  /**
   * Reset the typewriter to its initial state
   */
  reset: () => void;
}

multiline with useTypewriter() hook

You may use the multiline prop with the useTypewriter hook. In this case the text will be an array of strings.

import { useTypewriter, type TypewriterProps } from '@gfazioli/mantine-text-animate';
import { Badge, Center, Group, Stack } from '@mantine/core';

function Demo() {
  const { text } = useTypewriter({
    value: ['Hello', 'From', 'Mantine useTypewriter()'],
    multiline: true,
  });

  return (
    <Center h={200}>
      <Stack w="100%">
        <Stack>
          {(text as string[]).map((line) => (
            <Badge size="xl" key={line}>
              {line}
            </Badge>
          ))}
        </Stack>
        <Group>
          {(text as string[]).map((line) => (
            <Badge size="md" color="lime" key={line}>
              {line}
            </Badge>
          ))}
        </Group>
      </Stack>
    </Center>
  );
}

Example: Terminal

Below a funny example of a terminal.

import { TextAnimate, type TypewriterProps } from '@gfazioli/mantine-text-animate';
import { Center } from '@mantine/core';

function Demo() {
  return (
      <TextAnimate.Typewriter
        multiline
        c="green"
        ff="monospace"
        cursorChar="█"
        withBlink={true}
        speed={0.02}
        delay={200}
        value={[
          '$ cd /home/user/projects',
          '$ ls -la',
          'total 32',
          'drwxr-xr-x  5 user user 4096 Mar 10 14:30 .',
          'drwxr-xr-x 18 user user 4096 Mar 10 14:28 ..',
          'drwxr-xr-x  8 user user 4096 Mar 10 14:30 .git',
          '-rw-r--r--  1 user user  948 Mar 10 14:30 README.md',
          'drwxr-xr-x  2 user user 4096 Mar 10 14:30 src',
          '$ npm run build',
          '> project@1.0.0 build',
          '> webpack --mode production',
          '✓ Compiled successfully in 2.36s',
        ]}
      />
  );
}

TextAnimate.Spinner component

The TextAnimate.Spinner component allows you to animate text with a spinner effect.

Direction
Speed
Radius
Char offset
Repeat count
import { TextAnimate, type SpinnerProps } from '@gfazioli/mantine-text-animate';
import { Center } from '@mantine/core'

function Demo() {
  return (
    <TextAnimate.Spinner>★ SPINNING TEXT EXAMPLE ★</TextAnimate.Spinner>
  );
}

repeatText and repeatCount

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

function Demo() {
  return (
    <TextAnimate.Spinner repeatText={true} repeatCount={3}>
      Hello *
    </TextAnimate.Spinner>
  );
}

TextAnimate.NumberTicker component

The TextAnimate.NumberTicker component allows you to animate text with a number ticker effect.

0

Value
Start value
Delay
Decimal places
Speed
import { TextAnimate, type NumberTickerProps } from '@gfazioli/mantine-text-animate';
import { Center, Stack, Switch } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';

function Demo() {
  return (
    <Stack>
      <Switch size="xl" checked={started} onLabel="ON" offLabel="OFF" onChange={toggle} />
      <TextAnimate.NumberTicker
        fz={64}
        c="violet"
        started={started}
        onNumberTickerCompleted={close}
      />
    </Stack>
  );
}

useNumberTicker() hook

The TextAnimate.NumberTicker is built by using the useNumberTicker hook.

Done

0

0

import { useNumberTicker } from '@gfazioli/mantine-text-animate';
import { Badge, Button, Divider, Group, Stack, Text } from '@mantine/core';

function Demo() {
  const { text, isAnimating, start, stop, reset, displayValue } = useNumberTicker({
    value: 64,
    startValue: 0,
    delay: 0,
    decimalPlaces: 0,
    speed: 0.2,
    easing: 'ease-out',
    animate: false,
  });

  return (
    <Stack
      <Text>{isAnimating ? 'Animating...' : 'Done'}</Text>
      <Group>
        <Button size="xs" onClick={start}>
          Start
        </Button>
        <Button size="xs" onClick={stop}>
          Stop
        </Button>
        <Button size="xs" onClick={reset}>
          Reset
        </Button>
      </Group>
      <Divider />
      <Badge color="violet" size="xl">
        {text}
      </Badge>
      <Text>{displayValue}</Text>
    </Stack>
  );
}

The interface of the useNumberTicker hook is the following:

export interface NumberTickerBaseProps {
  /**
   * The target value to animate to
   */
  value: number;

  /**
   * The initial value to start from
   * @default 0
   */
  startValue?: number;

  /**
   * Delay before starting the animation in seconds
   * @default 0
   */
  delay?: number;

  /**
   * Number of decimal places to display
   * @default 0
   */
  decimalPlaces?: number;

  /**
   * Animation speed multiplier (higher = faster)
   * @default 1
   */
  speed?: number;

  /**
   * Easing function for the animation
   * @default "ease-out"
   */
  easing?: NumberTickerEasing;

  /**
   * Whether the animation should start automatically
   * @default true
   */
  animate?: boolean;

  /**
   * Callback function called when animation completes
   */
  onCompleted?: () => void;
}

and the return value of the useNumberTicker hook is the following:

export interface UseNumberTickerResult {
  /**
   * The formatted text representation of the current value
   */
  text: string;

  /**
   * The current numeric value (not formatted)
   */
  displayValue: number;

  /**
   * Function to start the animation
   */
  start: () => void;

  /**
   * Function to stop the animation while keeping the current value
   */
  stop: () => void;

  /**
   * Function to reset the animation to the initial value
   */
  reset: () => void;

  /**
   * Whether the animation is currently running
   */
  isAnimating: boolean;
}

Example: NumberTicker

Norway

Norway Fjord Adventures

On Sale

100.99

$

With Mountain Expeditions, you can immerse yourself in the breathtaking mountain scenery through tours and activities in and around the majestic peaks

Download

0

Norway

Norway Fjord Adventures

On Sale

200.00

$

With Fjord Tours you can explore more of the magical fjord landscapes with tours and activities on and around the fjords of Norway

Download

0

import { useEffect, useRef, useState } from 'react';
import { TextAnimate } from '@gfazioli/mantine-text-animate';
import { Badge, Button, Card, Group, Image, Text } from '@mantine/core';
import { useInViewport } from '@mantine/hooks';


function Demo() {
  const { ref, inViewport } = useInViewport();

  const [downloadCount, setDownloadCount] = useState<number>(1266);
  const timerRef = useRef<NodeJS.Timeout | null>(null);

  useEffect(() => {
    if (inViewport) {
      timerRef.current = setTimeout(() => {
        setDownloadCount((prev) => prev + Math.floor(Math.random() * 10));
      }, 3000);
    }
  });

  return (
    <Group grow ref={ref}>
      <Card shadow="sm" padding="lg" radius="md" withBorder>
        <Card.Section>
          <Image
            src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-8.png"
            height={160}
            alt="Norway"
          />
        </Card.Section>

        <Group justify="space-between" mt="md" mb="xs">
          <Text fw={500}>Norway Fjord Adventures</Text>
          <Badge color="pink">
            On Sale{' '}
            <TextAnimate.NumberTicker
              animate={inViewport}
              startValue={100.99}
              value={88.99}
              speed={0.15}
              decimalPlaces={2}
            />
            $
          </Badge>
        </Group>

        <Text size="sm" c="dimmed">
          With Mountain Expeditions, you can immerse yourself in the breathtaking mountain scenery
          through tours and activities in and around the majestic peaks
        </Text>

        <Text size="md" ml="auto" mt={8}>
          Download{' '}
          <TextAnimate.NumberTicker
            animate={inViewport}
            fw={900}
            value={downloadCount}
            speed={0.2}
          />
        </Text>

        <Button color="blue" fullWidth mt="md" radius="md">
          Book classic tour now
        </Button>
      </Card>

      <Card shadow="sm" padding="lg" radius="md" withBorder>
        <Card.Section>
          <Image
            src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/images/bg-2.png"
            height={160}
            alt="Norway"
          />
        </Card.Section>

        <Group justify="space-between" mt="md" mb="xs">
          <Text fw={500}>Norway Fjord Adventures</Text>
          <Badge color="pink">
            On Sale{' '}
            <TextAnimate.NumberTicker
              animate={inViewport}
              startValue={200}
              value={99.99}
              speed={0.15}
              decimalPlaces={2}
            />
            $
          </Badge>
        </Group>

        <Text size="sm" c="dimmed">
          With Fjord Tours you can explore more of the magical fjord landscapes with tours and
          activities on and around the fjords of Norway
        </Text>

        <Text size="md" ml="auto" mt={8}>
          Download{' '}
          <TextAnimate.NumberTicker animate={inViewport} fw={900} value={984777} speed={0.12} />
        </Text>

        <Button color="blue" fullWidth mt="md" radius="md">
          Book classic tour now
        </Button>
      </Card>
    </Group>
  );

TextAnimate.TextTicker

The TextAnimate.TextTicker component allows you to animate text with a text ticker effect.

Delay
Speed
Random change speed
import { TextAnimate, type TextTickerProps } from '@gfazioli/mantine-text-animate';
import { Button, Center, Stack } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';

function Demo() {
  const [animated, { open, close, toggle }] = useDisclosure();

  return (
    <Center>
      <Stack>
        <TextAnimate.TextTicker fz="xl" {...props} animate={animated } onCompleted={close} />
        <Button onClick={open}>Start</Button>
      </Stack>
    </Center>
  );
}

useTextTicker() hook

The TextAnimate.TextTicker is built by using the useTextTicker hook.

Done

import { useTextTicker } from '@gfazioli/mantine-text-animate';
import { Badge, Button, Divider, Group, Stack, Text } from '@mantine/core';

function Demo() {
  const { text, isAnimating, start, stop, reset } = useTextTicker({
    value: 'Mantine useTextTicker',
    delay: 0,
    speed: 0.2,
    easing: 'ease-out',
    animate: false,
  });

  return (
    <Stack w={'100%'}>
      <Text>{isAnimating ? 'Animating...' : 'Done'}</Text>
      <Group>
        <Button size="xs" onClick={start}>
          Start
        </Button>
        <Button size="xs" onClick={stop}>
          Stop
        </Button>
        <Button size="xs" onClick={reset}>
          Reset
        </Button>
      </Group>
      <Divider />
      <Badge color="violet" size="xl">
        {text}
      </Badge>
    </Stack>
  );
}

The interface of the useTextTicker hook is the following:

export interface TextTickerBaseProps {
  /**
   * The target text to animate to
   */
  value: string;

  /**
   * Initial text display option
   * - "none": Display nothing until animation starts
   * - "random": Display random characters until animation starts
   * - "target": Display the target text immediately
   * @default "random"
   */
  initialText?: TextTickerInitialDisplay;

  /**
   * Whether the animation should start automatically
   * @default true
   */
  animate?: boolean;

  /**
   * Character set to use for random characters
   * @default "alphanumeric"
   */
  characterSet?: TextTickerCharacterSet;

  /**
   * Custom characters to use when characterSet is "custom"
   * @default ""
   */
  customCharacters?: string;

  /**
   * Delay before starting the animation in seconds
   * @default 0
   */
  delay?: number;

  /**
   * Animation speed multiplier (higher = faster)
   * @default 1
   */
  speed?: number;

  /**
   * Easing function for the animation
   * @default "ease-out"
   */
  easing?: TextTickerEasing;

  /**
   * Speed multiplier for random character changes (higher = more frequent changes)
   * @default 1
   */
  randomChangeSpeed?: number;

  /**
   * Direction for revealing the target text
   * @default "left-to-right"
   */
  revealDirection?: TextTickerRevealDirection;

  /**
   * Callback function called when animation completes
   */
  onCompleted?: () => void;
}

and the return value of the useTextTicker hook is the following:

export interface UseTextTickerResult {
  /**
   * The current text being displayed
   */
  text: string;

  /**
   * Function to start the animation
   */
  start: () => void;

  /**
   * Function to stop the animation while keeping the current text
   */
  stop: () => void;

  /**
   * Function to reset the animation to the initial text
   */
  reset: () => void;

  /**
   * Whether the animation is currently running
   */
  isAnimating: boolean;
}

Example: TextTicker

Welcome

import { TextAnimate } from '@gfazioli/mantine-text-animate';
import { Stack, Title } from '@mantine/core';
import { useInViewport } from '@mantine/hooks';

function Demo() {
  const { ref, inViewport } = useInViewport();

  return (
    <Stack w={'100%'} align="center" ref={ref}>
      <Title order={1}>Welcome</Title>
      <TextAnimate.TextTicker
        fs="italic"
        delay={1}
        c="violet"
        style={{
          textShadow: '0 0 10px rgba(255, 255, 255, 0.5)',
        }}
        speed={0.05}
        characterSet="custom"
        customCharacters="*"
        revealDirection="center-out"
        value="Amazing TextTicker component for Mantine UI Library"
        animate={inViewport}
      />
    </Stack>
  );
}