A non-invasive layer on Tweakpane v4: localStorage persistence, a draggable/resizable panel, persistent folds, and a save/apply/export presets menu.
Driftpane is a small, non-invasive layer on top of Tweakpane v4 that turns a debug panel into a real, persistent control surface — without touching the Tweakpane core.
One call on an existing Pane adds four things:
- State persistence — control values and the
expandedstate of the pane are saved tolocalStorageand restored on reload. - Draggable, resizable, persistent panel — the pane is wrapped in a
position: fixedcontainer you can drag by its title bar (mouse + touch), clamped to the viewport. A right-edge handle resizes the width. Position and width persist. - Persistent open/closed folders (nested too) — the
expandedstate of the pane and of every folder/tab, at any depth, survives a reload. - Presets — save / apply / list / rename / export / import named snapshots of the state, via a dedicated folder auto-injected as the last entry of the pane.
Driftpane uses only Tweakpane's public API (exportState / importState,
element, addFolder / addButton / addBlade, on('change') /
on('fold')). No core file is modified — so this fork stays cleanly mergeable
with upstream Tweakpane (see below).
📦 The package and its full documentation live in
driftpane/.
npm install @niccolofanton/driftpane tweakpanetweakpane is a peer dependency (Tweakpane v4, tweakpane@^4.0.0): install
it alongside Driftpane.
import {Pane} from 'tweakpane';
import {createDriftpane} from '@niccolofanton/driftpane';
const params = {speed: 0.5, color: '#1e1e1e'};
const pane = new Pane({title: 'Parameters'});
pane.addBinding(params, 'speed', {min: 0, max: 1});
pane.addBinding(params, 'color');
// Build the pane first, then enable all four features in one call.
const panel = createDriftpane(pane, {
storageNamespace: 'demo',
draggable: true,
presetsEnabled: true, // injects the "Preset" folder as the LAST entry
clampToViewport: true,
});
// Programmatic API:
panel.savePresetAs('Dark');
panel.applyPreset('<id>');
panel.resetState(); // clears persisted state (not presets nor position)ℹ️
importState()does not re-fire your bindingchangehandlers. If your app applies side effects in those handlers, call your ownapplyAll()(andpane.refresh()) right aftercreateDriftpaneso the restored values take effect on first paint.
An "Apple-minimal / frosted glass" skin ships with the package — dark by default,
light via data-theme="light":
import '@niccolofanton/driftpane/theme.css';For the exact look, load the Geist fonts:
<link
href="https://fonts.googleapis.com/css2?family=Geist:wght@300..700&family=Geist+Mono:wght@400;500&display=swap"
rel="stylesheet"
/>This repository is a fork of cocopon/tweakpane:
the entire Driftpane layer lives in driftpane/ and never touches
the core, so pulling upstream stays conflict-free.
cd driftpane
./update-tweakpane.sh # fetch upstream/main and realign the demo CDN pin
./update-tweakpane.sh v4.1.0 # or a specific release/branchFull docs (architecture, the preset model, localStorage keys, the core
positional-import constraint, demo instructions) are in
driftpane/README.md.
MIT. Tweakpane itself is © cocopon, also MIT.