Skip to content

Blocks

Blocks are the visual elements on your canvas — shapes, text, images, sticky notes, and anything else you can see. Every block is an ECS entity with a Block component that stores its core properties.

The Block component contains:

interface Block {
tag: string; // Block type identifier
position: [number, number]; // [x, y] in world coordinates
size: [number, number]; // [width, height] in pixels
rank: string; // Z-order (fractional indexing)
rotateZ: number; // Rotation in radians
flip: [boolean, boolean]; // [flipX, flipY] for mirroring
layerId: number | null; // Layer membership (null = unlayered)
}

Woven Canvas includes the following block types:

TagDescriptionComponents
sticky-noteColored sticky notesBlock, Color, Text
textRich text blocksBlock, Text
shapeGeometric shapesBlock, Shape, Text
imageImagesBlock, Image, Asset
pen-strokeFreehand drawingsBlock, PenStroke, Color
elbow-arrowRight-angle connectorsBlock, ElbowArrow, Connector

The WovenCanvas component renders blocks using named slots. For each block, it looks for a slot named block:<tag>:

<template>
<WovenCanvas>
<!-- Override how sticky notes render -->
<template #block:sticky-note="props">
<MyStickyNote v-bind="props" />
</template>
<!-- Add a new block type -->
<template #block:my-block="props">
<MyBlock v-bind="props" />
</template>
</WovenCanvas>
</template>

The slot receives a BlockData object in props:

interface BlockData {
entityId: number; // The entity ID
block: Block; // Block component data
stratum: Stratum; // Render layer
selected: boolean; // Is selected?
hovered: boolean; // Is mouse over?
edited: boolean; // Is being edited?
held: HeldData; // Who is dragging this?
opacity: number; // Current opacity
}

Use useComponent to reactively read block data:

import { useComponent } from "@woven-canvas/vue";
import { Block, Color, Text } from "@woven-canvas/core";
const props = defineProps<{ entityId: number }>();
const block = useComponent(props.entityId, Block);
const color = useComponent(props.entityId, Color);
const text = useComponent(props.entityId, Text);
// React to changes
watchEffect(() => {
console.log("Position:", block.value?.position);
console.log(
"Color:",
color.value?.red,
color.value?.green,
color.value?.blue,
);
});

Use nextEditorTick to modify blocks, this ensures your changes are applied at the start of the next editor tick. It’s important to use nextEditorTick to apply updates, otherwise the update may be applied mid-frame, potentially causing visual glitches and inconsistent state.

import { useEditorContext } from "@woven-canvas/vue";
import { Block, Color } from "@woven-canvas/core";
const { nextEditorTick } = useEditorContext();
function moveBlock(entityId: number, dx: number, dy: number) {
nextEditorTick((ctx) => {
const block = Block.write(ctx, entityId);
block.position[0] += dx;
block.position[1] += dy;
});
}
function setColor(entityId: number, r: number, g: number, b: number) {
nextEditorTick((ctx) => {
const color = Color.write(ctx, entityId);
color.red = r;
color.green = g;
color.blue = b;
});
}

Use useQuery to find blocks matching criteria:

import { useQuery } from "@woven-canvas/vue";
import { Block, Selected } from "@woven-canvas/core";
// All blocks
const allBlocks = useQuery([Block]);
// Selected blocks only
const selectedBlocks = useQuery([Block, Selected]);
// React to selection changes
watchEffect(() => {
console.log("Selected count:", selectedBlocks.value.length);
});

Blocks are rendered in three strata:

StratumPurpose
backgroundBackground elements
contentMain content (default)
overlaySelection UI, handles

Within each stratum, blocks are sorted by their rank property.

Layers can be useful if you’re building a design app.

Layers are named z-order bands within a page that let you group blocks and control how they stack, independently of each block’s own rank. A block joins a layer through its layerId (null means it’s unlayered).

Each Layer is its own ECS entity with these properties:

PropertyDescription
labelDisplay name shown in the layers panel
rankZ-order among the page’s layers (fractional indexing) — higher sorts in front
lockedWhen true, blocks in the layer can’t be selected or hovered
hiddenWhen true, the layer isn’t rendered and is skipped by hit-testing

A block’s effective stacking order is (stratum, layer rank, block rank)

Toolbar tools place blocks for you from a snapshot. When a plugin needs to create a block from its own system, use createBlock:

import {
addComponent,
createBlock,
defineEditorSystem,
} from "@woven-canvas/core";
const spawnPotionSystem = defineEditorSystem({ phase: "update" }, (ctx) => {
for (const cmd of SpawnPotion.consume(ctx)) {
const entityId = createBlock(ctx, {
tag: "potion-card",
position: cmd.point, // top-left, in world coordinates
size: [200, 120],
});
// Add any extra components as usual.
addComponent(ctx, entityId, Potion, { name: "Health" });
}
});

createBlock handles the boilerplate that’s easy to forget:

  • assigns a Synced id so the block persists and syncs in multiplayer,
  • gives it a top-of-stack rank (z-order),
  • adds the Block component, and
  • places the block: parents it to the frame under its center.