A short burst of digital glitch: the word splits into two coloured channels, slices jump sideways, then it snaps back clean.
Customize
Usage
import { GlitchText } from "@/components/motionkit/glitch-text";
<GlitchText text="SYSTEM ONLINE" className="font-mono text-5xl font-bold" />Motion
The exact curves this component runs, plotted with Motion's own maths.
Glitch burst
Hover or focus
Tech, gaming, security and cyber themes: hover states on nav links, section labels and short headings.
Two copies of the word sit exactly over the real one, each in one of the channel colours and blended with screen. A burst runs both through the same hand-tuned keyframes in mirror image: their clip-path cuts the word into a different horizontal slice each frame while they jump a few pixels sideways, then they fade out and the clean word remains. A fresh key replays it every time.
Works in the App Router as is. Requires Tailwind CSS v4.
One command adds the file and installs its dependencies. Needs a components.json (run npx shadcn@latest init once if you don't have one).
npx shadcn@latest add https://motionkitui.com/r/glitch-text.jsonnpm install motionSave as components/motionkit/glitch-text.tsx.
"use client";
import { useEffect, useRef, useState, type ElementType } from "react";
import { motion, useInView, useReducedMotion } from "motion/react";
type GlitchTextProps = {
text: string;
/** "hover" glitches when the pointer enters or focus lands; "view" once as it scrolls in. */
trigger?: "hover" | "view";
/** Seconds the burst lasts. Kept short on purpose. */
duration?: number;
/** The two split channels. */
colors?: [string, string];
/** Element to render, e.g. "h2". */
as?: ElementType;
className?: string;
};
// where each slice cuts and how far it jumps, frame by frame: hand-tuned so the burst reads as digital noise
const slices = ["inset(8% 0 70% 0)", "inset(55% 0 20% 0)", "inset(30% 0 45% 0)", "inset(75% 0 5% 0)", "inset(15% 0 60% 0)", "inset(0 0 0 0)"];
const shifts = [-4, 5, -3, 4, -2, 0];
/**
* A short burst of digital glitch: the word splits into two coloured channels, slices jump sideways,
* then it snaps back clean. Brief and small by design, so it stays well clear of flashing limits.
*/
export function GlitchText({ text, trigger = "hover", duration = 0.55, colors = ["#5FAEB4", "#A56D5B"], as: Tag = "span", className = "" }: GlitchTextProps) {
const reduceMotion = useReducedMotion();
const ref = useRef<HTMLElement>(null);
const inView = useInView(ref, { once: true, amount: 0.8 });
const [burst, setBurst] = useState(0);
// the view trigger plays one burst the first time the text is properly on screen
useEffect(() => {
if (trigger !== "view" || !inView || reduceMotion) return;
const timer = window.setTimeout(() => setBurst((n) => n + 1), 150);
return () => window.clearTimeout(timer);
}, [trigger, inView, reduceMotion]);
const play = () => trigger === "hover" && !reduceMotion && setBurst((n) => n + 1);
const layer = (color: string, direction: 1 | -1) => (
<motion.span
key={`${color}-${burst}`}
aria-hidden
className="pointer-events-none absolute inset-0 select-none"
style={{ color, mixBlendMode: "screen" }}
initial={{ opacity: 0 }}
animate={burst ? { opacity: [1, 1, 1, 1, 1, 0], x: shifts.map((shift) => shift * direction), clipPath: direction === 1 ? slices : [...slices].reverse() } : undefined}
transition={{ duration, ease: "linear", times: [0, 0.2, 0.4, 0.6, 0.8, 1] }}
>
{text}
</motion.span>
);
return (
<Tag ref={ref} onPointerEnter={play} onFocus={play} className={`relative inline-block ${className}`}>
{text}
{burst > 0 && (
<>
{layer(colors[0], 1)}
{layer(colors[1], -1)}
</>
)}
</Tag>
);
}
| Prop | Type | Default | Description |
|---|---|---|---|
| text | string | — | The word or short phrase. |
| trigger | "hover" | "view" | "hover" | Glitch on hover, or once as it scrolls in. |
| duration | number | 0.55 | Seconds the burst lasts. |
| colors | [string, string] | ["#5FAEB4", "#A56D5B"] | The two split channels. |
| as | ElementType | "span" | Element to render. |
How we audit: the WCAG 2.2 animation checklist and respecting reduced motion in React.
Yes. The file starts with "use client", so you can import it straight into a Server Component page or layout. It works in Vite as is, and in Astro as a React island.
Only the motion package, beside React and Tailwind CSS v4. There is no MotionKit package and no Tailwind config to add.
Yes. Run npx shadcn@latest add https://motionkitui.com/r/glitch-text.json in any project with a components.json. It saves the file to components/motionkit and installs its dependencies.
With reduced motion on, there's no glitch at all.
Text Scramble
Text that decodes itself character by character, terminal style.
Variable Proximity
Letters near the cursor swell to a heavier weight and ease back as it moves on, using a variable font.
Magnetic Letters
Each letter leans toward the cursor and springs home, like a magnetic button for every character.