01 Software

UI

CanvasRenderer

Headless canvas renderer

CanvasRenderer

A headless component for rendering Canvases collection canvas data. Built on @xyflow/react, with fully customizable node renderers.

CanvasRenderer is imported from: @01.software/sdk/ui/canvas.

Installation

@xyflow/react is required as a peer dependency.

npm install @xyflow/react

You must import the CSS:

import '@xyflow/react/dist/style.css'

Basic Usage

import { CanvasRenderer } from '@01.software/sdk/ui/canvas'
import '@xyflow/react/dist/style.css'

function CanvasViewer({ canvas }) {
  return (
    <div style={{ width: '100%', height: 600 }}>
      <CanvasRenderer data={canvas.canvas} />
    </div>
  )
}

Props

PropTypeDefaultDescription
dataCanvasDataCanvas document's canvas field (required). Returns null if null/undefined
classNamestringContainer CSS class
styleCSSPropertiesContainer inline style
nodeRenderersRecord<string, ComponentType<DynamicNodeSlotProps>>Custom renderers by node type slug
nodeTypeDefsNodeTypeDef[]Node type definitions (enables color badges, field-type-aware rendering)
edgeTypeDefsEdgeTypeDef[]Edge type definitions for styled edges (color, stroke, markers, animation)
backgroundbooleanfalseShow background pattern
interactivebooleanfalseEnable pan/zoom (default: read-only)
fitViewbooleantrueFit view on mount
onNodeClick(event, node: CanvasNode) => voidNode click handler
onNodeDoubleClick(event, node: CanvasNode) => voidNode double-click handler
onNodeContextMenu(event, node: CanvasNode) => voidNode right-click handler
onNodeMouseEnter(event, node: CanvasNode) => voidNode mouse enter handler
onNodeMouseLeave(event, node: CanvasNode) => voidNode mouse leave handler
onEdgeClick(event, edge: CanvasEdge) => voidEdge click handler
frameRendererComponentType<FrameNodeSlotProps>Custom frame node renderer
nodeWrapperComponentType<NodeWrapperSlotProps>Wraps every dynamic node's rendered content
edgeRenderersRecord<string, ComponentType<EdgeSlotProps>>Custom edge renderers by edge type slug
controlsbooleanfalseShow zoom/fit controls
minimapbooleanfalseShow minimap
minimapNodeColor(node: CanvasNode) => stringCustom minimap node color function
childrenReactNodeAdditional children rendered inside ReactFlow
renderNode(props: DynamicNodeSlotProps, content: ReactElement) => ReactElement | nullGlobal override for all dynamic nodes. content is the resolved rendering, props.defaultRender has original template/field rendering
onViewportChange(viewport: CanvasViewport) => voidCalled when viewport changes (pan/zoom)
defaultViewportCanvasViewportDefault viewport (used when fitView is false)
boundsCanvasBoundsFocus on specific bounds
clampBoundsCanvasBoundsSeparate panning restriction bounds (no padding). Auto-enforces minZoom at cover zoom. Overrides bounds-based translateExtent
focusPaddingnumber0.1Padding for focus bounds
focusAnimationboolean | numbertrueAnimate focus transition (true=300ms, number=custom ms, false=instant)
focusMode'contain' | 'cover''contain'contain fits entire bounds, cover fills viewport (may crop)
responsiveFitbooleantrueRe-fit viewport on container resize (when bounds is set)
translateExtent[[number, number], [number, number]]Override translateExtent directly for custom pan limits
minZoomnumberMinimum zoom level. When clampBounds is set, auto-enforced as cover zoom
maxZoomnumberMaximum zoom level

Stable references required: nodeRenderers, frameRenderer, nodeWrapper, renderNode, and edgeRenderers must be stable — defined outside the component or wrapped with useCallback/useMemo. Unstable references recreate nodeTypes/edgeTypes on every render, causing React Flow to remount all nodes.

Node Types

CanvasRenderer supports two node types:

TypeDescription
dynamicDefault renderer for all regular nodes. Distinguished by nodeTypeSlug
frameContainer/grouping node (dashed border, background color)

Built-in Node Types

Default node type definitions provided via BUILT_IN_NODE_TYPES:

slugFields
textbody (textarea)
imageimage (image), alt (text), caption (text)

Built-in Edge Types

Default edge type definition provided via BUILT_IN_EDGE_TYPES:

