Kanso UI LogoKanso UI
Components/text reveal
typography

Text Reveal

Text that reveals character by character with a blur effect when scrolled into view.

textrevealscrollanimationtypographystagger

Preview

Simplicity,Engineered.
Buildbeautifulinterfaceswithoutunnecessarycomplexity.

🖲Usage

example-usage.tsx
import { TextReveal } from "@/components/kanso/text-reveal"

export default function TextRevealDemo() {
  return (
    <TextReveal 
      text="Simplicity, Engineered." 
      className="text-2xl font-semibold text-zinc-900 dark:text-zinc-100" 
    />
  )
}

Installation

1

Create folder & copy source

Create a folder named kanso inside your project's components directory (i.e. components/kanso/). Copy the source code shown in the next section, and paste it into a file named text-reveal.tsx inside it.

2

Install dependencies

pnpm add framer-motion
3

Required helper files

Ensure your project has the following helper files configured:

  • lib/utils

<>Source Code

text-reveal.tsx
'use client';

import * as React from 'react';
import { motion, useInView } from 'framer-motion';
import { cn } from '@/lib/utils';

interface TextRevealProps extends React.HTMLAttributes<HTMLDivElement> {
  /** The text string to reveal */
  text: string;
  /** Delay before animation starts in seconds (default: 0) */
  delay?: number;
  /** Delay between each character in seconds (default: 0.03) */
  staggerDelay?: number;
}

/**
 * TextReveal — Text that reveals character by character when scrolled into view.
 *
 * Uses Framer Motion's `useInView` hook to trigger a staggered opacity animation
 * on each character. Creates an elegant typewriter-like reveal effect.
 *
 * @example
 * ```tsx
 * import { TextReveal } from "@/components/kanso/text-reveal"
 *
 * export default function Demo() {
 *   return (
 *     <TextReveal
 *       text="Simplicity, Engineered."
 *       className="text-4xl font-semibold tracking-tight"
 *     />
 *   )
 * }
 * ```
 */
function TextReveal({
  text,
  className,
  delay = 0,
  staggerDelay = 0.03,
  ...props
}: TextRevealProps) {
  const ref = React.useRef<HTMLDivElement>(null);
  const isInView = useInView(ref, { once: true, margin: '-10% 0px' });

  const words = text.split(' ');

  return (
    <div ref={ref} className={cn('flex flex-wrap', className)} {...props}>
      {words.map((word, wordIndex) => (
        <span key={wordIndex} className="inline-block mr-[0.25em]">
          {word.split('').map((char, charIndex) => {
            // Calculate global index for stagger
            const globalIndex =
              words.slice(0, wordIndex).reduce((acc, w) => acc + w.length, 0) +
              charIndex;

            return (
              <motion.span
                key={`${wordIndex}-${charIndex}`}
                className="inline-block"
                initial={{ opacity: 0, y: 8, filter: 'blur(4px)' }}
                animate={
                  isInView
                    ? { opacity: 1, y: 0, filter: 'blur(0px)' }
                    : { opacity: 0, y: 8, filter: 'blur(4px)' }
                }
                transition={{
                  duration: 0.3,
                  delay: delay + globalIndex * staggerDelay,
                  ease: [0.25, 0.1, 0.25, 1],
                }}
              >
                {char}
              </motion.span>
            );
          })}
        </span>
      ))}
    </div>
  );
}

export { TextReveal };
export type { TextRevealProps };

Props

Command Palette

Search for a command to run...