Render structured plugin nodes.
Use Canvas UI to display field-driven text, images, and layout inside Editful's WebGL canvas.
What Canvas UI is for
Use Canvas UI when a plugin adds many structured nodes to the canvas. Each node renders with the WebGL scene instead of mounting its own DOM or component runtime.
- Canvas-native
- Nodes zoom, pan, select, and batch with the rest of the scene.
- Data-driven
- Fields can drive text, images, fill, stroke, and other display state.
- Content-sized
- Cards can derive their height from wrapped text and fixed sections.
- Display-only
- Commands, importers, and editor surfaces own input and updates.
Canvas UI is display-only. Commands and editor surfaces write fields. The view reads and renders them.
Components
Use a different surface when needed
Canvas UI is for node presentation. Other plugin surfaces cover other jobs.
kind.pack()- Use custom geometry when the node cannot be expressed as text, images, boxes, and stacks.
- Toolbar and editors
- Use these surfaces to change the fields a view displays.
Complete example
This GitHub card binds fields to WebGL text and paint. Edit the fields or width below the preview.
import { TextFont, definePlugin, hexColor } from '@editful/canvas-sdk';
export const CANVAS_UI_CARD_KIND = 'docs:github-card';
export default definePlugin({
register(context) {
const kind = context.kind(CANVAS_UI_CARD_KIND);
const repo = kind.field.string('repo');
const title = kind.field.string('title');
const state = kind.field.string('state');
const metadata = kind.field.string('metadata');
const statusFill = kind.field.f64('status-fill');
const ui = kind.ui;
kind.hit('rect');
kind.defaults({
fill: hexColor('#0d1117'),
stroke: hexColor('#30363d'),
strokeWidth: 1,
cornerRadius: 6,
fontSize: 16,
textColor: hexColor('#f0f6fc'),
lineHeight: 1.35,
});
const compactText = (value: typeof repo, color: number) => ui.text(value, {
font: { family: TextFont.Sans, size: 12, weight: 400, lineHeight: 1.25 },
color,
wrap: false,
});
kind.view({
root: ui.box({
fill: 'node',
stroke: 'node',
strokeWidth: 'node',
cornerRadius: 'node',
child: ui.stack({
axis: 'block',
children: [
ui.box({
fill: hexColor('#161b22'),
padding: { inline: 20, block: 10 },
child: ui.stack({
axis: 'inline',
align: 'center',
gap: 12,
children: [
compactText(repo, hexColor('#4493f8')),
ui.spacer(),
ui.box({
fill: statusFill,
cornerRadius: 'full',
padding: { inline: 12, block: 4 },
child: compactText(state, hexColor('#ffffff')),
}),
],
}),
}),
ui.box({
padding: { inline: 20, top: 16, bottom: 14 },
child: ui.text(title, {
font: {
family: TextFont.Sans,
size: 'node',
weight: 'node',
lineHeight: 'node',
},
color: 'node',
}),
}),
ui.box({
padding: { inline: 20, block: 10 },
child: ui.stack({
axis: 'inline',
align: 'center',
children: [
compactText(metadata, hexColor('#9198a1')),
ui.spacer(),
ui.text('Open on GitHub', {
font: {
family: TextFont.Sans,
size: 10,
weight: 400,
lineHeight: 1.25,
},
color: hexColor('#4493f8'),
wrap: false,
}),
],
}),
}),
],
}),
}),
width: { mode: 'user', min: 320 },
height: { mode: 'fit-content' },
});
},
});