# Text Effects

Accessible React text effects for Next.js and Tailwind CSS: a shimmer, a word-by-word reveal and a typewriter, all real text and off with reduced motion.

Source: https://ui.fewtone.com/components/text-effects

## Installation

```bash
npx shadcn@latest add https://ui.fewtone.com/r/text-effects.json
```

## Usage

```tsx
import { TextReveal, TextShimmer, Typewriter } from "@/components/ui/text-effects"

export function Hero() {
  return (
    <>
      <TextReveal as="h1">Ship it this afternoon</TextReveal>
      <p>
        Build <Typewriter phrases={["forms", "menus", "dialogs"]} />
      </p>
      <TextShimmer>Generating a reply…</TextShimmer>
    </>
  )
}
```

## API reference

### TextShimmer

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` | – | The text. Keep it to text: it's drawn twice. |

### TextReveal

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `string` | – | The text to reveal. |
| `as` | `"p" \| "span" \| "div" \| "h1" \| "h2" \| "h3" \| "h4" \| "h5" \| "h6" \| "blockquote" \| "li"` | `"p"` | The element to render, for example h2 for a heading. |
| `by` | `"word" \| "character"` | `"word"` | Reveal word by word, or character by character for a short line. |
| `delay` | `number` | `0` | Seconds before the first piece rises. |
| `startOnView` | `boolean` | `true` | Reveal once it scrolls into view. With false, it reveals as soon as it mounts. |

### Typewriter

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `phrases` | `string \| string[]` | – | One phrase, or several to type in turn. |
| `loop` | `boolean` | – | Start over after the last phrase. On by default for several phrases, off for one. |
| `typeSpeed` | `number` | `0.08` | Seconds per character typed. |
| `deleteSpeed` | `number` | `0.04` | Seconds per character deleted. |
| `hold` | `number` | `2` | Seconds a typed phrase stays before it's deleted. |
| `delay` | `number` | `0` | Seconds before the first character. |
| `cursor` | `boolean` | `true` | Show the caret. It fades away once a phrase typed only once is done. |

**When to use it:** Use TextReveal for a heading or line that should rise into place once as it scrolls into view, Typewriter for a short phrase that cycles ("Build forms, menus, dialogs"), and TextShimmer for a label that says something is working, such as an AI reply being generated. Keep them for one or two moments per page.
**Built on:** Web Animations API.
**npm packages (installed by the CLI):** motion@^14.0.0.

## Examples

### Shimmer, reveal and typewriter

A light sweeps across the shimmer in one neutral hue. The reveal's words rise through the blur, 0.05s apart. The typewriter's caret stays solid while it types and breathes while it waits. Press Replay to play them again.

```tsx
import { TextReveal, TextShimmer, Typewriter } from "@/components/ui/text-effects";

/*
 * The three effects side by side, named underneath. Replay plays the reveal and the typing again; the shimmer
 * keeps sweeping.
 */
export default function TextEffectsOverview() {
  return (
    <ul className="grid w-full max-w-2xl grid-cols-1 items-end justify-items-center gap-x-10 gap-y-8 sm:grid-cols-3">
      <li className="flex flex-col items-center gap-2">
        <TextShimmer className="text-sm font-medium">Generating a reply…</TextShimmer>
        <span className="text-xs text-muted-foreground">Shimmer</span>
      </li>
      <li className="flex flex-col items-center gap-2">
        <TextReveal className="text-base font-semibold tracking-tight">Ship it this afternoon</TextReveal>
        <span className="text-xs text-muted-foreground">Reveal</span>
      </li>
      <li className="flex flex-col items-center gap-2">
        {/* The longest phrase, invisible, holds the width, so the line doesn't re-centre as it types */}
        <span className="grid text-base font-semibold tracking-tight">
          <span aria-hidden className="invisible col-start-1 row-start-1">
            Build dialogs
          </span>
          <span className="col-start-1 row-start-1">
            Build <Typewriter phrases={["forms", "menus", "dialogs"]} />
          </span>
        </span>
        <span className="text-xs text-muted-foreground">Typewriter</span>
      </li>
    </ul>
  );
}
```

### Reveal by character

by="character" for a short line. Each word stays whole across a line break.

```tsx
import { TextReveal } from "@/components/ui/text-effects";

/** Character by character, for a short line. Each word stays whole across a line break. */
export default function TextRevealByCharacter() {
  return (
    <TextReveal by="character" className="text-base font-semibold tracking-tight">
      Calm by default
    </TextReveal>
  );
}
```

### Type once

One phrase types once and stays; the caret breathes for a moment, then fades away.

```tsx
import { Typewriter } from "@/components/ui/text-effects";

/** One phrase, typed once: the caret breathes for a moment, then fades away. */
export default function TypewriterOnce() {
  return <Typewriter phrases="Your workspace is ready." className="text-sm" />;
}
```

## Related

- [Number Ticker](https://ui.fewtone.com/components/number-ticker.md): An accessible React number ticker for Next.js and Tailwind CSS: digits roll like an odometer and glide as the number changes length, with Intl formatting.
- [Skeleton](https://ui.fewtone.com/components/skeleton.md): A React skeleton loader for Next.js and Tailwind CSS: placeholder blocks while content loads, with a calm sheen that sweeps every skeleton in step.
- [Marquee](https://ui.fewtone.com/components/marquee.md): An accessible React marquee for Next.js and Tailwind CSS: an endless, seamless row of logos or cards that pauses on hover and stops with reduced motion.
