Skip to content
Docs
Glitch text

Glitch text

RGB-split glitch on text — two colored ghosts jitter on a loop while the label stays sharp on top.

textanimated
Loading...

Installation

CLI

pnpm dlx shadcn@latest add https://animata.design/r/text/glitch-text.json

The CLI installs glitch-text.tsx and the co-located glitch-text.css in one step.

Manual

Run the following command

mkdir -p animata/text && touch animata/text/glitch-text.tsx animata/text/glitch-text.css

Paste the code

@layer components {
  .glitch-text {
    --glitch-intensity: 5;
    --glitch-step: 0.01em;
    --glitch-duration: 2.5s;
    --glitch-color-base: #ffffff;
    --glitch-color-a: #ff00ff;
    --glitch-color-b: #00ffff;
    --glitch-blend-mode: screen;
 
    /* Derived offsets — em-relative so glitch scales with font-size. */
    --glitch-x: calc(var(--glitch-intensity) * var(--glitch-step));
    --glitch-y: max(var(--glitch-step), calc(var(--glitch-x) * 0.35));
    --glitch-x-1: calc((var(--glitch-intensity) - 1) * var(--glitch-step));
    --glitch-x-2: calc((var(--glitch-intensity) - 2) * var(--glitch-step));
 
    position: relative;
    display: inline-grid;
    grid-template-areas: stack;
  }
 
  .glitch-text__ghost-a,
  .glitch-text__ghost-b,
  .glitch-text__base {
    grid-area: stack;
  }
 
  .glitch-text__ghost-a {
    color: var(--glitch-color-a);
    mix-blend-mode: var(--glitch-blend-mode);
    animation: glitch-a var(--glitch-duration) infinite;
  }
 
  .glitch-text__ghost-b {
    color: var(--glitch-color-b);
    mix-blend-mode: var(--glitch-blend-mode);
    animation: glitch-b var(--glitch-duration) infinite;
  }
 
  .glitch-text__base {
    position: relative;
    z-index: 2;
    color: var(--glitch-color-base);
  }
 
  @keyframes glitch-a {
    0%,
    88%,
    100% {
      transform: translate(0, 0);
      opacity: 0.85;
    }
 
    90% {
      transform: translate(calc(var(--glitch-x) * -1), calc(var(--glitch-y) * -1));
      opacity: 1;
    }
 
    92% {
      transform: translate(var(--glitch-x-1), var(--glitch-y));
      opacity: 0.9;
    }
 
    94% {
      transform: translate(calc(var(--glitch-x-2) * -1), 0);
      opacity: 1;
    }
 
    96% {
      transform: translate(var(--glitch-x), calc(var(--glitch-y) * -1));
      opacity: 0.85;
    }
  }
 
  @keyframes glitch-b {
    0%,
    86%,
    100% {
      transform: translate(0, 0);
      opacity: 0.85;
    }
 
    88% {
      transform: translate(var(--glitch-x), var(--glitch-y));
      opacity: 1;
    }
 
    91% {
      transform: translate(calc(var(--glitch-x) * -1), calc(var(--glitch-y) * -1));
      opacity: 0.9;
    }
 
    93% {
      transform: translate(var(--glitch-x-1), 0);
      opacity: 1;
    }
 
    95% {
      transform: translate(calc(var(--glitch-x-2) * -1), var(--glitch-y));
      opacity: 0.85;
    }
  }
 
  @media (prefers-reduced-motion: reduce) {
    .glitch-text__ghost-a,
    .glitch-text__ghost-b {
      animation: none;
      opacity: 0.35;
    }
  }
}
"use client";
 
import { cn } from "@/lib/utils";
 
import "./glitch-text.css";
 
export interface GlitchTextProps extends Omit<React.ComponentPropsWithoutRef<"span">, "children"> {
  children: string;
  /** Unitless multiplier on `--glitch-step`. @default 5 (via CSS) */
  intensity?: number;
  /** Base offset unit — em scales with font size. @default "0.01em" */
  step?: string;
  /** Glitch burst cycle. Numbers are seconds (`2.5` → `2.5s`). @default 2.5 */
  duration?: number | string;
  /** Main layer color (on top). @default "#ffffff" */
  baseColor?: string;
  /** Back ghost layer A. @default "#ff00ff" */
  colorA?: string;
  /** Back ghost layer B. @default "#00ffff" */
  colorB?: string;
  /** Ghost layer blend mode. @default "screen" */
  blendMode?: "screen" | "normal";
}
 
function glitchDuration(duration: number | string) {
  return typeof duration === "number" ? `${duration}s` : duration;
}
 