slugProperties
defaultstrokeWidth: 2, animated: false, lineStyle: default

useCanvas Hook

Fetches a Canvas document, node type definitions, and edge type definitions in one call. Queries by slug or UUID.

import { useCanvas, CanvasRenderer } from '@01.software/sdk/ui/canvas'

function CanvasPage({ slug }) {
  const { data, nodeTypeDefs, edgeTypeDefs, isLoading, error } = useCanvas({
    client,
    slug,
    enabled: true,
  })

  if (isLoading) return <div>Loading...</div>
  if (!data) return null

  return (
    <div style={{ width: '100%', height: 600 }}>
      <CanvasRenderer data={data} nodeTypeDefs={nodeTypeDefs} />
    </div>
  )
}
OptionTypeDescription
clientClient | ServerClientSDK client (required)
slugstringCanvas slug (query by slug)
idstringCanvas ID (query by UUID)
enabledbooleanEnable query (default: true)
ReturnTypeDescription
dataCanvasData | undefinedCanvas data
nodeTypeDefsNodeTypeDef[]Built-in + API node type definitions (merged)
edgeTypeDefsEdgeTypeDef[]Built-in + API edge type definitions (merged)
canvasRecord<string, unknown> | undefinedFull canvas document
isLoadingbooleanLoading state
errorError | nullError

Custom Node Rendering

Use nodeRenderers to provide custom renderers by node type slug:

import {
  CanvasRenderer,
  type DynamicNodeSlotProps,
} from '@01.software/sdk/ui/canvas'

function ProductCard({ id, label, fields, nodeTypeDef }: DynamicNodeSlotProps) {
  return (
    <div className="product-card">
      <h3>{label}</h3>
      {fields.image && <img src={String(fields.image)} alt={label} />}
      <p>{String(fields.price)}</p>
    </div>
  )
}

<CanvasRenderer
  data={canvasData}
  nodeTypeDefs={nodeTypeDefs}
  nodeRenderers={{
    'product-card': ProductCard,
  }}
/>

DynamicNodeSlotProps

PropTypeDescription
idstringNode ID
nodeTypeSlugstringNode type slug
labelstringNode label
fieldsRecord<string, unknown>Node field data
nodeTypeDefNodeTypeDef | undefinedNode type definition (if available)
selectedboolean | undefinedWhether this node is currently selected
widthnumber | undefinedMeasured node width (undefined before first measurement)
heightnumber | undefinedMeasured node height (undefined before first measurement)
defaultRenderReactElement | undefinedDefault rendering (template or field-based). Allows wrapping/extending

Rendering Priority

CanvasRenderer resolves dynamic node renderers in this order:

  1. nodeRenderers[slug] — Custom renderer (highest priority)
  2. NodeTypeDef.template — Auto-compiled JSX template
  3. Default field rendering — Type-aware display (image, text, etc.)

If you provide a nodeRenderer for a slug that also has a template, your renderer completely replaces the built-in template handling — including size calculation and CSS injection. Only use nodeRenderers when you need full control over rendering.

Template Rendering

When NodeTypeDef.template is set, CanvasRenderer compiles JSX at runtime using sucrase and renders the component automatically. NodeTypeDef.customCSS is injected as a global <style> tag.

CodeComponentProps

Template components receive these props:

PropTypeDescription
fieldsRecord<string, unknown>Node field data
labelstringNode label
colorstringNode type color
nodeTypeSlugstringNode type slug
widthnumberNode width (measured or defaultSize)
heightnumberNode height (measured or defaultSize)

Security

Templates are sandboxed: document, window, fetch, eval, import, localStorage, setTimeout, and other browser globals are blocked. Templates that contain these patterns will not compile.

Advanced Usage

import { compileTemplate, clearTemplateCache } from '@01.software/sdk/ui/canvas'

// Manually compile a template
const Component = compileTemplate(templateCode, 'my-slug')

// Clear all cached compiled templates
clearTemplateCache()

Node Wrapper

Use nodeWrapper to wrap every dynamic node's rendered content. This is useful for adding card styling, selection indicators, or React Flow Handle components for edge connections.

CanvasRenderer does not include Handle components by default. If you need visible edges connecting to nodes, use nodeWrapper to add them.

import { Handle, Position } from '@xyflow/react'
import {
  CanvasRenderer,
  type NodeWrapperSlotProps,
} from '@01.software/sdk/ui/canvas'

