Star on GitHub
npm install react-driftkit
import { FlickDeck } from 'react-driftkit';
Peek edge
Peek size
28px
Depth fade
0.10/depth
Hover peek
12px
Animation
320ms
Swipe to dismiss
overview
Overview
Stack 2–N cards where each back card peeks out by a configurable amount. Click the peek to flick that card to the front.
details
Details
Peek from any edge — top, bottom, left, or right. Peek size and animation duration/easing are all configurable.
stats
Stats
Cards are React children keyed by id. Controlled or uncontrolled front card, plus optional swipe-to-dismiss for tip-style flows.
credits
Credits
Built on a single CSS grid cell with transforms — no runtime deps, no layout thrash. Style the cards however you'd like.
Hover a peeking card — it nudges out a little further and lifts to full opacity as a click affordance. Click it to flick it forward. Flip the peek edge and size above to reshape the deck: top/bottom cards recede into depth, left/right cards fan out at an angle, and further-back cards fade. Toggle swipe-to-dismiss and drag the front card in the direction opposite the peek to remove it past the threshold.
Prop Type Default
frontId
Controlled id of the front card — matches a child's React key. Omit for uncontrolled.
string —
defaultFrontId
Uncontrolled initial front card id. Falls back to the first child's key if unset.
string —
peek
Which edge the back cards peek from.
'top' | 'bottom' | 'left' | 'right' 'bottom'
peekSize
Pixels of each back card that remain visible behind the card in front of it.
number 24
depthScale
How much each back card shrinks per depth level, for top/bottom peek — makes the stack feel recessed. Set to 0 for a flat stack.
number 0.05
fanAngle
Degrees each back card rotates per depth level, for left/right peek — makes the stack fan out at an angle. Set to 0 for a flat stack.
number 4
depthFade
Opacity subtracted per depth level — further-back cards look more distant. Clamped so no card drops below 0.25. Set to 0 to disable.
number 0.08
hoverPeek
Extra pixels a back card translates out along the peek axis when it is hovered or keyboard-focused. Opacity also snaps back to 1 during the hover to signal clickability. Set to 0 to disable.
number 8
swipeToDismiss
When true, the front card can be pointer-dragged off in the direction opposite the peek to fire on.dismiss.
boolean false
dismissThreshold
Fraction of the card's axis size the drag must cross to count as a dismiss. Values are clamped to [0, 1].
number 0.3
animation
Override the transition used for the flick and swipe animations — duration (ms) and easing (CSS easing).
FlickDeckAnimation —
animation.duration
Transition duration in milliseconds.
number 320
animation.easing
CSS easing function applied to transform and opacity transitions.
string 'cubic-bezier(0.22, 1, 0.36, 1)'
on
Event handlers: frontChange, dismiss. Both optional.
FlickDeckEvents —
on.frontChange
Fires whenever the front card changes — click, keyboard activation, or setter.
(id: string) => void —
on.dismiss
Fires when the front card is swiped past dismissThreshold. Consumer is expected to remove that child from children.
(id: string) => void —
className
CSS class added to the deck container.
string ''
style
Inline styles merged onto the deck container.
CSSProperties —
cardClassName
CSS class added to every card wrapper.
string ''
cardStyle
Inline styles merged onto every card wrapper.
CSSProperties —
children
Each child must have a unique key — that key is the card's id.
ReactNode —

The deck lays its cards out in a single CSS grid cell and offsets back cards via transform, so the container auto-sizes to the largest card plus padding for the peek. Each card wrapper exposes data-flick-deck-card, data-flick-deck-front, data-flick-deck-active (hovered/focused back card), and data-flick-deck-depth so you can drive styles from CSS without re-rendering.

tsx
import { useState } from 'react';
import { FlickDeck } from 'react-driftkit';

function App() {
  const [frontId, setFrontId] = useState('overview');

  return (
    <FlickDeck
      frontId={frontId}
      peek="bottom"
      peekSize={28}
      on={{ frontChange: setFrontId }}
    >
      <Card key="overview" title="Overview">...</Card>
      <Card key="details"  title="Details">...</Card>
      <Card key="stats"    title="Stats">...</Card>
    </FlickDeck>
  );
}
typescript
type FlickDeckPeek = 'top' | 'bottom' | 'left' | 'right';

interface FlickDeckProps {
  // Controlled / uncontrolled front card. The id is the child's React `key`.
  frontId?: string;
  defaultFrontId?: string;

  // Which edge the back cards peek from, and how much of each is visible.
  peek?: FlickDeckPeek;        // default 'bottom'
  peekSize?: number;           // default 24 (px)

  // Back cards shrink along the peek axis (top/bottom), fan out at an angle
  // on the peek axis (left/right). Set either to 0 for a flat stack.
  depthScale?: number;         // default 0.05 (5% smaller per depth level)
  fanAngle?: number;           // default 4 (degrees per depth level)

  // Further-back cards fade. Hovered/focused back card peeks out a little
  // more and snaps to full opacity as a click affordance. Set to 0 to disable.
  depthFade?: number;          // default 0.08 (opacity per depth level)
  hoverPeek?: number;          // default 8 (extra px on hover/focus)

  // When true, the front card can be dragged off in the direction opposite
  // of `peek` to fire `on.dismiss`. Off by default.
  swipeToDismiss?: boolean;
  dismissThreshold?: number;   // fraction of card axis — default 0.3

  animation?: {
    duration?: number;         // default 320 (ms)
    easing?: string;           // default 'cubic-bezier(0.22, 1, 0.36, 1)'
  };

  on?: {
    frontChange?: (id: string) => void;
    dismiss?: (id: string) => void;
  };

  className?: string;
  style?: CSSProperties;
  cardClassName?: string;
  cardStyle?: CSSProperties;

  // Each child must have a unique `key` — that key is the card's id.
  children?: ReactNode;
}

Enjoying react-driftkit?

Star the repo on GitHub to help more devs discover it.

Star on GitHub