Basic Usage
The minimal Canvas places one piece of content at a fixed world-space position using CanvasItem:
import { Canvas, CanvasItem } from '@cratis/components/Canvas';
function MyBoard() { return ( <Canvas> <CanvasItem x={100} y={100}> <div style={{ width: 160, height: 100, background: 'white', borderRadius: 8 }}> Hello, board! </div> </CanvasItem> </Canvas> );}Canvas renders an empty surface by default — nothing appears until you give it content. There are three ways to do that, and they can be mixed:
1. Declarative children (CanvasItem)
Section titled “1. Declarative children (CanvasItem)”Wrap ordinary React content in CanvasItem and pass x/y world coordinates. This is the normal path for anything interactive — forms, notes, chat panels — because it is plain HTML/React underneath, not a rendering primitive of its own:
<Canvas> <CanvasItem x={0} y={0}><MyCard /></CanvasItem> <CanvasItem x={300} y={150}><MyCard /></CanvasItem></Canvas>CanvasItem also reports its rendered size back to the Canvas (via a ResizeObserver), which is what lets the built-in minimap and CanvasHandle.getItemBounds() know where every item actually is without you tracking sizes yourself. zIndex controls stacking against sibling items, and onSize is called whenever the item’s own size changes.
2. Data-driven items (items + renderItem)
Section titled “2. Data-driven items (items + renderItem)”For a large number of simple items, pass an items array and a renderItem function instead of JSX children. Canvas syncs the array to a WebGL scene under the hood (each item becomes a PIXI.Container), diffing additions, removals, and position changes on every render:
import { Canvas, type CanvasItemData } from '@cratis/components/Canvas';import * as PIXI from 'pixi.js';
interface Dot extends CanvasItemData { color: number;}
const dots: Dot[] = [ { id: '1', x: 0, y: 0, color: 0x60a5fa }, { id: '2', x: 120, y: 40, color: 0xf472b6 },];
function DotBoard() { return ( <Canvas items={dots} renderItem={item => { const graphics = new PIXI.Graphics(); graphics.circle(0, 0, 12).fill((item as Dot).color); return graphics; }} onItemPointerDown={(item) => console.log('clicked', item.id)} /> );}This path is lower-level than CanvasItem (you are building a PIXI.Container, not writing JSX) but scales to far more items since nothing here touches the DOM.
3. Imperative access (onReady)
Section titled “3. Imperative access (onReady)”onReady hands you the raw PIXI Application and the world container once the canvas has initialized, for cases the two approaches above don’t cover — managing your own PIXI scene graph directly:
<Canvas onReady={({ app, world }) => { // app: the PIXI.Application: app.stage, app.renderer, ... // world: the PIXI.Container everything pans/zooms with }}/>The three approaches compose: declarative CanvasItem children, items/renderItem, and content added imperatively through onReady can all be present on the same Canvas at once.