WebberUI

Rolling Text

Characters roll and flip vertically on hover, with per-character or per-word splitting, direction control, and controlled mode.

Loading preview…
npx shadcn@latest add https://webberui.com/r/rolling-text.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

0.025
0.4
<RollingText />

Installation

npx shadcn@latest add https://webberui.com/r/rolling-text.json

Or, once registries are configured in components.json, install it as @webberui/rolling-text.

Usage

import { RollingText } from "@/components/ui/rolling-text";

<RollingText text="HOVER TO ROLL" className="text-4xl font-bold" />

Controlled mode (parent-triggered)

Inside a link or button, bind active to the parent's hover/focus so keyboard users can trigger the roll too:

const [active, setActive] = React.useState(false);

<a
  href="/products"
  onMouseEnter={() => setActive(true)}
  onMouseLeave={() => setActive(false)}
  onFocus={() => setActive(true)}
  onBlur={() => setActive(false)}
>
  <RollingText text="Products" active={active} />
</a>

Props

PropTypeDefaultDescription
textstringThe text to roll and flip
by"char" | "word""char"Split unit: per character or per word
direction"up" | "down""up"Roll direction: which way the original rolls out and the copy rolls in
staggernumber0.025Delay between adjacent units (seconds), producing a left-to-right wave
durationnumber0.4Duration of a single unit's roll (seconds)
activebooleancontrolled mode: the flip state is decided from outside; when omitted, the component manages it internally and triggers on hover
asReact.ElementType"span"The element tag to render
classNamestringAppended to the root element's className

How it works

  • Each unit is made of two text layers: the original and its copy swap places inside a clip-path: inset(0) clipping box, so everything outside the glyph box stays invisible
  • Clipping uses clip-path rather than overflow: hidden: the latter moves an inline-block's baseline to the bottom edge of the box, which vertically misaligns it against adjacent inline text
  • Splitting is done on graphemes, so emoji, flags, and combining characters are never broken apart; whitespace does not participate in the roll, keeping line width and line breaking stable
  • The clipping box height equals the line height. With a very small line height (such as leading-none) descenders may get clipped, so a line-height of at least 1.2 is recommended

Accessibility

  • When the user has "reduce motion" enabled at the system level, the text renders statically
  • The complete text is exposed sr-only to assistive technology and the entire rolling layer is aria-hidden, so the screen reader announces a whole sentence rather than individual characters
  • uncontrolled mode responds to mouse hover only; to make keyboard focus trigger it too, switch to controlled mode and bind active to the focus/blur of a focusable parent element

On this page