Star on GitHub
npm install react-driftkit
import { SnapDock, InspectorBubble } 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