Text Effects
InkReveal
Text spreads out from the left like ink soaking into paper, soft at the edge and sharpening as it dries.
Download
- React
- Tailwind CSS v4
- motion
components/ui/ink-reveal.tsx
"use client";
import type { ReactNode } from "react";
import { motion, useReducedMotion } from "motion/react";
type InkRevealProps = {
children: ReactNode;
className?: string;
/** Seconds the ink takes to spread. */
duration?: number;
/** Play only the first time it scrolls into view. */
once?: boolean;
};
const MASK = "radial-gradient(circle at 0% 50%, #000 var(--ink), transparent calc(var(--ink) + 25%))";
/* InkReveal: text spreads out from the left like ink soaking into paper, soft at the edge and sharpening as it dries. */
export function InkReveal({ children, className = "", duration = 1.6, once = true }: InkRevealProps) {
const reduce = useReducedMotion();
if (reduce) return <span className={className}>{children}</span>;
return (
<motion.span
className={`inline-block ${className}`}
style={{ maskImage: MASK, WebkitMaskImage: MASK }}
initial={{ "--ink": "-25%", filter: "blur(6px)" }}
whileInView={{ "--ink": "150%", filter: "blur(0px)" }}
viewport={{ once, amount: 0.6 }}
transition={{ duration, ease: [0.25, 1, 0.5, 1] }}
>
{children}
</motion.span>
);
}
1. Install the dependency.
npm i motion2. Paste the Code tab into
components/ui/ink-reveal.tsx. The styles are Tailwind CSS v4 classes.3. Use it. This is the preview above:
app/ink-reveal-demo.tsx
"use client"; import { useState } from "react"; import { InkReveal } from "@/components/ui/ink-reveal"; export default function InkRevealExample() { const [run, setRun] = useState(0); return ( <div className="grid h-full place-items-center bg-[#f4f1ea] p-6"> <div className="max-w-xl text-center"> <InkReveal key={run} className="font-serif text-[clamp(28px,5vw,56px)] italic leading-tight text-[#1b1a3a]"> Every word, written slowly. </InkReveal> <button type="button" onClick={() => setRun((r) => r + 1)} className="mt-6 block w-full text-xs text-[#1b1a3a]/50 hover:text-[#1b1a3a]"> Replay </button> </div> </div> ); }
Props
| Name | Type | Default | What it does |
|---|---|---|---|
| children | ReactNode | — | Text to reveal. |
| duration | number | 1.6 | Seconds the ink takes to spread. |
| once | boolean | true | Play only the first time it scrolls into view. |
| className | string | "" | Type styles for the text. |