Arc Scroll

Cards that travel along a semicircle as you scroll, entering at the bottom right and leaving at the bottom left.

  • Scroll
  • Cards
  • Motion

Uses motion, clsx, tailwind-merge

Open in ChatGPTOpen in Claude

Scroll inside the box

Installation

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

The CLI writes components/ui/arc-scroll.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/arc-scroll

Examples

Finished sections built with Arc Scroll. Each one installs whole, with the component included.

Case Studies

A portfolio hero: project cards on the arc, with a pinned headline and a caption for the project at the top.

Timeline

A company history with tighter spacing, the current year highlighted and a year rail that follows along.

Testimonials

Wide quote cards with tilt turned off so the text stays level, plus page dots.

Props

children

TypeReact.ReactNode

The cards. Each child is placed on the arc.

spacing

TypenumberDefault18

Degrees between neighboring cards along the arc.

radius

Typenumber

Arc radius in pixels. Defaults to 75% of the stage width, capped at 900.

scrollPerCard

TypenumberDefault320

Pixels of scroll it takes to move one card along the arc.

tilt

TypebooleanDefaulttrue

Rotate each card to follow the curve. Turned off for reduced motion.

containerRef

TypeReact.RefObject<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 here.

cardClassName

Typestring

Classes for the wrapper around every card.

apexGap

TypenumberDefault16

Minimum gap in pixels between two cards as they trade places at the top. Cards spread apart near the top to make room, so the overlap never flips suddenly.

overlay

TypeReact.ReactNode

Content pinned inside the stage, behind the cards (headlines, captions, progress). Position it with absolute classes.

onActiveChange

Type(index: number) => void

Called with the index of the card nearest the top whenever it changes.

className

Typestring

Extra classes for the outer section.