Quick tour
The schema is the whole API. Every property becomes a control, inferred from the shape of its value. No registration, no builders. This page is the grammar.
One schema, one scene
Six values, six different shapes, six controls. Everything driving the
tile on the left is plain data on panel.params.
const card = target.querySelector(".qt-card");
const panel = tweaks("Scene", {
size: [140, 60, 240, 1], // [value, min, max, step] → slider
blur: 0, // bare number → slider over a sensible range
caption: "Shorthands", // string → text input
show: true, // boolean → checkbox
blend: ["normal", "screen", "overlay", "multiply"], // array → list
glow: "#7C5CFF", // color string → wide-gamut picker
});
mount.append(panel.el);
const apply = (p) => {
card.style.width = `${p.size}px`;
card.style.height = `${p.size}px`;
card.style.filter = `blur(${p.blur * 24}px)`;
card.style.mixBlendMode = p.blend;
card.style.background = p.glow;
card.style.boxShadow = `0 0 70px ${p.glow}`;
card.style.opacity = p.show ? 1 : 0.06;
card.querySelector("span").textContent = p.caption;
};
panel.on(apply);
panel.ready.then(() => apply(panel.params)); // color is lazy on the split buildmount 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).
The inference table
Everything the inference understands, in one place:
| You write | You get | params value |
|---|---|---|
24 | slider, 0–1 for a value ≤ 1, else 0–3× the value; a negative spans that either side of 0 | number |
[1.2, 0, 3, 0.1] | slider with min / max / step | number |
[[20, 80], 0, 100] | interval (dual-handle range) | [lo, hi] |
true | checkbox | boolean |
"Hello" | text input | string |
"#7C5CFF" | wide-gamut color picker | color string |
["a", "b"] | dropdown list | string |
{ options: ["a", "b"], value: "b" } | dropdown list with a chosen default | string |
{ action: fn } | button | — |
{ x: 0, y: 10 } | folder (collapsible group) | nested object |
{ type: "…", … } | that control, verbatim | per control |
The verbose escape hatch
Any control can be written as { type, … } to reach what the
shorthands can't say: the heavy controls (gradient,
motion, spring, cubic-bézier, plot,
monitors…) and per-control options. Every object form
also accepts render, disabled and hint (covered in
the panel API). Hover the ⓘ beside “Opacity”.
const tile = target.querySelector(".qt-tile");
const panel = tweaks("Verbose", {
opacity: { type: "slider", value: 1, min: 0, max: 1, step: 0.05,
hint: "Alpha blend of the tile" },
zoom: { type: "number", value: 100, min: 25, max: 400, step: 5, unit: "%" },
fit: { type: "segmented", options: ["cover", "contain", "auto"] },
});
mount.append(panel.el);
const apply = (p) => {
tile.style.opacity = p.opacity;
tile.style.transform = `scale(${p.zoom / 100})`;
tile.style.backgroundSize = p.fit;
};
panel.on(apply);
apply(panel.params); // every control here is built-in → params are live already