/**
 * RGB glitch via stacked text layers — ghosts animate transform only (compositor),
 * base stays full-color on top. Props map to CSS variables; defaults live in `glitch-text.css`.
 */
export default function GlitchText({
  children,
  className,
  intensity,
  step,
  duration,
  baseColor,
  colorA,
  colorB,
  blendMode,
  style,
  ...props
}: GlitchTextProps) {
  return (
    <span
      {...props}
      className={cn("glitch-text", className)}
      style={
        {
          ...(intensity !== undefined && { "--glitch-intensity": intensity }),
          ...(step !== undefined && { "--glitch-step": step }),
          ...(duration !== undefined && {
            "--glitch-duration": glitchDuration(duration),
          }),
          ...(baseColor !== undefined && { "--glitch-color-base": baseColor }),
          ...(colorA !== undefined && { "--glitch-color-a": colorA }),
          ...(colorB !== undefined && { "--glitch-color-b": colorB }),
          ...(blendMode !== undefined && { "--glitch-blend-mode": blendMode }),
          ...style,
        } as React.CSSProperties
      }
    >
      <span aria-hidden className="glitch-text__ghost-a select-none">
        {children}
      </span>
      <span aria-hidden className="glitch-text__ghost-b select-none">
        {children}
      </span>
      <span className="glitch-text__base">{children}</span>
    </span>
  );
}

Usage

import GlitchText from "@/animata/text/glitch-text";
 
<GlitchText className="font-(family-name:--font-mono) text-8xl font-bold tracking-tight">
  404
</GlitchText>

GlitchText is text only. Drop it on your own background — a gradient, a starfield, a solid block. Size it for display type; the offset is em-relative, but the effect is hard to read at body scale.

Set the readable color with baseColor (or --glitch-color-base). Ghost colors use colorA / colorB. Font, weight, and casing live on className.

<HyperspaceScatter className="min-h-[480px]">
  <GlitchText
    baseColor="#ffffff"
    className="font-(family-name:--font-mono) text-8xl font-bold tracking-tight"
  >
    404
  </GlitchText>
</HyperspaceScatter>

Tune the burst with props — each one writes a matching --glitch-* variable on the root:

<GlitchText
  baseColor="#ffffff"
  intensity={8}
  duration={3}
  colorA="#ff00ff"
  colorB="#00ffff"
  className="text-6xl font-black"
>
  signal lost
</GlitchText>

Prefer CSS-only? Same variables on .glitch-text, or Tailwind arbitrary properties like [--glitch-intensity:8]. Pass style last if you need to override a prop.

GlitchText always renders a span. Wrap it in a heading or link when you need that semantics — do not expect as on the component.

How it works

Markup

The root is an inline grid (.glitch-text) with three stacked layers in the same cell: .glitch-text__ghost-a, .glitch-text__ghost-b, and .glitch-text__base. Each layer repeats the same string; the ghosts are aria-hidden.

Motion

Ghosts animate transform and opacity only — no layout thrash, no JS loop. The base layer sits on top at full opacity. Keyframes live in glitch-text.css.

Bursts run on a cycle (--glitch-duration, default 2.5s). Most of the time the ghosts sit still; for a few frames they snap sideways. Horizontal travel is --glitch-intensity × --glitch-step (default 5 × 0.01em), with a smaller vertical offset derived from that. Bump intensity for a harsher glitch, or step if you want a different em scale.

Ghost colors default to magenta and cyan with mix-blend-mode: screen. Change blendMode to normal on dark backgrounds if screen washes out.

Reduced motion

At prefers-reduced-motion: reduce, the ghosts stop animating and fade to 0.35 opacity — the label stays readable.

Accessibility

Screen readers get the string once from .glitch-text__base. The two ghost copies are decorative and hidden from the tree.

Props

Component props
PropTypeDefaultDescription
childrenstring

Label copied into all three layers.

classNamestring

Typography on the root — font, size, weight, tracking, casing.

intensitynumber5

Unitless multiplier on --glitch-step for horizontal offset.

stepstring"0.01em"

Base offset unit. Em-relative so the glitch scales with font size.

durationnumber | string2.5

Length of one glitch cycle. Numbers count as seconds (33s).

baseColorstring"#ffffff"

Top layer — the text you actually read.

colorAstring"#ff00ff"

First ghost layer.

colorBstring"#00ffff"

Second ghost layer.

blendMode"screen" | "normal""screen"

Blend mode on both ghosts.

styleReact.CSSProperties

Merged after glitch props — override any --glitch-* variable.

Defaults for --glitch-intensity, --glitch-step, --glitch-duration, and the color variables also live on .glitch-text in glitch-text.css if you skip the props entirely.

Credits

Built by hari.