Blur-in Letters

A headline comes into focus letter by letter, each one sharpening and rising slightly as it lands.

  • CSS
  • Text
  • Entrance
  • Added

AI prompt

Create a React headline entrance where each letter goes from blurred to sharp. Split "Into focus" into inline-block spans with Array.from and set --i to each index. Each span animates from opacity 0, filter blur(12px) and translateY(0.25em) to opacity 1, blur(0) and translateY(0) over 700ms with cubic-bezier(0.16, 1, 0.3, 1), fill-mode both, delayed by calc(var(--i) * 45ms). Add a "Replay" button that restarts the animation by changing the wrapper key. Keep the readable text in aria-label. Under prefers-reduced-motion, use a single 150ms opacity fade on the whole word instead.

Source

Demo.tsx
'use client'

import { useState } from 'react'
import s from './demo.module.css'

export default function BlurInLetters({ labels }: { labels: Record<string, string> }) {
  const [run, setRun] = useState(0)

  return (
    <div className={s.wrap}>
      <p key={run} className={s.text} aria-label={labels.text}>
        {Array.from(labels.text).map((ch, i) => (
          <span key={i} className={s.ch} style={{ '--i': i } as React.CSSProperties} aria-hidden="true">
            {ch === ' ' ? ' ' : ch}
          </span>
        ))}
      </p>
      <button type="button" className={s.button} onClick={() => setRun(run + 1)}>
        {labels.replay}
      </button>
    </div>
  )
}
demo.module.css
.wrap { display: grid; justify-items: center; gap: 16px; }

.text {
  display: flex;
  margin: 0;
  font: 700 2.6rem / 1.1 var(--font-display);
  letter-spacing: -0.03em;
  color: var(--stage-ink);
  white-space: nowrap;
}

.ch {
  display: inline-block;
  animation: focus-in 700ms var(--ease-out) both;
  animation-delay: calc(var(--i) * 45ms);
}

@keyframes focus-in {
  from { opacity: 0; filter: blur(12px); transform: translateY(0.25em); }
}

.button {
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--stage-rule);
  border-radius: 8px;
  background: var(--stage-surface);
  color: var(--stage-ink);
  font: 500 var(--text-sm) / 1 var(--font-body);
  white-space: nowrap;
  cursor: pointer;
}

.button:hover { border-color: var(--stage-accent); }
.button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .ch { animation: none; }
  .text { animation: fade 150ms linear both; }
}

@keyframes fade { from { opacity: 0; } }

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.