kyle.berry
All components

Component · May 22, 2026

Stagger button

A button that staggers its label characters into view on hover.

Stagger button

component.tsx
'use client'

import { useState } from 'react'

const label = 'Stagger'

export default function StaggerButton() {
  // Keyboard parity: focusing the button plays the same stagger as hover.
  const [lifted, setLifted] = useState(false)
  return (
    <button
      type="button"
      onMouseEnter={() => setLifted(true)}
      onMouseLeave={() => setLifted(false)}
      onFocus={() => setLifted(true)}
      onBlur={() => setLifted(false)}
      className="overflow-hidden rounded-md border border-(--color-border-strong) bg-(--color-accent-soft) px-5 py-2.5 text-sm text-(--color-fg)"
    >
      <span className="inline-flex">
        {label.split('').map((ch, i) => (
          <span
            key={i}
            className="inline-block transition-transform duration-200 ease-out motion-reduce:transition-none"
            style={{
              transform: lifted ? 'translateY(-2px)' : 'translateY(0)',
              transitionDelay: lifted ? `${i * 25}ms` : `${(label.length - i) * 15}ms`,
            }}
          >
            {ch}
          </span>
        ))}
      </span>
    </button>
  )
}

A small piece of motion you can drop on any CTA. The hover staggers the label characters one by one, short enough not to feel laggy, long enough to read as deliberate.

The ghost variant strips the fill and keeps the same character stagger; useful inside contexts where a solid button would feel heavy.

Related

Live leaderboardmotion
Sortable listaccessibility
Toast queueaccessibility