Use React components when a node or dashboard widget needs application UI, context or local state. You paint the inside of the box; the engine owns its geometry and gestures. For a different silhouette or fill without application UI, use the shipped shapes instead; see JavaScript: elements and content.
The examples below run in your React application's browser entry. Install the binding and its peers:
bashnpm install @grafloria/react @grafloria/engine @grafloria/renderer @grafloria/element react react-dom
1. Render components inside connected nodes
GrafloriaFlow maps nodeTypes keys to node type strings. Your component receives NodeProps:
id: the node's id.data: the node's payload, not a separate set of component props.selected: the live selection state.node: the liveNodeModel, for queries or tracked setters beyond the other props.
Type the registry with NodeTypes, the nodes with NodeSpec, and the connections with EdgeSpec. Each custom spec below carries custom: true; for the HTML rendering opt-in, see elements and content.
Replace your application's App.tsx with this example. It renders Build and Deploy cards connected by an edge. Each card reads the surrounding React context; the checkbox changes the owner line in both cards.
tsximport { createContext, useContext, useState } from 'react';
import {
GrafloriaFlow,
type NodeProps,
type NodeTypes,
} from '@grafloria/react';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';
type CardData = { title: string; owner: string };
const ShowOwners = createContext(true);
function Card({ id, data, selected }: NodeProps<CardData>) {
const showOwners = useContext(ShowOwners);
return (
<div
aria-label={`${data.title} (${id})`}
style={{
width: '100%',
height: '100%',
boxSizing: 'border-box',
padding: '12px 16px',
background: '#fff',
color: '#232a3d',
borderRadius: 12,
border: selected ? '2px solid #3b52d9' : '2px solid #94a5f0',
font: '14px/1.5 system-ui, sans-serif',
}}
>
<strong>{data.title}</strong>
{showOwners && <div>Owner: {data.owner}</div>}
</div>
);
}
const nodeTypes: NodeTypes = { card: Card };
const nodes: NodeSpec[] = [
{
id: 'build', type: 'card', custom: true,
position: { x: 80, y: 90 }, size: { width: 230, height: 110 },
data: { title: 'Build', owner: 'CI' },
},
{
id: 'deploy', type: 'card', custom: true,
position: { x: 430, y: 90 }, size: { width: 230, height: 110 },
data: { title: 'Deploy', owner: 'CD' },
},
];
const edges: EdgeSpec[] = [
{ id: 'build-deploy', source: 'build', target: 'deploy' },
];
export default function App() {
const [showOwners, setShowOwners] = useState(true);
return (
<ShowOwners.Provider value={showOwners}>
<label>
<input
type="checkbox"
checked={showOwners}
onChange={(event) => setShowOwners(event.target.checked)}
/>
Show owners
</label>
<div style={{ height: 400 }}>
<GrafloriaFlow
defaultNodes={nodes}
defaultEdges={edges}
nodeTypes={nodeTypes}
fitView
/>
</div>
</ShowOwners.Provider>
);
}
Click a card to change its selection border, or drag it to move its box. The binding uses React portals, so context and hooks remain part of your application tree rather than a separate React root. Removing a node drops its portal and unmounts the component.
Give each node a size and fill that box with width: '100%', height: '100%' and boxSizing: 'border-box'. Padding and borders then stay inside the geometry used for hit-testing and connections. Declare ports on the spec, not as elements inside the card; see port connections.
When node content refreshes
The binding refreshes custom components on nodes:change and selection:change, reading node.isSelected() again. It also observes the node's data and metadata writes and supplies a fresh data object. Use node.setData() rather than assigning payload properties directly.
Position changes during a drag move the host rather than triggering a content render on every frame. Keep positioning out of your component's styles. Your own React state and context still render normally.
These examples use uncontrolled defaults. For controlled data and its change-event return path, follow the React quick start.
2. Mix custom widgets with shipped painters
GrafloriaDashboard uses widgetTypes to map a widget's kind to a component. WidgetProps supplies { widget, data }: the full widget spec and its payload. Unlike nodes, dashboard widgets need no custom flag.
Use the shipped kpi, line, bar, donut, funnel and table painters for those kinds. Register a component only where you need your own UI; unmatched kinds pass to the built-in renderer.
Known issue: A painter supplied through
options.renderWidgetnever runs in the React binding: the wrapper overwrites that option with its portal callback. Until this is fixed, supply custom content throughwidgetTypesand keepoptionsfor board behavior and geometry.
The kit's intended custom-painting option is options.renderWidget; the React equivalent is widgetTypes={{ note: Note }}, used below. Type that mapping with WidgetTypes and the data with DashboardWidgetSpec.
Replace App.tsx with this independent example. It renders a shipped Revenue KPI beside a React release note. The note reads application context and keeps an acknowledgement count in local state. The toolbar switches the mounted board between fit and grow sizing.
tsximport { createContext, useContext, useState } from 'react';
import {
GrafloriaDashboard,
type WidgetProps,
type WidgetTypes,
} from '@grafloria/react';
import type { DashboardWidgetSpec } from '@grafloria/element';
const Team = createContext('Release team');
function Note({ widget, data }: WidgetProps) {
const team = useContext(Team);
const [acknowledgements, setAcknowledgements] = useState(0);
const text = typeof data.text === 'string' ? data.text : '';
return (
<section style={{
width: '100%', height: '100%', boxSizing: 'border-box',
padding: 16, background: '#eef2ff', color: '#232a3d',
borderRadius: 10, font: '14px/1.5 system-ui, sans-serif',
}}>
<strong>{widget.title}</strong>
<div>{team}</div>
<p>{text}</p>
<button onClick={() => setAcknowledgements((count) => count + 1)}>
Acknowledge ({acknowledgements})
</button>
</section>
);
}
const widgetTypes: WidgetTypes = { note: Note };
const widgets: DashboardWidgetSpec[] = [
{
id: 'revenue', kind: 'kpi', span: 3, rows: 1,
data: { label: 'Revenue', value: '$6.81M', delta: 12.4 },
},
{
id: 'release', kind: 'note', span: 3, rows: 1,
title: 'Release note', data: { text: 'Deployment approved for Friday.' },
},
];
export default function App() {
const [sizing, setSizing] = useState<'fit' | 'grow'>('fit');
return (
<Team.Provider value="Release team">
<button onClick={() => setSizing('fit')}>Fit rows</button>
<button onClick={() => setSizing('grow')}>Grow rows</button>
<span> Sizing: {sizing}</span>
<div style={{ height: 400 }}>
<GrafloriaDashboard
widgets={widgets}
widgetTypes={widgetTypes}
options={{ columns: 6, gap: 8, rowHeight: 180 }}
sizing={sizing}
/>
</div>
</Team.Provider>
);
}
Custom widgets also mount through portals. Switching sizing calls the board handle rather than remounting the board, so the note's acknowledgement state remains intact.
3. Mount a spec without React content registrations
Use GrafloriaDiagram when you already have a RenderSpec rather than React node or widget components. It accepts plain diagram specs as well as kit specs. This independent App.tsx renders two connected, shipped silhouettes without a nodeTypes registry.
tsximport { GrafloriaDiagram } from '@grafloria/react';
import type { RenderSpec } from '@grafloria/element';
const spec: RenderSpec = {
nodes: [
{
id: 'ingest', position: { x: 60, y: 80 },
size: { width: 180, height: 80 }, label: 'Ingest',
shape: { type: 'terminal', fill: '#ecfdf5', stroke: '#059669' },
},
{
id: 'publish', position: { x: 380, y: 80 },
size: { width: 180, height: 80 }, label: 'Publish',
shape: { type: 'document', fill: '#fdf4ff', stroke: '#9333ea' },
},
],
edges: [{ id: 'ingest-publish', source: 'ingest', target: 'publish' }],
};
export default function App() {
return (
<div style={{ height: 400 }}>
<GrafloriaDiagram spec={spec} options={{ fitView: true }} />
</div>
);
}
Unlike the mount-once dashboard inputs, a changed spec or options value replaces this component's diagram. An equal value rebuilt on a React render keeps the existing instance. For React components inside boxes, keep using GrafloriaFlow or GrafloriaDashboard above.
Options that matter
Board geometry lives in DashboardOptions. The following defaults come from the kit; the example explicitly selects fit sizing at first render.
| Option | Type | Default | What it does |
|---|---|---|---|
columns | number | 12 | Sets the column count for views without an override. |
gap | number | 8 | Sets the widget gap and board padding in pixels. |
rowHeight | number | 130 | Sets row height in grow mode. |
sizing | 'fit' | 'grow' | Grow for fluid boards; fit for fixed boards | Fit keeps the board height and squeezes rows; grow extends downward at rowHeight. |
layout | 'grid' | 'split' | 'grid' | Selects cells or a splitter tree; split always uses fit sizing. |
mode | 'fluid' | 'fixed' | Fluid unless width is supplied | Fluid uses the container's CSS dimensions; fixed uses authored world dimensions. |
Respect the mount-once board props
views, widgets and options seed the dashboard at mount. Replacing those props later does not rebuild or reconcile the board. Use either views for multiple boards or widgets for one board, not both.
The live props are activeView, layout, sizing and static. Changes call the handle's showView(), setLayout(), setSizing() and setStatic() respectively. Top-level layout, sizing and static override the corresponding options fields at mount.
For runtime widget edits, capture the DashboardHandle with onReady and use its widget methods rather than replacing the initial array. Use onLayoutChange to receive the affected view's widgets after committed gestures. See Build a dashboard for handle-driven editing.
Demos and related guides
- Custom React nodes: components inside connected nodes.
- Custom shapes: use shipped silhouettes when no component is needed.
- Dashboard builder: a data-first board with shipped widget kinds.
- React state and subscriptions: keep application state aligned with model events.
- Theme a canvas: size the canvas and integrate its appearance with your application.
Was this page helpful?