Sync Grid

A grid of videos on one clock. Scrub any card and the rest follow it to the same moment. Stack them to flip between versions of a single frame, or drop one onto another to compare them side by side.

  • Video
  • Comparison
  • Scrubbing
  • Motion

Uses motion, clsx, tailwind-merge

Open in ChatGPTOpen in Claude
linear
Linear
cubic-bezier(0.22, 1, 0.36, 1)
Ease out
cubic-bezier(0.65, 0, 0.35, 1)
Ease in-out
spring · bounce 0.3
Spring

0.00s

Installation

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

The CLI writes components/ui/sync-grid.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/sync-grid

Examples

Finished sections built with Sync Grid. Each one installs whole, with the component included.

Model Output

One prompt, four fictional video models. The clips run 7 to 21 seconds and line up by percentage, so halfway is halfway in all of them. Stack them and move up and down to flip between outputs on the same frame.

A paper boat drifting down a rain-soaked street

38s$0.09
Bramble 2
1m 12s$0.31
Fernlight XL
54s$0.18
Quarry
27s$0.06
Nimbus v4

0.00s

Render Quality

One clip in four cards at 1/8, 1/4, 1/2 and full resolution. It decodes once and is drawn into each card, so all four always show the same frame. Drag Final onto Draft and switch to Difference: only the detail the draft lost lights up.

Shot 04 · Lakeside · four render passes

Drag Final onto Draft, then switch to Difference

160 × 900.4s / frame
Draft
320 × 1801.1s / frame
Preview
640 × 3604.8s / frame
High
1280 × 72019s / frame
Final

0.00s

Redesign

Three versions of one product's site, built from markup, each scrolling with the clock while the same visitor moves and clicks through it. Drag 2026 onto 2019 and slide the divider: the old and new layouts sit at the same scroll point.

tidely.app · three redesigns, one visit

Drag 2026 onto 2019 to compare

tidely.app
tidelyFeaturesPricingBlogSign up

The smarter way to manage your team's calendar

Start free trial

Everything you need

Smart syncTeam viewReminders

Simple pricing

Free$0Choose planTeam$8Choose planBusiness$16Choose plan
© 2019 Tidely Inc.
2019
tidely.app
tidelyProductPricingLog in
New · AI scheduling

Calendars that think ahead

Get startedWatch demo
Auto-scheduleFocus timeTime zonesInsights
Starter$0Get startedTeam$12Start trial
© 2023 Tidely
2023
tidely.app
TidelyProductPricingSign in →
Calendar, rethoughtTime, held together.Try Tidely
Trusted by
What it does01Plans the week02Guards focus03Speaks every zone
PricingPersonalFreeTeam$10Choose
Tidely, 2026Made with care
2026

0.00s

Props

columns

TypenumberDefault2

Cards per row.

duration

Typenumber

Length of the timeline in seconds. Drives render items and playback. Videos of any length are matched to it by percentage, so a 10s and a 30s clip both reach their middle at the timeline's middle. Defaults to the first video's length, or 4 with no video.

fps

TypenumberDefault30

Frame rate of the timeline: the step for ← and → (Shift: ten frames), and the frame every card lands on together once scrubbing stops.

aspect

TypenumberDefault16 / 9

Width ÷ height of every card. Videos and sprites cover it.

ripple

TypebooleanDefaulttrue

Followers chase the card you're scrubbing on springs that slow with each cell of distance, so time travels outward like a wave, and all land on the same frame within 150ms of the pointer stopping. Off with reduced motion.

stackable

TypebooleanDefaulttrue

Show the Stack button. Stacked, the cards fan into one frame: x scrubs, y (or ↑ ↓, or the list beside it) picks the variant in front, which swaps in a 100ms crossfade.

comparable

TypebooleanDefaulttrue

Drag a card onto another (long-press on touch, or the Compare button on a focused card) to merge them into one frame with a draggable divider, or into Difference, where identical pixels go black.

hint

TypebooleanDefaulttrue

The first time the grid scrolls into view, a ghost cursor sweeps the first card once. Cancelled by any pointer or key; skipped with reduced motion.

onTimeChange

Type(seconds: number, progress: number) => void

Called whenever the clock moves, with the time in seconds and as 0–1. Runs every frame while playing or scrubbing.

cardClassName

Typestring

Classes for every card, such as its corner radius. Defaults to rounded-xl.

className

Typestring

Extra classes for the outer element. Other div props pass through.

item.labelRequired

Typestring

SyncGridItem: names the card on screen, in the stack's list, and to screen readers.

item.render

Type(progress: MotionValue<number>) => ReactNode

SyncGridItem: draws the card from the clock with no assets. Gets this card's progress, 0–1, as a MotionValue; bind it to motion styles or useTransform so nothing re-renders per frame.

item.src

Typestring

SyncGridItem: a video. Loads once the grid is within a screen of the viewport. Cards that share a src decode it once and draw the same frame.

item.poster

Typestring

SyncGridItem: shown until the video's first frame is ready.

item.sprite

Type{ url: string; columns: number; rows: number; frames?: number }

SyncGridItem: a sheet of preview frames, left to right then top to bottom, used while scrubbing. With src, the video only loads to play.

item.resolution

TypenumberDefault1

SyncGridItem: 0–1. Draws the video at that share of its own pixels (1/8 of 1280 × 720 is 160 × 90) and scales it up pixelated.

item.children

TypeReactNode

SyncGridItem: overlay content such as stat chips, over the media. Position it yourself; it takes no pointer events and hides in Difference view.

item.className

Typestring

SyncGridItem: classes for this card.