The panel API
tweaks() returns synchronously — panel.el to mount,
panel.params for live values, and the methods on this page. On the
code-split build, a panel that needs lazy modules builds its controls behind
panel.ready; the element, params and methods work immediately.
Events
panel.on(fn) subscribes to every change; the callback gets
the params bag and the key that changed (also available as
params._last). It returns an unsubscribe function. Move things and
watch the feed.
— change something —
const log = target.querySelector(".pa-log");
const lines = [];
const panel = tweaks("Events", {
radius: [12, 0, 40, 1],
mode: ["calm", "wild"],
armed: false,
});
mount.append(panel.el);
panel.on((p, changed) => {
lines.unshift(changed ? `${changed} → ${JSON.stringify(p[changed])}` : "(reset)");
log.textContent = lines.slice(0, 8).join("\n");
});mount is the panel's slot inside the stage; target is the demo surface it controls. In your own page you'd just document.body.append(panel.el).
set & reset
The panel is just another consumer of its own state:
panel.set(key, value) moves a control programmatically (listeners
fire, the UI follows), and panel.reset() restores every default —
the same thing the toolbar's reset button does.
const tile = target.querySelector(".pa-tile");
const panel = tweaks("Remote", {
hue: [260, 0, 360, 1],
lift: [0, 0, 40, 1],
});
mount.append(panel.el);
const apply = (p) => {
tile.style.background = `oklch(0.65 0.2 ${p.hue})`;
tile.style.transform = `translateY(${-p.lift}px)`;
};
panel.on(apply);
apply(panel.params);
target.querySelector(".pa-shuffle").addEventListener("click", () => {
panel.set("hue", Math.round(Math.random() * 360));
panel.set("lift", Math.round(Math.random() * 40));
});
target.querySelector(".pa-restore").addEventListener("click", () => panel.reset());render, disabled & hint
Any object-form control can carry a render predicate
(show/hide on sibling values), a disabled flag or predicate
(gray out + lock), and a hint tooltip behind an ⓘ marker.
Flip “Glow” and watch the other two rows.
const chip = target.querySelector(".pa-chip");
const panel = tweaks("Conditional", {
glow: false,
color: { type: "color", value: "#7C5CFF",
render: (get) => get("glow"), // hidden until glow is on
hint: "Only rendered while glow is on" },
strength: { type: "slider", value: 36, min: 0, max: 90,
disabled: (get) => !get("glow") }, // grayed out instead
});
mount.append(panel.el);
const apply = (p) => {
chip.style.boxShadow = p.glow ? `0 0 ${p.strength}px ${p.color}` : "none";
};
panel.on(apply);
panel.ready.then(() => apply(panel.params));Undo & redo
Pass { undo: true } and the panel keeps a debounced history:
⌘Z / ⇧⌘Z work while the panel is hovered or focused (so it never hijacks the
page's own undo), a continuous drag coalesces into one step, and
panel.undo() / panel.redo() drive it from outside.
const card = target.querySelector(".ud-card");
const panel = tweaks("History", {
width: [160, 60, 260, 1],
round: [18, 0, 50, 1],
}, { undo: true });
mount.append(panel.el);
const apply = (p) => {
card.style.width = `${p.width}px`;
card.style.borderRadius = `${p.round}px`;
};
panel.on(apply);
apply(panel.params);
target.querySelector(".ud-undo-btn").addEventListener("click", () => panel.undo());
target.querySelector(".ud-redo-btn").addEventListener("click", () => panel.redo());Persistence & presets
{ persist: "key" } saves values to localStorage (reload this
page — the panel comes back as you left it) and unlocks presets: a presets menu
appears in the toolbar, and savePreset / loadPreset /
deletePreset / presets() drive the same store from code.
const tile = target.querySelector(".ps-tile");
const note = target.querySelector(".ps-note");
const panel = tweaks("Presets", {
hue: [260, 0, 360, 1],
size: [120, 40, 190, 1],
}, { persist: "tweakit-docs" });
mount.append(panel.el);
const apply = (p) => {
tile.style.background = `oklch(0.6 0.18 ${p.hue})`;
tile.style.width = `${p.size}px`;
tile.style.height = `${p.size}px`;
};
panel.on(apply);
apply(panel.params);
target.querySelector(".ps-save").addEventListener("click", () => {
panel.savePreset("mine");
note.textContent = `presets: ${panel.presets().join(", ")}`;
});
target.querySelector(".ps-load").addEventListener("click", () => {
note.textContent = panel.loadPreset("mine") ? "loaded “mine”" : "no preset saved yet";
});Floating & draggable
Every panel is draggable by its header — an inline panel lifts into a
floating layer on first drag (draggable: false pins it). Pass
floating: true or { x, y } to start it floated, like a
classic debug overlay.
const btn = target.querySelector(".fl-spawn");
let floater = null;
btn.addEventListener("click", () => {
if (floater) { floater.el.remove(); floater = null; btn.textContent = "Spawn a floating panel"; return; }
floater = tweaks("Floating", {
note: "Drag my header",
hue: [280, 0, 360, 1],
}, { floating: { x: 24, y: 96 } });
document.body.append(floater.el);
btn.textContent = "Dismiss it";
});The rest of the options
The full third argument, for reference:
| Option | Does |
|---|---|
theme | token overrides — see Theming |
persist | localStorage key (or true to key by panel name); enables presets |
filter | adds a fuzzy search toggle to the toolbar (the field swaps in for the title) |
floating | start floated: true or { x, y } |
draggable | header dragging — on by default, false pins |
toolbar | false for a bare panel (no copy / reset / presets) |
undo | debounced undo/redo history |
onReset | replace the default reset behavior |
onEditStart / onEditEnd | bracket a continuous drag — pause expensive work mid-scrub |