Rich controls

What are rich controls? (And how to use them.)

Some extensions, such as editing extensions, can render user interface components in the Canva editor. Canva refers to these components as rich controls. You can use rich controls to create heavily customized user experiences while still remaining aligned with Canva's design system.

This tutorial explains how to create and manage rich controls.

Step 1: Create a control

You can create controls with the create method:

canva.create("button", {
id: "myButton",
label: "My Button",
});

This method accepts two arguments:

  • The type of control.

  • Properties for configuring the control.

The available properties depend on the control, but at a minimum, all controls require an id property.

You can also use JSX to render controls. For more information, refer to JSX.

Step 2: Render a control in the control panel

Creating a control doesn't render the control in the control panel. You have to explicitly render controls in the control panel with the updateControlPanel method.

The updateControlPanel method accepts an array of controls as its only argument:

canva.updateControlPanel([
canva.create("button", {
id: "myButton",
label: "My Button",
}),
]);

Canva renders the controls in the same order they appear in the array.

An extension must call the updateControlPanel method at various points throughout its lifecycle, including when adding, updating, and removing controls from the control panel.

Until an extension calls the updateControlPanel method:

  • The control panel remains in a "loading" state.

  • Canva doesn't render the Apply and Cancel buttons.

For this reason, extensions should call the updateControlPanel method in the onReady callback.

If necessary, an extension can render an empty control panel by passing an empty array into the updateControlPanel method.

Step 3: Detect control events

When a user interacts with a control, Canva emits an event. Your extension can listen for this event by registering a callback with the onControlsEvent method:

canva.onControlsEvent((opts) => {
console.log(opts);
});

This callback receives an opts object that contains information about the event. This object always contains a controlId and controlType property, which you can use to identify the control the user is interacting with:

canva.onControlsEvent((opts) => {
if (opts.message.controlId === "myButton") {
console.log("You clicked my button!");
}
});

For some controls, the opts object also contains the value of the control. You can access this value via the opts.message.message.value property:

canva.onControlsEvent((opts) => {
if (opts.message.controlType === "slider") {
console.log(event.message.message.value);
}
});

What happens when a user interacts with a control is the responsibility of the extension.

Step 4: Manage the state of controls

Controls don't maintain their own state. For example, if an extension renders a Select control, the default behavior means the user can't change the value of the control.

This is because, when a user interacts with a control, Canva doesn't:

  • Keep track of the control's value.

  • Re-render the control when its value changes.

Extensions are responsible for both of these tasks.

A common solution is store the values of controls in an object. Then, when a user interacts with a control, the extension can update the values in the object and re-render the controls. The following snippet demonstrates this pattern:

const canva = window.canva.init();
const state = {
selectExample: "apples",
};
canva.onReady(async () => {
// Render the control panel
renderControls();
});
canva.onControlsEvent(async (opts) => {
// Update the state object
if (opts.message.controlType === "select") {
state[opts.message.controlId] = opts.message.message.value;
}
// Re-render the control panel
renderControls();
});
function renderControls() {
// Define the controls
const controls = [
canva.create("select", {
id: "selectExample",
value: state.selectExample,
options: [
{ value: "apples", label: "Apples" },
{ value: "bananas", label: "Bananas" },
{ value: "pears", label: "Pears" },
],
}),
];
// Update the control panel
canva.updateControlPanel(controls);
}

This is only a pattern though. You can use other techniques to manage the state of controls.

Step 5: Improve the performance of controls

Some controls, such as the color picker, emit a lot of events. This can cause performance issues if an extension re-renders controls (or performs some other action) for every event.

To avoid this problem, Canva passes a commit property into the onControlsEvent callback:

canva.onControlsEvent((opts) => {
console.log(opts.message.commit);
});

This property is false while the user is actively interacting with a control (for example, adjusting the value of a slider) and true once the user has finished interacting with a control.

You can use the commit property to return early from the onControlsEvent callback if the user is actively interacting with a control:

canva.onControlsEvent((opts) => {
if (!opts.message.commit) {
return;
}
console.log(opts);
});

This significantly reduces the number of times Canva re-renders the controls.

The following controls emit an event with a commit property that may be false:

For the rest of the controls that emit an event, the commit property is always true.

If it's important that users see real-time updates to their images, return early from the onControlsEvent callback after re-rendering the user's image but before re-rendering the controls.

Example

const canva = window.canva.init();
const state = {
colorPickerExample: "#ff0099",
};
canva.onReady(async () => {
renderControls();
});
canva.onControlsEvent(async (opts) => {
if (!opts.message.commit) {
return;
}
if (opts.message.controlType === "color_picker") {
state[opts.message.controlId] = opts.message.message.value;
document.body.style.backgroundColor = opts.message.message.value;
}
renderControls();
});
function renderControls() {
const controls = [
canva.create("color_picker", {
id: "colorPickerExample",
color: state.colorPickerExample,
}),
];
canva.updateControlPanel(controls);
}