Skip to main content

Prism Reveal Text

Text that materializes behind a traveling prism: the fill is a 300%-wide gradient — one third transparent, a five-stop prism band (violet, red, amber, lavender, blue) riding the reveal edge, then solid ink — clipped to the glyphs. Animating backgroundPosition sweeps the edge across so characters appear out of a flash of refracted color; both directions plus a custom-color dynamic variant.

TextReactMotionTailwind CSS
CSSTailwind

Manual

Create a file and paste the following code into it.

prism-reveal-text.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"use client";

import { useMemo, useRef } from "react";
import { motion, useInView } from "motion/react";
import { cn } from "@/lib/cn";

/* ------------------------------------------------------------------ */
/*  PrismRevealText                                                    */
/* ------------------------------------------------------------------ */

type RevealVariant = "prism-rtl" | "prism-ltr" | "dynamic";

interface PrismRevealTextProps {
  text: string;
  /** Sweep duration in seconds [Optional, default: 0.9] */
  duration?: number;
  delay?: number;
  repeat?: boolean;
  repeatDelay?: number;
  /** Stops for the dynamic variant's reveal edge [Optional] */
  colors?: string[];
  ease?: "linear" | "easeIn" | "easeOut" | "easeInOut";
  /** Which edge the ink appears from [Optional, default: "prism-rtl"] */
  variant?: RevealVariant;
  className?: string;
  startOnView?: boolean;
  once?: boolean;
}

const INK = "light-dark(#0a0a0a, #f5f5f5)";

/* The house prism — coral, cream, mint, lavender, ultramarine (the kinetic
   landing palette) — riding the reveal edge like light through a lens. */
const PRISM_RTL =
  "#F4574D 40%, #FFE29E 45%, #BDEDC9 50%, #898FE9 55%, #202DED 60%";
const PRISM_LTR =
  "#202DED 40%, #898FE9 45%, #BDEDC9 50%, #FFE29E 55%, #F4574D 60%";

/**
 * Text that materializes behind a traveling prism: the fill is a 300%-wide
 * gradient — one third transparent (not yet revealed), a five-stop prism
 * band at the edge, then solid ink — clipped to the glyphs, and animating
 * backgroundPosition sweeps the edge across so characters appear out of a
 * flash of refracted color.
 * @param {string} text - Text to reveal [Required]
 * @param {RevealVariant} variant - Reveal direction or custom colors [Optional, default: "prism-rtl"]
 */
export function PrismRevealText({
  text,
  duration = 0.9,
  delay = 0,
  repeat = false,
  repeatDelay = 0.5,
  colors,
  ease = "linear",
  variant = "prism-rtl",
  className,
  startOnView = true,
  once = true,
}: PrismRevealTextProps) {
  const ref = useRef<HTMLSpanElement | null>(null);
  const inView = useInView(ref, { once });

  const backgroundImage = useMemo(() => {
    if (variant === "prism-rtl") {
      return `linear-gradient(90deg, transparent 0%, transparent 33.33%, ${PRISM_RTL}, ${INK} 66.67%, ${INK})`;
    }
    if (variant === "prism-ltr") {
      return `linear-gradient(90deg, ${INK} 0%, ${INK} 33.33%, ${PRISM_LTR}, transparent 66.67%, transparent)`;
    }
    const stops = colors ?? ["rgb(96,165,250)", "rgb(191,219,254)"];
    const span = Math.max(stops.length - 1, 1);
    const band = stops
      .map((c, i) => `${c} ${(33.33 + (i / span) * 33.34).toFixed(2)}%`)
      .join(", ");
    return `linear-gradient(90deg, ${INK} 0%, ${INK} 33.33%, ${band}, transparent 66.67%, transparent)`;
  }, [variant, colors]);

  const from = variant === "prism-rtl" ? "0% center" : "100% center";
  const to = variant === "prism-rtl" ? "100% center" : "0% center";

  return (
    <motion.span
      ref={ref}
      className={cn("relative inline-block bg-clip-text pb-[0.15em] text-transparent", className)}
      style={{ backgroundImage, backgroundSize: "300% 100%", backgroundRepeat: "no-repeat" }}
      initial={{ backgroundPosition: from }}
      animate={!startOnView || inView ? { backgroundPosition: to } : {}}
      transition={{
        backgroundPosition: {
          duration,
          delay,
          ease,
          repeat: repeat ? Infinity : 0,
          repeatDelay,
        },
      }}
    >
      {text}
    </motion.span>
  );
}

Update the import paths to match your project setup.

Similar components

Spotlight Quotes

Char Spring Morph

Snap Scroll Headlines

Cipher Text Reveal

Install via CLI

Resource details

PublishedJuly 17, 2026
CategoryText
ReactMotionTailwind CSS