AnimationsSimpleApril 9, 2026

Highlight Text

A polymorphic text wrapper that uses GSAP SplitText and ScrollTrigger scrub to fade each character in as you scroll. Works on any heading or paragraph element via the `as` prop.

View Full Demo →

demo.jsx
import HighlightText from "./index.jsx";
import styles from "./demo.module.css";

export default function HighlightTextDemo() {
  return (
    <div className={styles.page}>

      {/* Hero — scroll down to see the effect */}
      <section className={styles.hero}>
        <p className={styles.heroLabel}>Scroll to reveal</p>
        <h1 className={styles.heroTitle}>
          Highlight<br />Text
        </h1>
      </section>

      {/* Highlight section */}
      <section className={styles.section}>
        <p className={styles.sectionLabel}>Manifesto</p>

        <HighlightText
          as="h2"
          scrollStart="top 85%"
          scrollEnd="center 35%"
          fade={0.15}
          stagger={0.08}
          className={styles.heading}
        >
          We build websites that move people — not just pixels.
        </HighlightText>

        <HighlightText
          as="p"
          scrollStart="top 90%"
          scrollEnd="center 40%"
          fade={0.2}
          stagger={0.05}
          className={styles.body}
        >
          Every project starts with an animation language. A set of principles that
          define how your brand communicates through motion, timing, and space.
        </HighlightText>
      </section>

      <div className={styles.spacer} />
    </div>
  );
}
index.jsx
"use client";

import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { SplitText } from "gsap/SplitText";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(SplitText, ScrollTrigger);

export default function HighlightText({
  as: Tag = "h2",
  children,
  scrollStart = "top 90%",
  scrollEnd = "center 40%",
  fade = 0.2,
  stagger = 0.1,
  className = "",
}) {
  const ref = useRef(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;

    let ctx;

    const split = new SplitText(el, {
      type: "words, chars",
      autoSplit: true,
      onSplit(self) {
        ctx = gsap.context(() => {
          const tl = gsap.timeline({
            scrollTrigger: {
              trigger: el,
              start: scrollStart,
              end: scrollEnd,
              scrub: true,
            },
          });

          tl.from(self.chars, {
            autoAlpha: fade,
            stagger,
            ease: "linear",
          });
        });

        return ctx;
      },
    });

    return () => {
      ctx?.revert();
      split?.revert();
    };
  }, [scrollStart, scrollEnd, fade, stagger]);

  return (
    <Tag ref={ref} className={className}>
      {children}
    </Tag>
  );
}
demo.module.css
.page {
  background: #0d0d0d;
  color: #efeeec;
}

/* Hero — pushes highlight section below the fold */
.hero {
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 3rem 4vw;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.heroLabel {
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.35);
  margin: 0 0 1.5rem;
}

.heroTitle {
  font-size: clamp(3.5rem, 10vw, 9rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 0.95;
  margin: 0;
}

/* Highlight section */
.section {
  padding: 15vh 4vw;
}

.sectionLabel {
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.3);
  margin: 0 0 3rem;
}

.heading {
  font-size: clamp(2.5rem, 6vw, 6rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin: 0 0 6rem;
  max-width: 16em;
}

.body {
  font-size: clamp(1.25rem, 2.5vw, 2rem);
  font-weight: 400;
  line-height: 1.5;
  max-width: 28em;
  margin: 0;
}

.spacer {
  height: 30vh;
}
  • gsap

Apr 27, 2026

ANIMATIONS

Cursor Hover Label

A custom cursor label that follows the mouse and fades in when hovering trigger elements. Uses GSAP quickTo for smooth tracking. Trigger elements use data-cursor-label attributes to set label text. Inspired by portfolio/agency sites like Studio PIC.

Apr 27, 2026

ANIMATIONS

Section Transition 01

GSAP ScrollTrigger-based section transition system. Sibling sections opt into parallax, pin, or reveal modes via data attributes. Supports y offset, overlay opacity, and overlay color per section. Mobile strategy simplifies motion on smaller screens.

Apr 27, 2026

ANIMATIONS

Text Reveal 01

GSAP SplitText-based text reveal system. Text elements opt in with data-reveal-01 and split into lines, words, or characters. Supports load-time reveals, scroll-triggered reveals, scrubbed scroll reveals, and manual split-only mode for custom timelines. Per-element overrides for duration, stagger, delay, ease, and replay behavior.