Playhead Reel

A whole archive in one thin strip, with a playhead that walks along it as you scroll. Whatever's under the playhead opens into a loose collage, and the ruler below is a scrubber you can drag.

  • Gallery
  • Scroll
  • Timeline

Uses motion, clsx, tailwind-merge

Inspired by Maria Vasilyeva

Open in ChatGPTOpen in Claude

Selected work · 01 / 06

Halden House

Architecture · 2025

View case study

Installation

Add the component on its own with the CLI, or copy the file in by hand.

The CLI writes components/ui/playhead-reel.tsx and installs motion, clsx and tailwind-merge. It needs the @keyframeui registry in components.json; add it once per project.

npx shadcn@latest add @keyframeui/playhead-reel

Examples

Finished sections built with Playhead Reel. Each one installs whole, with the component included.

Changelog

Six releases as live UI mockups rather than screenshots, so they stay sharp and follow your theme. The header shows the version and date, and a caption for the screen under the playhead. Tab to the playhead and use the arrow keys: Shift jumps a release.

Changelogv2.0
January 2025

A faster editor

Pages open instantly, even with thousands of blocks.

Travel Journal

A year of trips, one group each, in portrait and landscape. Each photo has its own caption, and the trip's dates and a day count sit above.

Journal · 2025Trip 1 of 5

Lisbon & Porto

Mar 3 – 11

Day 1Tram 28 through Baixa

Props

groupsRequired

TypePlayheadReelGroup[]

What's on the reel, in order. Each group has a label for the ruler ("v2.4", "2019", "Lisbon") and its items. Groups sit apart in the strip, and the playhead pauses briefly in each gap.

src

Typestring

PlayheadReelItem: image URL. Images are fetched once the reel comes within a screen of the viewport.

alt

Typestring

PlayheadReelItem: alt text. The playhead handle also reads it out as it reaches the item.

aspect

TypenumberDefault4 / 3

PlayheadReelItem: width ÷ height. Sets how wide the item is in the strip and the shape it opens to. Mixing shapes gives the strip its rhythm.

content

TypeReactNode

PlayheadReelItem: your own markup instead of an image, laid out at the item's open size and scaled down with it. Use it for UI mockups, cards or video.

thumbHeight

TypenumberDefault40

Height of an item in the strip, in px.

scrollPerItem

TypenumberDefault80

Pixels of scroll it takes to move the playhead one item along.

reach

TypenumberDefault2.5

Roughly how many items on each side of the playhead open up. Lower keeps the collage tight; higher opens more of the strip at once.

skim

TypebooleanDefaulttrue

Fold the collage flat into the strip while scrolling fast, so you can see where you're going, and open it again when you slow down. Off with reduced motion.

header

TypeReactNode

Pinned above the reel, in the flow, so the collage never covers it. Put titles, counters and links here, driven by onActiveChange.

footer

TypeReactNode

Pinned below the ruler, in the flow.

onActiveChange

Type(active: { group: number; item: number }) => void

Called with the group and item under the playhead whenever they change, read from the smoothed position so it matches what's on screen. item counts from 0 within its group.

containerRef

TypeRefObject<HTMLElement | null>

Scroll container to track. Defaults to the window. The container must be positioned (e.g. relative).

stageClassName

TypestringDefault"h-dvh"

Classes for the pinned stage. Set its height and text color here: the playhead, ruler and labels are drawn in the text color.

itemClassName

Typestring

Classes for every item's frame, which defaults to rounded-lg with a soft shadow. Hovering an open item lifts it to the top of the pile.

className

Typestring

Extra classes for the outer section. Other section props pass through.