Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@lesetong/cardstack

A fanned card deck with hover fan, spring swipe-to-flip and progress dots, built on motion. Available on npm.

Inspired by HyperOS 4

Features

  • Hover fan — cards 2/3 tilt out behind the front card with progressive blur
  • Bidirectional flip — drag up for the next card or down for the previous card; horizontal-only releases restore without changing the index
  • Force-coupled deck — vertical pulls give the second card a subtle same-direction follow; downward pulls also counterload the backmost card
  • Progress dots — white dot indicator on the right tracks your position in the deck
  • Controlled rolling — optionally animate parent-driven index jumps through every intermediate card
  • Configurable — per-card CardPose (tilt / origin / blur), size variant, radius
  • Fast — three cards at rest, with a short-lived maximum of five during overlapping gestures

Install

npm install @lesetong/cardstack
# or
pnpm add @lesetong/cardstack

Requires react and motion as peer dependencies.

import { CardStack } from '@lesetong/cardstack'
import '@lesetong/cardstack/style.css'

<CardStack
  cards={[<img src="a.jpg" alt="" />, <img src="b.jpg" alt="" />]}
  size="large"
  second={{ rotate: 6 }}
  third={{ rotate: -6, blur: 3 }}
/>

Controlled usage:

import { useState } from 'react'

const [index, setIndex] = useState(0)

<CardStack
  cards={cards}
  index={index}
  animateIndexChanges
  indexDirection="auto"
  onChange={setIndex}
/>

Passing index makes the component controlled. Parent-driven index changes synchronize immediately by default. Enable animateIndexChanges to roll through every intermediate card. indexDirection="auto" takes the shortest circular route; use "forward" or "backward" to force a route. Intermediate visual steps do not call onChange. Omit index for uncontrolled usage; use defaultIndex to choose the initial card.

Gestures

Drag up       → next card
Drag down     → previous card
Drag sideways → restore current card

The front card follows both axes, but only projected vertical movement changes the index. During vertical pulls, the visible second card follows the same x/y direction at a much smaller amplitude. A downward pull also keeps the backmost card in its original z-index and counterloads it. Releasing a committed backward gesture promotes the backmost card and returns the dragged card to the center as the new second slot.

Props

Prop Type Default Description
cards ReactNode[] Card contents, front to back
index number Controlled front-card index (0-based)
defaultIndex number 0 Initial index for uncontrolled usage
animateIndexChanges boolean false Roll through intermediate cards when the parent changes index
indexDirection "auto" | "forward" | "backward" "auto" Route used for animated index changes; auto chooses the shortest circular path
onChange (index: number) => void Called after a user gesture successfully flips a card
size "large" | "small" "large" Variant (dimensions, origins, dots)
width / height number per size Deck size (px)
radius number 14 / 12 Card corner radius (px)
second / third CardPose Pose overrides: { rotate?, origin?, blur? }

Demo

Live Storybook: https://leset0ng.github.io/card-stack/

Dev

nub run dev              # storybook
nub run build            # library build
nub run typecheck        # tsc --noEmit
nub run build-storybook  # static docs site

Releases

Packages

Contributors

Languages