Drive block colors, sizes, images and typefaces from typed variables, so one change updates every bound property.
Text variables fill {{key}} tokens in text, as the Text Variables guide shows. Color, number, image and typeface variables hold one typed value each. When you bind a block property to such a variable, the property takes the variable’s value and follows every change of it. One brand color can then drive every fill, border and text that uses it.
This guide covers the typed variables, binding and unbinding properties, the lock on bound properties, renaming variables, and the errors to expect.
Creating Typed Variables#
Each type has its own setter. setColor(), setNumber(), setImage() and setTypeface() create the variable when the key is new, and update it otherwise. setString() stays the setter for text variables.
// One variable per type. A setter creates the variable when the key is new.const roboto: Typeface = { name: 'Roboto', fonts: [ { uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Regular.ttf', subFamily: 'Regular', weight: 'normal', style: 'normal' } ]};engine.variable.setColor('accent', { r: 0.48, g: 0.23, b: 0.93, a: 1 });engine.variable.setNumber('cornerRadius', 40);engine.variable.setImage( 'heroImage', 'https://img.ly/static/ubq_samples/sample_1.jpg');engine.variable.setTypeface('headline', roboto);engine.variable.setString('title', 'Summer Sale');A number must be finite. A typeface needs a name and at least one font. One key holds one type, so setNumber('accent', 1) fails while accent is a color variable.
Reading Variables#
getColor(), getNumber(), getImage() and getTypeface() return the values. getType() returns 'String', 'Color', 'Number', 'Image' or 'Typeface'. findAllOfType() lists the keys of one type.
console.log(engine.variable.getType('accent')); // 'Color'console.log(engine.variable.getNumber('cornerRadius')); // 40console.log(engine.variable.findAllOfType('Color')); // ['accent']// findAll() lists text variables onlyconsole.log(engine.variable.findAll()); // ['title']findAll() lists text variables only, as before. Use findAllOfType() for the other types.
Binding Properties#
We create a card with a color fill and a rect shape, an image with an image fill, and a text that shows the title variable. A binding targets the block that owns the property: the fill owns fill/color/value, and the shape owns the corner radii.
// A card with a color fill and a rect shapeconst card = engine.block.create('graphic');engine.block.setShape(card, engine.block.createShape('rect'));engine.block.setFill(card, engine.block.createFill('color'));engine.block.setWidth(card, 720);engine.block.setHeight(card, 520);engine.block.setPositionX(card, 40);engine.block.setPositionY(card, 40);engine.block.appendChild(page, card);const cardFill = engine.block.getFill(card);const cardShape = engine.block.getShape(card);
// An image with an image fillconst image = engine.block.create('graphic');engine.block.setShape(image, engine.block.createShape('rect'));const imageFill = engine.block.createFill('image');engine.block.setFill(image, imageFill);engine.block.setWidth(image, 640);engine.block.setHeight(image, 300);engine.block.setPositionX(image, 80);engine.block.setPositionY(image, 80);engine.block.appendChild(page, image);
// A headline that shows the text variableconst text = engine.block.create('text');engine.block.replaceText(text, '{{title}}');engine.block.setWidth(text, 640);engine.block.setHeightMode(text, 'Auto');engine.block.setPositionX(text, 80);engine.block.setPositionY(text, 420);engine.block.appendChild(page, text);setVariableBinding() binds a property to a variable. The property takes the variable’s current value at once.
// Each binding applies the current value of the variable at onceengine.block.setVariableBinding(cardFill, 'fill/color/value', 'accent');for (const corner of ['TL', 'TR', 'BL', 'BR']) { engine.block.setVariableBinding( cardShape, `shape/rect/cornerRadius${corner}`, 'cornerRadius' );}engine.block.setVariableBinding( imageFill, 'fill/image/imageFileURI', 'heroImage');engine.block.setVariableBinding(text, 'text/typeface', 'headline');Each variable type binds to a fixed set of properties:
| Variable type | Properties it binds to |
|---|---|
| Color | Color properties, for example fill/color/value and fill/solid/color |
| Number | Float properties, for example opacity, width and shape/rect/cornerRadiusTL. Int properties, such as shape/star/points, do not bind |
| Image | fill/image/imageFileURI, fill/image/previewFileURI and fill/video/fileURI |
| Typeface | text/typeface and caption/typeface |
Text variables and gradient color stops cannot bind. On a text block, a bound typeface, text color or font size applies to the whole text.
Changing Variables#
Set a variable again to update every property that is bound to it.
// Every bound property follows the new valuesengine.variable.setColor('accent', { r: 0.05, g: 0.55, b: 0.55, a: 1 });engine.variable.setNumber('cornerRadius', 24);engine.variable.setImage( 'heroImage', 'https://img.ly/static/ubq_samples/sample_2.jpg');console.log(engine.block.getColor(cardFill, 'fill/color/value'));// { r: 0.05, g: 0.55, b: 0.55, a: 1 }An image variable also empties the source set of the fill, so the fill shows the URI of the variable. When one bound property rejects a value, for example an opacity above 1, the setter throws the first error. The variable and the other bound properties change anyway.
Reading Bindings#
getVariableBinding() returns the key of the variable that a property is bound to, or null. findAllVariableBindings() lists the bound properties of a block.
console.log(engine.block.getVariableBinding(cardFill, 'fill/color/value')); // 'accent'console.log(engine.block.getVariableBinding(card, 'opacity')); // nullconsole.log(engine.block.findAllVariableBindings(cardShape));// The four corner radius properties of the shapeBound Properties Are Locked#
A bound property changes only through its variable. A direct write throws an error with the code BLOCK.PROPERTY_BOUND and leaves the value as it is.
// A bound property changes only through its variabletry { engine.block.setColor(cardFill, 'fill/color/value', { r: 1, g: 0, b: 0, a: 1 });} catch (error) { // Engine errors carry a stable catalog id in `code` const { code } = error as { code?: string }; console.log(code); // 'BLOCK.PROPERTY_BOUND'}Moving, resizing, rotating, cropping, aligning and scaling also leave a bound property unchanged. A bound typeface also locks setTypeface() and setFont(). Keyframes, undo and loading a scene still change a bound property. Changing the design unit also converts a bound length, such as a width or a corner radius. A number variable whose bound properties all hold its value converts with them. Otherwise the variable keeps its value, and the next write to the variable sets the lengths in the new unit.
Removing Bindings#
removeVariableBinding() removes the binding. The property keeps its current value and no longer follows the variable.
// The bottom corners keep their current radius and stop followingengine.block.removeVariableBinding(cardShape, 'shape/rect/cornerRadiusBL');engine.block.removeVariableBinding(cardShape, 'shape/rect/cornerRadiusBR');engine.variable.setNumber('cornerRadius', 60);console.log(engine.block.getFloat(cardShape, 'shape/rect/cornerRadiusBL')); // 24console.log(engine.block.getFloat(cardShape, 'shape/rect/cornerRadiusTL')); // 60remove() removes a variable of any type. Its bound properties keep the last value and the binding. When you set a variable with the same key and type again, they follow it again.
Renaming Variables#
rename() moves the value and the type of a variable to a new key. Every binding follows the new key. For a text variable, every {{key}} token in text changes to the new key.
// Bindings and {{tokens}} in text follow the new nameengine.variable.rename('accent', 'brand/accent');engine.variable.rename('title', 'campaign/title');console.log(engine.block.getVariableBinding(cardFill, 'fill/color/value')); // 'brand/accent'console.log(engine.block.getString(text, 'text/text')); // '{{campaign/title}}'A new name must follow these rules:
- It is not empty.
- It does not start or end with whitespace. Spaces inside the name are allowed.
- It does not contain
{{or}}, because text tokens use them. - No other variable uses it. A rename to the current name does nothing.
for (const name of ['', ' padded', 'a{{b', 'campaign/title']) { try { engine.variable.rename('brand/accent', name); } catch (error) { const { code } = error as { code?: string }; // 'BLOCK.VARIABLE_NAME_INVALID' for the first three names, // 'BLOCK.VARIABLE_NAME_TAKEN' for the name of another variable console.log(code); }}Handling Type Errors#
A getter of another type and a binding to a property of another type throw errors. The variable and the property stay unchanged.
try { // A color variable has no number value engine.variable.getNumber('brand/accent');} catch (error) { const { code } = error as { code?: string }; console.log(code); // 'BLOCK.VARIABLE_TYPE_MISMATCH'}try { // Opacity is a number property, so it cannot bind to a color variable engine.block.setVariableBinding(card, 'opacity', 'brand/accent');} catch (error) { const { code } = error as { code?: string }; console.log(code); // 'BLOCK.VARIABLE_BINDING_UNSUPPORTED'}Saving and Undo#
A scene or an archive saves the typed variables and the bindings. Undo and redo restore variable values, bindings and renames.
Exporting the Result#
We export the page as a PNG. The card shows the last values of its variables.
const outputDir = './output';if (!existsSync(outputDir)) { mkdirSync(outputDir, { recursive: true });}
const blob = await engine.block.export(page, { mimeType: 'image/png' });const buffer = Buffer.from(await blob.arrayBuffer());writeFileSync(`${outputDir}/variable-bindings.png`, buffer);
console.log('Exported result to output/variable-bindings.png');Dispose of the engine in a finally block to free its resources.
// Always dispose of the engine to free resourcesengine.dispose();Troubleshooting#
- The variable does not exist (
BLOCK.VARIABLE_NOT_FOUND): set the variable before you read or bind it. - The key holds another type (
BLOCK.VARIABLE_TYPE_MISMATCH): use the getter and setter of the stored type, or remove the variable first. - The typeface is not accepted (
BLOCK.VARIABLE_TYPEFACE_INVALID): pass a typeface with a name and at least one font. - The property cannot bind to this variable (
BLOCK.VARIABLE_BINDING_UNSUPPORTED): check the table in Binding Properties, and pass the block that owns the property. - Gradient stops do not bind (
BLOCK.VARIABLE_BINDING_GRADIENT_STOPS_UNSUPPORTED): bind a color variable to a solid color fill instead. - A write to a property fails (
BLOCK.PROPERTY_BOUND): set the variable instead, or remove the binding first. - A rename fails with an invalid name (
BLOCK.VARIABLE_NAME_INVALID): follow the name rules in Renaming Variables. - A rename fails with a taken name (
BLOCK.VARIABLE_NAME_TAKEN): pick a name that no variable uses.
The Error Catalog lists every error code.
API Reference#
| Method | Description |
|---|---|
variable.setColor(key, color) / variable.getColor(key) |
Set or read a color variable |
variable.setNumber(key, number) / variable.getNumber(key) |
Set or read a number variable |
variable.setImage(key, uri) / variable.getImage(key) |
Set or read an image variable |
variable.setTypeface(key, typeface) / variable.getTypeface(key) |
Set or read a typeface variable |
variable.getType(key) |
Get the type of a variable |
variable.findAllOfType(type) |
List the keys of the variables of one type |
variable.rename(from, to) |
Rename a variable of any type |
variable.remove(key) |
Remove a variable of any type |
block.setVariableBinding(block, property, key) |
Bind a property to a variable |
block.getVariableBinding(block, property) |
Get the key of the bound variable, or null |
block.findAllVariableBindings(block) |
List the bound properties of a block |
block.removeVariableBinding(block, property) |
Remove a binding and keep the current value |
Next Steps#
- Text Variables: fill
{{key}}tokens in text with values. - Placeholders: mark the blocks that template users can replace.
- Undo and History: work with undo steps.