# Shimmer text

> A calm light passes across the words.

- Type: Component (light)
- Page: https://textlab.dev/components/shimmer-text
- Markdown: https://textlab.dev/components/shimmer-text.md
- Registry item: https://textlab.dev/r/shimmer-text.json
- Source file: `registry/textlab/shimmer-text/shimmer-text.tsx`
- Dependencies: none
- Variants: subtle, bright, rainbow
- Keywords: shimmer, loading text, skeleton, thinking, sheen, ai loading

## When to use

- Loading, thinking and generating states.
- Placeholders that should feel alive while content arrives.

## When not to use

- Headings that are already loaded; the motion implies work in progress.
- Error or success messages.

## Installation

### CLI

```bash
npx shadcn@latest add @textlab/shimmer-text
pnpm dlx shadcn@latest add @textlab/shimmer-text
```

The `@textlab` name needs `"registries": { "@textlab": "https://textlab.dev/r/{name}.json" }` in `components.json`. Without it, use the full URL:

```bash
npx shadcn@latest add https://textlab.dev/r/shimmer-text.json
```

Import the tokens once in your root layout: `import "@/components/textlab/tokens.css";`

### Manual

1. Install the dependencies: `npm install motion@12`
2. Copy `text-fx` (https://textlab.dev/r/text-fx.json) and this component into `components/textlab/`.

## Usage

```tsx
import { ShimmerText } from "@/components/textlab/shimmer-text/shimmer-text";

export function Example() {
  return <ShimmerText text="Thinking" />;
}
```

## API reference

### ShimmerText

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `text` (required) | `string` | – | The copy. |
| `variant` | `"subtle" \| "bright" \| "rainbow"` | `"subtle"` | A soft pass for status lines, a crisp sheen, or a hint of color. |
| `speed` | `number` | `2.4` | Seconds per pass, including the rest after it. |
| `spread` | `number` | `0.18` | Width of the light band as a share of the text. |
| `as` | `ElementType` | `"span"` | Rendered element. |
| `reducedMotion` | `"user" \| "always" \| "never"` | `"user"` | user follows the OS setting; always shows the still version. |
| `className` | `string` | – | Merged onto the root. |

## Accessibility

- Plain, unsplit text. Pair it with aria-busy on the region that is loading.

## Motion

- The light crosses in the first 70% of each cycle, then rests, so it never feels frantic.

## Reduced motion

The text shows at a steady, slightly muted color.

## Performance

- One CSS animation on background position.

## Notes for AI

- Use only the props documented above. Do not invent props.
- Keep the screen-reader text and the reduced-motion branch when editing the source.
- Pick a variant from: subtle, bright, rainbow.

## Related

- [Gradient text](https://textlab.dev/components/gradient-text.md): Color flows slowly through the letters.
- [Neon sign](https://textlab.dev/components/neon-text.md): A neon sign that hums and flickers on.
- [Typewriter](https://textlab.dev/components/typewriter.md): Types, deletes and retypes with a caret.

## Also in light

- [Aurora text](https://textlab.dev/components/aurora-text.md): Northern lights drift inside the letters.
- [Holographic text](https://textlab.dev/components/holo-text.md): Foil that shifts color as you move across it.
- [Spotlight text](https://textlab.dev/components/spotlight-text.md): A light follows the cursor and reveals the copy.
- [Glass text](https://textlab.dev/components/glass-text.md): Glass letters that bend the scene behind them.

## Source

### components/textlab/shimmer-text/shimmer-text.tsx

```tsx
"use client";

import type { CSSProperties, ElementType } from "react";
import { useReduced, type ReducedMotion } from "../lib/use-reduced";

export type ShimmerTextVariant = "subtle" | "bright" | "rainbow";

export interface ShimmerTextProps {
  text: string;
  /** subtle is a soft pass for status lines, bright is a crisp sheen, rainbow carries a hint of color. */
  variant?: ShimmerTextVariant;
  /** Seconds per pass, including the rest between passes. */
  speed?: number;
  /** Width of the light band, as a share of the text. */
  spread?: number;
  as?: ElementType;
  reducedMotion?: ReducedMotion;
  className?: string;
  style?: CSSProperties;
}

const CSS = `
@keyframes tl-shimmer { 0% { background-position: 100% 0; } 70%, 100% { background-position: 0% 0; } }
.tl-shimmer { background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-repeat: no-repeat; }
@media (prefers-reduced-motion: reduce) { .tl-shimmer[data-motion="user"] { animation: none !important; background-position: 50% 0 !important; } }
`;

/**
 * A calm light passes across the words, then rests. Made for "thinking", "loading" and
 * "generating" lines where something is happening but nothing needs attention yet.
 */
export function ShimmerText({ text, variant = "subtle", speed = 2.4, spread = 0.18, as: Tag = "span", reducedMotion = "user", className, style }: ShimmerTextProps) {
  const reduced = useReduced(reducedMotion);
  const half = (spread * 100) / 2;
  const base = variant === "bright" ? "color-mix(in oklab, currentColor 45%, transparent)" : "color-mix(in oklab, currentColor 55%, transparent)";
  const light =
    variant === "rainbow"
      ? `var(--tl-fx-spectrum-1) ${50 - half}%, var(--tl-fx-spectrum-2) ${50 - half / 3}%, var(--tl-fx-spectrum-3) ${50 + half / 3}%, var(--tl-fx-spectrum-4) ${50 + half}%`
      : `currentColor 50%`;
  const image = `linear-gradient(90deg, ${base} ${50 - half}%, ${light}, ${base} ${50 + half}%)`;

  return (
    <Tag
      className={["tl-shimmer", className].filter(Boolean).join(" ")}
      data-motion={reducedMotion === "user" ? "user" : undefined}
      style={{
        backgroundImage: `${image}, linear-gradient(${base}, ${base})`,
        backgroundSize: "250% 100%, 100% 100%",
        animation: reduced ? "none" : `tl-shimmer ${speed}s linear infinite`,
        backgroundPosition: reduced ? "50% 0" : undefined,
        filter: variant === "bright" ? "drop-shadow(0 0 0.25em color-mix(in oklab, currentColor 18%, transparent))" : undefined,
        ...style,
      }}
    >
      <style href="tl-shimmer-text" precedence="default">{CSS}</style>
      {text}
    </Tag>
  );
}

export default ShimmerText;
```

Full library index: https://textlab.dev/llms.txt
