logo

Aki UI

アキ ウイ

Scroll Stack

スクロールスタック

Cards pin to the top as you scroll and the ones behind recede, turning a flat list into a deck with depth. Each card reads its own slice of the container's scroll progress, so the stacking is driven by position rather than by a timer. Built with Framer Motion useScroll and Tailwind CSS.

Collect

Every card starts at its natural size, laid out in a plain vertical flow.

Pin

As it reaches the top of the viewport it sticks, offset just enough to leave the one before it visible.

Recede

Cards already pinned scale down as later ones arrive, so depth reads as distance.

Settle

The last card never shrinks — it lands at full size and closes the stack.

Installation

Install the following dependencies:1

npm install framer-motion

Copy and paste the following code into a new component file:2

"use client"

import { motion, useScroll, useTransform, type MotionValue } from "framer-motion"
import { useRef, type ReactNode } from "react"

export type ScrollStackEntry = {
id: string | number,
content: ReactNode
}

How to use it3

"use client"

import { ScrollStack } from "@/components/ui/scroll-stack"

const items = [
{ id: 1, content: <div>Collect</div> },
{ id: 2, content: <div>Pin</div> },
{ id: 3, content: <div>Recede</div> },
{ id: 4, content: <div>Settle</div> },
]

Props

ScrollStack

NameDefaultDescription
items

ScrollStackEntry[]

The cards to stack, each with an id and its content

height420

number

Height of the scrollable viewport in pixels

peek16

number

Pixels of each pinned card left visible below the one above it

step0.04

number

How much a card shrinks for each card stacked after it

tilt0

number

Degrees each card rotates as it recedes, alternating sign to fan the deck

darkfalse

boolean

Render the dark palette

ScrollStackEntry (type)

NameDefaultDescription
id

string | number

Stable key for the card

content

ReactNode

What the card renders