Home / Snippets / Typography /

Animated heading entrance

Three heading entrance patterns — fade-up, clip-path wipe, and letter-by-letter stagger — built entirely with @keyframes.

Fade-up

Design with motion

Clip-path wipe

Reveal left to right

Letter stagger

Widely Supported
typographyanimationno-js

Quick implementation

/* 1. Fade-up entrance */
.heading-fade-up {
  animation: heading-fade-up 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes heading-fade-up {
  from { transform: translateY(1.25em); opacity: 0; }
  to   { transform: translateY(0);      opacity: 1; }
}

/* 2. Clip-path wipe reveal (left → right) */
.heading-clip-reveal {
  animation: heading-clip-reveal 0.75s cubic-bezier(0.4, 0, 0.2, 1) both;
}
@keyframes heading-clip-reveal {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0% 0 0); }
}

/* 3. Letter-by-letter stagger */
/* Wrap each character in a <span class="letter"> */
.heading-stagger .letter {
  display: inline-block;
  animation: heading-letter-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
/* Set animation-delay via inline style or :nth-child() */
@keyframes heading-letter-in {
  from { transform: translateY(0.6em) rotate(6deg); opacity: 0; }
  to   { transform: translateY(0) rotate(0deg);     opacity: 1; }
}

/* Reduced-motion overrides */
@media (prefers-reduced-motion: reduce) {
  .heading-fade-up,
  .heading-clip-reveal,
  .heading-stagger .letter {
    animation: none;
    opacity: 1;
    transform: none;
    clip-path: none;
  }
}

Prompt this to your LLM

Includes role, goal, constraints, framework variants, and edge cases to handle.

You are a senior frontend engineer implementing animated heading entrances
for a content-driven website.

Goal: CSS-only @keyframes animations for three heading entrance styles —
a fade-up (translateY + opacity), a clip-path wipe reveal (left to right),
and a letter-by-letter stagger (each character wrapped in a <span>).

Technical constraints:
- Use @keyframes exclusively — no JavaScript animation libraries.
- Fade-up: start at translateY(1.25em) opacity 0, end at translateY(0)
  opacity 1. Use cubic-bezier(0.22, 1, 0.36, 1) for a springy ease-out.
- Clip-path wipe: animate clip-path from inset(0 100% 0 0) to
  inset(0 0% 0 0) so the heading reveals left to right.
- Letter stagger: wrap each character in display: inline-block and apply
  animation-delay based on character index (e.g. 0.04s per letter).
  Include a small rotate(6deg) in the "from" state for personality.
- Use animation fill-mode: both on all variants.
- Use oklch() for any color values — no hex or rgba.
- Use CSS custom properties for font-family (var(--font-display)) and
  color (var(--text), var(--accent)).
- Include @media (prefers-reduced-motion: reduce) that sets animation: none,
  opacity: 1, transform: none, and clip-path: none on all animated elements.

Framework variant (pick one):
A) Vanilla CSS classes with HTML markup examples.
B) React — a component that accepts text (string), variant
   ('fade-up' | 'clip-reveal' | 'letter-stagger'), and tag ('h1'–'h4');
   auto-splits text into letter spans for the stagger variant.

Edge cases to handle:
- The letter-stagger variant splits text into individual characters; spaces
  must be preserved (use a non-breaking space span or padding-right on the
  preceding letter span).
- clip-path: inset() is not supported in older Safari — provide a
  graceful fallback using opacity-only if clip-path is unsupported.
- Avoid animating headings that are already in the viewport on load without
  a visibility check — consider adding .is-visible via IntersectionObserver
  to trigger the animation class rather than applying it in HTML directly.

Return CSS and HTML markup (or a React component if variant B is chosen).

Why animated headings create hierarchy and draw attention

A heading is the first thing a reader's eye lands on. An entrance animation adds a beat of time that lets the heading arrive before the surrounding content settles — reinforcing its role as the primary signal on the page. Motion draws the eye involuntarily, so even a 300ms fade-up is enough to pull focus without feeling gratuitous. The animation becomes part of the typographic hierarchy: the heading doesn't just sit at the top visually, it arrives first.

The fade-up is the safest default for this pattern. Starting at translateY(1.25em) and fading to full opacity covers a short, natural-feeling distance. Because the offset is expressed in em units rather than pixels, it scales proportionally with font size — a 48px display heading travels 60px, a 20px body heading travels 25px. The cubic-bezier (0.22, 1, 0.36, 1) gives a springy overshoot feel without actually overshooting, making the landing feel confident.

The clip-path wipe reveal reads differently: it feels like text being uncovered by a sweeping motion. This suits editorial contexts — magazine-style hero sections, feature announcements, or anywhere you want to evoke unveiling. The letter-by-letter stagger is the most expressive of the three and should be used sparingly; it works best on short display headings (two to five words) where the cascade of letters has enough room to breathe.

Performance: clip-path vs transform

transform and opacity are compositor-only properties. The browser promotes elements animating these properties to their own GPU layer and handles the animation entirely off the main thread — smooth at 60fps even when JavaScript is busy. The fade-up and letter-stagger variants rely exclusively on these properties and are as performant as CSS animation gets.

clip-path is more nuanced. When animating clip-path: inset(), modern browsers (Chrome 112+, Safari 17+, Firefox 117+) can also promote the animation to the compositor, but only when the element has no children that would require separate compositing. A heading with no nested interactive elements qualifies. For older targets, clip-path animation falls back to painting on the main thread — still fast for a single element, but worth benchmarking if you have many headings on screen simultaneously.

For the letter-stagger variant, each <span> wrapping a character gets its own compositing context. On long headings this can mean dozens of layers, which increases GPU memory usage. Keep stagger animations to short strings (under 30 characters) to avoid layer-budget pressure on mobile devices.

Accessibility

The prefers-reduced-motion: reduce override is non-negotiable for heading animations. Vestibular disorder sufferers can experience nausea from moving text, and the fade-up and stagger variants involve visible motion. Inside the media query, set animation: none, opacity: 1, transform: none, and clip-path: none so the heading is immediately visible in its final state — no flash of invisible content, no layout shift.

For the letter-stagger variant, add aria-label to the parent heading with the full text string. Screen readers will encounter the word split into individual <span> elements and may announce each letter separately without this label. The aria-label short-circuits that and provides the correct reading.