Artwork controls

Declare buttons, toggles, text, navigation and exports.

On this page

Declare your artwork's interactions in the posted script. The viewer creates touch buttons and native text fields, while your artwork remains responsible for its own keyboard behavior.

A declaration

let motion = true;
function toggleMotion() {
  motion = !motion;
  motion ? p.loop() : p.noLoop();
}
p.keyTyped = () => { if (p.key === 'A') toggleMotion(); };
p.immutablesControls = {
  version: 1,
  actions: [
    { id: 'motion', label: 'Animate', type: 'toggle', key: 'A' },
    { id: 'png', label: 'Save PNG', type: 'export' }
  ],
  getState: () => ({ values: { motion }, busy: false }),
  onAction: id => { if (id === 'png') p.saveCanvas('my-edition', 'png'); }
};

For other renderers use t.immutablesControls, v.immutablesControls or g.immutablesControls. Use callbacks without a key for their buttons and toggles; implement physical keyboard input in your own script.

Actions and state

Declare at most 12 actions. Each needs a unique lowercase ID beginning with a letter and containing only letters, digits or hyphens, up to 40 characters. Labels are plain text, up to 48 characters.

  • button: one action. With a key, p5's keyboard handler runs; without one, onAction(id) runs.
  • toggle: also needs getState().values[id] as a Boolean. Without a key, onAction(id, proposedBoolean) receives the proposed value; update your own state.
  • text: a native field and Send button. maxLength is optional, 1–500, default 120. Always provide onAction.
  • navigation: Up, Down, Open and Back buttons. Always provide onAction.
  • export: appears under Save. It runs only after the visitor chooses it; use it for explicit captures rather than automatic downloads.

Keys preserve case. Supported keys are single letters/digits, a space, arrows, Enter and Escape. A text/navigation key is a shortcut label; those controls always use callbacks, so implement the corresponding physical shortcut yourself.

Text and navigation

p.immutablesControls = {
  version: 1,
  actions: [{ id: 'message', label: 'Message', type: 'text', maxLength: 60 }],
  getState: () => ({ busy: thinking, activeControl: mode }),
  onAction: (id, event) => {
    if (event.action === 'open') mode = id;
    if (event.action === 'change') draft = event.text;
    if (event.action === 'submit') respond(event.text);
    if (event.action === 'close') mode = '';
  }
};

thinking, mode, draft and respond above stand for your own state and functions. Text events are open, change, submit, close; change and submit include text. Navigation events are open, up, down, back, close.

Keep getState() cheap and free of side effects. Return busy: true during work. Optional activeControl follows modes opened by the artwork's own keys or pointer handlers; return an action ID while open and an empty string when closed. If omitted, the viewer manages its own panel selection. Navigation Back stays available during busy work.

Visibility and styling

On mobile the panel opens automatically. On desktop it starts collapsed, with an icon and the bottom-bar Artwork controls entry. After four idle seconds it fades to a faint lower-right icon. Typing, navigation, an open Save menu, busy work and keyboard focus keep it open. It does not resize the canvas.

The controls live inside #root and use existing .btn, .btn-secondary, .btn-outline-secondary, .form-control, .modal-content and .font-display classes. Your project CSS can style them. There is no shadow DOM.

Stable selectors include .artwork-controls, .artwork-controls-panel, .artwork-control, .artwork-controls-handle, .artwork-controls-form, .artwork-controls-navigation, .artwork-controls-exports and .artwork-controls-status. Individual actions have data-action; the outer element has data-project-id, .is-touch or .is-desktop, and .is-open or .is-idle.

Existing projects and automatic discovery

Reviewed profiles match the exact script SHA-256 and project ID. They provide known controls for Syncret, Firmament and PILLARS; those editions open live even when their grids use cached images. Reviewed profiles take priority over declarations.

Otherwise an explicit declaration takes priority. With no declaration, the viewer conservatively detects literal comparisons inside p.keyTyped / p.keyPressed and offers simple Key buttons. It does not infer semantic labels or toggle state. Unannotated S/P exports and contextual Enter/Escape are omitted. Unsupported syntax may yield no automatic controls.

An invalid declaration produces no panel. { version: 1, actions: [] } opts out for an unreviewed script. Grids and still-image views do not execute interactive controls. Test your own declaration in Preview in UI, not only the wizard's isolated canvas.

Source basis: artworkControlConventions, artworkControlProfile, artworkInput, ArtworkControls and config/artwork-controls.json in clients/art/src/.

Use this guide with an assistantRead the Artists & Coders brief