Editful
DocsCanvas UI

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.

440 × 0
canvas-ui-card.ts
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' },
    });
  },
});

Guides and API