Split Flap

A departure board that flaps through its glyphs, one cell behind the last, until it lands on the value.

Loading preview

Installation

npx shadcn@latest add @loomui/split-flap

Usage

import { SplitFlap } from "@/components/ui/split-flap"
<SplitFlap value="LISBON" />

Give it a string. Every cell starts blank, walks forward through the charset, and stops on its letter. Change value and the board flaps from wherever it is to wherever it is going, so nothing ever jumps.

The flap

Each cell is four halves. Two are settled: the top already carries the new glyph and the bottom still carries the old one. The other two are the flap itself, hinged on the seam across the middle. The outgoing face falls away from it, then the incoming face swings down onto it.

The seam is not decoration. Without a visible hinge the turn reads as a fade rather than a card falling, which is the whole reason to use a board instead of a crossfade.

Timing

<SplitFlap value="OSAKA" interval={62} stagger={2} />

interval is the milliseconds a single flap takes, and also the gap between flaps, so a cell mid-run never shows a still frame. stagger is how many flaps of head start each cell hands to the one after it, the ripple down the board.

Longer words take longer, because a cell only ever moves forward through the charset. Landing on A from Z means walking the whole set, which is exactly what the real thing does.

Charset and width

<SplitFlap value="B12" charset=" 0123456789ABCDEFGH" padTo={3} />

charset is every glyph a cell can show, in the order it flaps through them. Anything in value that is not in the charset is set outright rather than walked to, so a stray character costs a flap rather than a stall.

padTo holds a minimum number of cells, which keeps a board from resizing when a shorter value arrives. Cells past the value flap back to blank.

Under prefers-reduced-motion the board is set to its value on the spot, with no walk and no flap.

Props

PropTypeDefaultDescription
valuestringrequiredThe string the board settles on.
charsetstringboard glyphsEvery glyph a cell can show, in flap order.
intervalnumber62Milliseconds per flap.
staggernumber2Flaps of head start between neighbouring cells.
padTonumbernoneMinimum number of cells.
classNamestringnoneMerged onto the board. Font size goes here.

Any other <div> prop is forwarded.

Accessibility

The board is a single role="img" labelled with value, and every cell is aria-hidden. Reading the cells out would spell the word one letter at a time, and mid-flap it would spell nonsense.