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.
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.
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.
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.
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 panelrenderControls();});​canva.onControlsEvent(async (opts) => {// Update the state objectif (opts.message.controlType === 'select') {state[opts.message.controlId] = opts.message.message.value;}​// Re-render the control panelrenderControls();});​function renderControls() {// Define the controlsconst 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 panelcanva.updateControlPanel(controls);}
This is only a pattern though. You can use other techniques to manage the state 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 (e.g. 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
:
​ColorPicker​
​Slider​
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.
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);}