KQ-1124 · STORY/CUBE

Story Cube

A stories viewer on a real CSS 3D cube inside its own phone-shaped screen: a tap on the right steps forward and on the left steps back, a held press pauses, and the bars fill and advance on their own. A sideways swipe turns the cube to the next person 1:1 under the finger, projects the throw and lands on the glide spring with the release velocity, each face darkening as it turns away; a downward swipe lifts and shrinks the frame, and let go far enough it folds into a circle and glides back into that person's avatar in the tray, otherwise it snaps back. The viewer is a focusable group whose arrow keys step, Shift with them turns, Space pauses and Escape closes, with the same tick per story and whoosh per turn.

KQ-1124 · STORY/CUBE

Waylight · Trips

  • Mara
  • Ines
  • Tomas
  • Oskar
MMara2h

Up before the sun on Coldbrook ridge.

Tap the right of the picture for the next story and the left for the previous one, and hold to pause. Swipe sideways to turn to another person and down to close. Left and Right step through stories, Shift with Left or Right turns to the previous or next person, Space pauses and Escape closes.

watching mara · 1 of 3 · tap or swipe

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/story-cube

Props

people
StoryCubePerson[]
default —
Everyone with stories, in the order the cube turns through them: { id, name, posted?, stories: { id, label, content, caption? }[] }. A story's label is its picture's accessible name; content is the picture.
value / defaultValue
{ person: string; story: number }
default { person: <first id>, story: 0 }
Whose story is on show, by person id, and which of theirs. Controlled or uncontrolled; the cube turns when the person changes, whoever changed it.
onValueChange
(value: StoryCubeValue) => void
default —
Fires from the tap, swipe, key or autoplay that moved it, with the new position.
open / defaultOpen
boolean
default false
Whether the viewer is open over the tray. Opening grows it out of the person's avatar; closing folds it back in. A refused close springs back.
onOpenChange
(open: boolean) => void
default —
Fires from the swipe, key, button, tray face or end of the last story that opened or closed the viewer.
duration
number
default 5
Seconds each story plays before the next one, 2 to 10.
depth
number
default 0.5
How deep the cube reads when it turns, 0 to 1: the perspective runs from four frame widths (almost flat) to 1.1 (steep), and the turning face darkens more.
bars
"split" | "single" | "ring"
default "split"
How progress is drawn: a bar per story, one bar notched at each story, or arcs around the avatar in the header.
autoplay
boolean
default true
Stories play on their own and advance when their bar is full. Off, each waits for a tap and its bar shows full to mark the place.
label
string
default "Stories"
The tray's name, shown above it and used as its accessible name.
children
ReactNode
default —
The screen under the tray: the host's feed, shown while the viewer is closed.
sound
boolean
default false
A tick for every story a tap, key or open starts, climbing the scale, and a whoosh when a swipe, key or tap turns the cube (a low one when it closes). Automatic advances are silent. Nothing plays, and no audio context exists, unless this is on.
disabled
boolean
default false
Dims the screen, stops the bars, and ignores taps, swipes and keys.
className
string
default —
Merged onto the screen's classes; the screen is w-full at 9:16 unless an aspect class here says otherwise.