Building Reusable p5.js Widgets with Instance Mode
Learn how p5.js instance mode isolates sketches, lets you package them as reusable widgets, and avoids global namespace clashes when embedding multiple instances on a page.
18 Sept 2025, 23:09 UTC

Putting two p5.js sketches on the same page in global mode causes them to overwrite each other's setup and draw functions, and any variables you declare at the top level end up as properties of window. The result is a broken sketch where the second definition wins.
What instance mode actually does
In instance mode you create a local p5 object and pass it to every sketch function. The pattern looks like:
const mySketch = (p) => {
let counter = 0;
p.setup = () => p.createCanvas(200, 200);
p.draw = () => {
p.background(30);
p.ellipse(p.width / 2, p.height / 2, 40 + counter, 40 + counter);
counter += 0.5;
};
};
new p5(mySketch, document.getElementById('sketch-a'));
The argument p is the instance; all p5 calls are made on it, and variables like counter live in the sketch’s closure, not on the global object.
Why isolation matters for reusable widgets
When you wrap a sketch in a factory function you can instantiate it anywhere—inside a React component, a Vue template, or plain HTML—without worrying about namespace collisions. Each instance owns its own canvas, animation frame, and event listeners, so tearing down a widget is as simple as calling p.remove() on its p5 object.
Worked example: a reusable hue-slider widget
The following factory creates a hue slider that reports its value via a callback. Because it uses instance mode, you can place multiple sliders on the same page and they will not interfere.
function hueSliderFactory(onChange) {
return (p) => {
let hue = 0;
const width = 200;
const height = 30;
p.setup = () => p.createCanvas(width, height);
p.draw = () => {
p.background(0);
// draw gradient
for (let x = 0; x < width; x++) {
const col = p.color(p.map(x, 0, width, 0, 360), 100, 100);
p.stroke(col);
p.line(x, 0, x, height);
}
// draw selector
const selectorX = p.map(hue, 0, 360, 0, width);
p.fill(0);
p.noStroke();
p.rect(selectorX - 5, 0, 10, height);
};
p.mouseDragged = () => {
hue = p.constrain(p.map(p.mouseX, 0, width, 0, 360), 0, 360);
if (typeof onChange === 'function') onChange(hue);
};
};
}
// Usage: two independent sliders
new p5(hueSliderFactory((v) => console.log('Slider A:', v)), document.getElementById('slider-a'));
new p5(hueSliderFactory((v) => console.log('Slider B:', v)), document.getElementById('slider-b'));
Each slider manages its own hue variable and its own mouse handler. Opening the browser console and typing window.hue returns undefined, confirming that no global variable leaked.
Trade‑offs and limitations
Verbosity: every p5 call must be prefixed with the instance (p.), which can feel like boilerplate compared to the terse global mode.
Helper functions: utilities that expect the global API need to receive the instance explicitly, e.g. function mapValue(p, v, min1, max1, min2, max2) { return p.map(v, min1, max1, min2, max2); }. Forgetting to pass p is a common mistake.
Plugin compatibility: some third‑party p5 add‑ons assume window holds the p5 namespace and may reference createVector or random directly. Those plugins either need a wrapper that passes the instance or must be avoided when using instance mode.
Actionable checklist
- Wrap each sketch in a factory that receives the p5 instance.
- Declare all sketch‑specific variables inside the factory closure.
- Call
p.remove()when the widget is unmounted to free canvas and animation frames. - Pass the instance to any helper functions you create.
- Test isolation by checking that
windowdoes not contain your sketch variables after instantiation.
When you need more than one sketch, want to embed p5 in a component framework, or desire explicit control over lifecycle, instance mode is the pragmatic choice. For a single‑file learning experiment where brevity matters, global mode remains acceptable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.