A tree-shakable React component library for floating UI: draggable widgets, an edge-pinned dock, a draggable bottom sheet, an N-pane resizable split pane, an image magnifier (zoom lens), and a peek-and-flick card stack.
Unstyled, TypeScript-first, zero dependencies, React 18 & React 19.
Building a chat widget, floating toolbar, debug panel, or side dock? You want these things to be draggable, stay on screen, and stay out of your way stylistically. Most draggable libraries are either too heavy, too opinionated, or don't handle edge cases like viewport resizing, touch input, and orientation changes.
react-driftkit ships each pattern as its own tiny component. Import only what you use — every component is tree-shakable and under a few KB gzipped. All visuals are yours; the kit owns positioning and interaction.
| Component | What it does |
|---|---|
<MovableLauncher> |
React draggable floating widget — pin a chat bubble, AI assistant, debug panel, or toolbar to any viewport corner with snap-on-release. |
<SnapDock> |
React floating dock / edge-pinned toolbar — slides along any side of the viewport and flips orientation between horizontal and vertical. |
<DraggableSheet> |
React bottom sheet & pull-up drawer — peek, half, full snap points, velocity-aware release. Mobile-style filters, cart drawers, detail panels. |
<ResizableSplitPane> |
React resizable split pane — N-pane IDE-style layout with draggable handles, min/max constraints, and localStorage-persisted ratios. |
<ZoomLens> |
React image magnifier / zoom lens — free-drag over the whole page or scope to one element (product-image zoom). Wheel to zoom, Escape to dismiss. |
<FlickDeck> |
React card stack & swipeable cards — back cards peek from one edge, click the peek to flick it forward; optional swipe-to-dismiss. |
npm install react-driftkityarn / pnpm / bun
yarn add react-driftkitpnpm add react-driftkitbun add react-driftkitEvery component is available two ways — both tree-shake to the same code.
// 1. Barrel import — grab multiple components at once
import { MovableLauncher, SnapDock, ZoomLens } from 'react-driftkit';
// 2. Per-component subpath — named or default, pick the style you prefer
import { SnapDock } from 'react-driftkit/SnapDock';
import SnapDock from 'react-driftkit/SnapDock';Subpath imports are handy when you only need one component and want the import line to be explicit about bundle cost (your bundler will tree-shake the barrel import down to the same output either way, as long as sideEffects is respected — which it is here).
import { useRef } from 'react';
import {
MovableLauncher,
SnapDock,
DraggableSheet,
ResizableSplitPane,
ZoomLens,
FlickDeck,
} from 'react-driftkit';
function App() {
const productRef = useRef<HTMLImageElement>(null);
return (
<>
{/* Draggable corner widget that snaps to the nearest viewport corner. */}
<MovableLauncher defaultPosition="bottom-right" snapToCorners>
<button>Chat with us</button>
</MovableLauncher>
{/* Edge-pinned dock that flips between horizontal and vertical on drop. */}
<SnapDock defaultEdge="bottom" shadow>
<button>Home</button>
<button>Search</button>
<button>Settings</button>
</SnapDock>
{/* Pull-up sheet with peek / half / full snap points and flick gestures. */}
<DraggableSheet snapPoints={['peek', 'half', 'full']} defaultSnap="half">
<div data-handle className="sheet-handle" />
<div className="sheet-body">Details, filters, cart…</div>
</DraggableSheet>
{/* N-pane resizable split layout with persisted ratios. */}
<ResizableSplitPane defaultSizes={[0.3, 0.7]} persistKey="app-split">
<Sidebar />
<MainContent />
</ResizableSplitPane>
{/* Magnifier scoped to one element — hover to zoom. */}
<img ref={productRef} src="/product.jpg" alt="Product" />
<ZoomLens defaultActive target={productRef} defaultZoom={3} />
{/* Stack of cards — click a peeking card to flick it to the front. */}
<FlickDeck defaultFrontId="overview" peek="bottom" peekSize={28}>
<div key="overview">Overview</div>
<div key="details">Details</div>
<div key="stats">Stats</div>
</FlickDeck>
</>
);
}All components are tree-shakable — import only what you use.
Full API, examples, and live demos are on the website. Each section below is a short pitch — click through for the detailed docs.
A draggable floating wrapper that lets users pick up any widget and drop it anywhere on the viewport — or snap it to the nearest corner on release.
- Drag anywhere with pointer events (mouse, touch, pen)
- Named or custom positioning —
'top-left','bottom-right', or{ x, y } - Optional snap-to-corners with a bounce-animated release
- Click vs. drag threshold (5 px) so nested buttons still fire
import MovableLauncher from 'react-driftkit/MovableLauncher';
<MovableLauncher defaultPosition="bottom-right" snapToCorners>
<button>Chat with us</button>
</MovableLauncher>Full API, more examples, and live demo → https://react-driftkit.saktichourasia.dev/movable-launcher
An edge-pinned dock that slides along any side of the viewport. Drop it anywhere — on release it snaps to the nearest edge and flips between horizontal (top/bottom) and vertical (left/right) layouts via a FLIP-style animation anchored to the active edge.
- Edge pinning —
left,right,top,bottom, with a0..1offset along the edge - Automatic orientation —
flex-directionfollows the active edge - Unstyled —
data-edgeanddata-orientationattributes let you drive CSS without re-rendering
import SnapDock from 'react-driftkit/SnapDock';
<SnapDock defaultEdge="bottom" shadow>
<button>Home</button>
<button>Search</button>
<button>Settings</button>
</SnapDock>Full API, more examples, and live demo → https://react-driftkit.saktichourasia.dev/snap-dock
A pull-up / pull-down sheet pinned to an edge, with snap points like peek, half, and full. Built for mobile detail drawers, filter panels, and dev-tool flyouts — but works at any edge on any screen size.
- Named presets + raw values — mix
'peek','half','full', pixel numbers, and'40%'strings in onesnapPointslist - Any edge —
bottom(default),top,left, orright; percentages resolve against the drag axis - Velocity-aware release — a fast flick advances one stop; slow drags snap to the nearest
- Drag handle selector — confine drag to a nested handle so inner content stays scrollable
import DraggableSheet from 'react-driftkit/DraggableSheet';
<DraggableSheet snapPoints={['peek', 'half', 'full']} defaultSnap="half">
<div data-handle className="sheet-handle" />
<div className="sheet-body">Details, filters, cart...</div>
</DraggableSheet>Full API, more examples, and live demo → https://react-driftkit.saktichourasia.dev/draggable-sheet
An N-pane resizable split layout. Drag the handles between panes to redistribute space. Supports horizontal and vertical orientations, min/max constraints, localStorage persistence, and a render prop for fully custom handles.
- N panes — pass 2 or more children; each adjacent pair gets a handle
- Single handle render prop — called per boundary with
{ index, isDragging, orientation } - Persisted layout —
persistKeysaves split ratios to localStorage across sessions - Double-click reset to
defaultSizes
import ResizableSplitPane from 'react-driftkit/ResizableSplitPane';
<ResizableSplitPane defaultSizes={[0.3, 0.7]} persistKey="app-split">
<Sidebar />
<MainContent />
</ResizableSplitPane>Full API, more examples, and live demo → https://react-driftkit.saktichourasia.dev/resizable-split-pane
A draggable magnifier circle that zooms into whatever it hovers. Great for design review, image inspection, or dense data tables. Drag it anywhere on the page, scroll to change zoom, or pass a target to scope it to a single element (product-image-zoom style — hover the element to magnify, leave to hide).
- Two modes — free-drag over the whole page, or
target-scoped (lens follows cursor inside one element, hides on leave) - Live DOM clone — mirrors the real page in real time; no snapshot staleness, no external deps
- Wheel to zoom, hotkey + Escape, and controlled/uncontrolled
active+zoom - Ignore rules — strip elements from the clone via
behavior.ignoreSelectoror[data-zoom-lens-ignore]
import ZoomLens from 'react-driftkit/ZoomLens';
<ZoomLens
defaultActive
target={imageRef} // omit for a free-drag whole-page lens
defaultZoom={3}
size={200}
/>Full API, more examples, and live demo → https://react-driftkit.saktichourasia.dev/zoom-lens
A stack of cards where each back card peeks from one edge — receding into depth for top/bottom peek, fanning out at an angle for left/right. Click the peek to flick that card to the front, or optionally swipe the front card off to dismiss it. Great for toggles between views, tip stacks, onboarding cards, and side-by-side comparisons.
- Four peek edges —
top,bottom,left,right; each child's Reactkeyis its card id - Depth cues you can tune —
depthScaleshrinks back cards,fanAnglerotates them,depthFadedims them, or set any to0for a flat stack - Controlled or uncontrolled — drive the front card via
frontId+on.frontChange, or just passdefaultFrontId - Optional swipe-to-dismiss — drag the front card off in the opposite direction of the peek to fire
on.dismiss(id) - Unstyled —
data-flick-deck-front,data-flick-deck-depth, anddata-flick-deck-activeattributes let you drive CSS without re-rendering
import FlickDeck from 'react-driftkit/FlickDeck';
<FlickDeck defaultFrontId="overview" peek="bottom" peekSize={28}>
<Card key="overview">Overview</Card>
<Card key="details">Details</Card>
<Card key="stats">Stats</Card>
</FlickDeck>Full API, more examples, and live demo → https://react-driftkit.saktichourasia.dev/flick-deck
- Chat widgets — floating support buttons that stay accessible
- Floating toolbars — draggable formatting bars or quick-action panels
- Side docks — VS Code / Figma-style side rails that snap to any edge
- Mobile detail sheets — pull-up drawers for details, filters, or carts
- Code editors — resizable file tree + editor + preview split layouts
- Admin dashboards — adjustable sidebar and content regions
- Debug panels — dev tool overlays that can be moved out of the way
- Media controls — picture-in-picture style video or audio controls
- Notification centers — persistent notification panels users can reposition
- Accessibility helpers — movable assistive overlays
- Product image zoom — magnifier scoped to a single image, follows the cursor, hides on leave
- Data table inspection — drag a magnifier over dense tables, charts, or heatmaps to read small values without re-flowing the page
- Tip / onboarding stacks — a deck of coachmark cards the user can flick through and swipe off one by one
- Comparison decks — toggle between product plans, chart variants, or before/after states with a single click on the peek
If you're searching for any of these, react-driftkit has a primitive for it:
- React draggable component / react draggable widget →
MovableLauncher - React chat widget / floating support button →
MovableLauncher+ your UI - React floating dock / react sidebar / react vertical toolbar →
SnapDock - React bottom sheet / react pull-up drawer / mobile sheet →
DraggableSheet - React resizable split pane / react split view / alternative to allotment, react-split-pane, react-resizable-panels →
ResizableSplitPane - React image magnifier / react product zoom / ecommerce hover zoom →
ZoomLens - React card stack / swipeable cards / alternative to react-tinder-card →
FlickDeck
Each one is independently importable, ships unstyled, and works with React 18 and React 19.
Under the hood all components use the Pointer Events API for universal input handling. MovableLauncher, SnapDock, and DraggableSheet render as position: fixed elements at the top of the z-index stack (2147483647) and use a ResizeObserver to stay pinned when their content changes size.
SnapDock's orientation flip uses a FLIP-style animation: it captures the old wrapper rect before the orientation changes, applies an inverse scale() anchored to the active edge, and animates back to identity in the next frame — so the dock glides between horizontal and vertical layouts instead of snapping.
ResizableSplitPane uses a flexbox layout with calc() sizing. Dragging a handle only redistributes space between the two adjacent panes, leaving all others unchanged. Window resize events trigger re-clamping against min/max constraints.
ZoomLens live-clones either document.body (free mode) or a target element (target mode) into a portalled host, then applies a translate() + scale() transform so the point under the lens center maps to target-local — or document — coords. A MutationObserver rebuilds the clone when the real DOM changes, debounced to 150 ms and skipped during drag. In target mode, pointer tracking attaches directly to the target, and the lens overlay is pointer-events: none so hover state keeps passing through to the real element underneath.
FlickDeck lays every card in a single CSS grid cell so the container auto-sizes to the largest card, then offsets back cards with pure transform — translate along the peek axis, plus scale (top/bottom peek) or rotate around the attached edge (left/right peek). Depth fade uses opacity. The front card's key is its id; a click or keyboard activation on a peek swaps ids with a CSS transition. Swipe-to-dismiss tracks pointer movement on the front card and fires on.dismiss(id) once the drag crosses dismissThreshold in the direction opposite the peek, leaving the consumer to remove that child.
Contributions are welcome. Open an issue or send a pull request.
git clone https://github.com/shakcho/react-driftkit.git
cd react-driftkit
npm install
npm run dev # Start the demo app
npm test # Run the test suiteMIT © Sakti Kumar Chourasia