function NodeCard({ label, children }: NodeWrapperSlotProps) {
  return (
    <div className="rounded-lg border bg-white p-3 shadow-sm">
      <Handle type="target" position={Position.Top} />
      <div className="text-sm font-semibold">{label}</div>
      {children}
      <Handle type="source" position={Position.Bottom} />
    </div>
  )
}

<CanvasRenderer data={canvas} nodeWrapper={NodeCard} />

NodeWrapperSlotProps

PropTypeDescription
idstringNode ID
nodeTypeSlugstringNode type slug
labelstringNode label
selectedboolean | undefinedWhether node is selected
nodeTypeDefNodeTypeDef | undefinedNode type definition
childrenReactNodeDefault rendered content

Frame Renderer

Use frameRenderer to provide a custom frame node renderer. Frame nodes are container/grouping nodes that visually wrap other nodes.

import { CanvasRenderer, type FrameNodeSlotProps } from '@01.software/sdk/ui/canvas'

function CustomFrame({
  id,
  label,
  color,
  padding,
  borderStyle,
  opacity,
}: FrameNodeSlotProps) {
  return (
    <div style={{ backgroundColor: color, padding, opacity, borderStyle }}>
      <span>{label}</span>
    </div>
  )
}

<CanvasRenderer data={canvas} frameRenderer={CustomFrame} />

FrameNodeSlotProps

PropTypeDescription
idstringNode ID
labelstringFrame label
colorstring | undefinedBackground color
paddingnumber | undefinedInner padding
borderStyle'dashed' | 'solid' | 'none' | undefinedBorder style
opacitynumber | undefinedBackground opacity
widthnumber | undefinedFrame width
heightnumber | undefinedFrame height
childrenReactNode | undefinedChildren (if any)

Frame Navigation

Use getFrameData to extract a frame's content and render it as an isolated view. Returns filtered nodes, edges, and dual bounds for viewport control.

import { useCanvas, CanvasRenderer, getFrameData } from '@01.software/sdk/ui/canvas'
import '@xyflow/react/dist/style.css'

const FRAME_ID = '54ed896d-...'

function FrameViewer({ slug }) {
  const { data, nodeTypeDefs, edgeTypeDefs } = useCanvas({ client, slug })

  const frameData = useMemo(
    () => (data ? getFrameData(data, FRAME_ID) : undefined),
    [data],
  )

  if (!frameData) return null

  return (
    <div style={{ width: '100%', height: 600 }}>
      <CanvasRenderer
        data={frameData.data}
        nodeTypeDefs={nodeTypeDefs}
        edgeTypeDefs={edgeTypeDefs}
        bounds={frameData.fitBounds}
        clampBounds={frameData.clampBounds}
        interactive
        frameRenderer={() => null}
      />
    </div>
  )
}

FrameData

FieldTypeDescription
dataCanvasDataFiltered canvas (frame + descendants, draggable: false)
fitBoundsCanvasBoundsChild content bounding box — use with bounds for viewport centering
clampBoundsCanvasBoundsFrame area bounding box — use with clampBounds for panning restriction

Use frameRenderer={() => null} to hide the frame node visually while keeping its children visible. The frame node is included in the data to preserve parent-child positioning.

FunctionDescription
getFrameData(data, frameId)Extract frame descendants with dual bounds
getFrames(nodes)Get all frame nodes with bounds (sorted top-left to bottom-right)
getNodeBounds(nodes, nodeIds)Calculate bounding box for given node IDs

Edge Renderers

Use edgeRenderers to provide custom edge rendering by edge type slug. Each renderer receives the edge data and its type definition.

import { CanvasRenderer, type EdgeSlotProps } from '@01.software/sdk/ui/canvas'

function CustomEdge({ id, source, target, edgeTypeDef, style }: EdgeSlotProps) {
  return (
    <g>
      <line
        x1={0}
        y1={0}
        x2={100}
        y2={100}
        stroke={edgeTypeDef?.color ?? '#888'}
        strokeWidth={edgeTypeDef?.strokeWidth ?? 2}
      />
    </g>
  )
}

<CanvasRenderer
  data={canvas}
  edgeTypeDefs={edgeTypeDefs}
  edgeRenderers={{ 'custom-edge': CustomEdge }}
/>

EdgeSlotProps

PropTypeDescription
idstringEdge ID
edgeTypeSlugstring | undefinedEdge type slug
sourcestringSource node ID
targetstringTarget node ID
labelstring | undefinedEdge label
fieldsRecord<string, unknown> | undefinedEdge field data
edgeTypeDefEdgeTypeDef | undefinedEdge type definition
styleCSSProperties | undefinedComputed edge style

useCanvasData Hook

A pure data transformation hook for headless or SSR usage. Extracts nodes, edges, and type definition maps from canvas data without rendering anything.

import { useCanvasData } from '@01.software/sdk/ui/canvas'

function CanvasAnalytics({ canvas, nodeTypeDefs, edgeTypeDefs }) {
  const { nodes, edges, nodeTypeDefsMap, edgeTypeDefsMap } = useCanvasData({
    data: canvas,
    nodeTypeDefs,
    edgeTypeDefs,
  })

  return (
    <div>
      <p>Nodes: {nodes.length}</p>
      <p>Edges: {edges.length}</p>
      <p>Node types: {nodeTypeDefsMap.size}</p>
    </div>
  )
}
OptionTypeDescription
dataCanvasData | undefinedCanvas data from Canvas document (required)
nodeTypeDefsNodeTypeDef[]Node type definitions (defaults to built-in types)
edgeTypeDefsEdgeTypeDef[]Edge type definitions (defaults to built-in types)
ReturnTypeDescription
nodesCanvasNode[]Nodes from canvas data
edgesEdge[]Edges from canvas data (typed for @xyflow/react)
nodeTypeDefsMapMap<string, NodeTypeDef>Map of node type slug to definition
edgeTypeDefsMapMap<string, EdgeTypeDef>Map of edge type slug to definition

NodeTypeDef

When nodeTypeDefs is provided, the default renderer is enhanced — field-type-aware rendering (image fields render as <img>, all other fields render as text) and template support.

interface NodeTypeDef {
  slug: string
  name: string
  color: string
  defaultSize: { width: number; height: number }
  fields: NodeTypeFieldDef[]
  transparentBackground?: boolean
  template?: string | null
  customCSS?: string | null
}

interface NodeTypeFieldDef {
  name: string
  label: string
  fieldType:
    | 'text'
    | 'textarea'
    | 'number'
    | 'url'
    | 'color'
    | 'image'
    | 'select'
    | 'toggle'
  options?: { label: string; value: string }[]
  defaultValue?: string
  required?: boolean
}

EdgeTypeDef

Edge type definitions returned by useCanvas. Tenants define custom edge types with visual properties and custom fields.

interface EdgeTypeDef {
  slug: string
  name: string
  color: string
  strokeWidth: number
  animated: boolean
  lineStyle: string
  markerStart: string
  markerEnd: string
  fields: NodeTypeFieldDef[]
}

Exports

ExportDescription
CanvasRendererMain component
CanvasRendererPropsProps type
useCanvasCanvas document + node type fetch hook
SDKClientuseCanvas client interface type
UseCanvasOptions, UseCanvasResultuseCanvas types
useCanvasDataPure data transformation hook (headless/SSR)
UseCanvasDataOptions, UseCanvasDataResultuseCanvasData types
CanvasDataCanvas data ({ nodes, edges, viewport })
CanvasNode, CanvasEdge, CanvasViewportReact Flow entity types
CanvasNodePosition, CanvasNodeDataNode position/data types
DynamicNodeData, DynamicNodeSlotPropsDynamic node data/slot props
FrameNodeData, FrameNodeSlotPropsFrame node data/slot props
EdgeSlotPropsEdge renderer slot props
NodeWrapperSlotPropsNode wrapper slot props
CanvasBoundsViewport focus bounds type
NodeTypeDef, NodeTypeFieldDefNode type definitions
EdgeTypeDefEdge type definition
isDynamicNode, isFrameNodeType guards
BUILT_IN_NODE_TYPESBuilt-in node type definitions (text, image)
BUILT_IN_EDGE_TYPESBuilt-in edge type definitions (default)
getNodeBounds, getFramesUtility functions for viewport focus and frame navigation
getFrameDataExtract frame descendants with dual bounds
FrameDatagetFrameData return type
compileTemplateCompile JSX template string to React component
clearTemplateCacheClear compiled template cache
CodeComponentPropsTemplate component props type

On this page