react-wireflow
v0.7.2
Published
Composable React node editor canvas with configurable nodes, connections, and inspector UI.
Maintainers
Readme
react-wireflow
React components for building node-based workflow editors with TypeScript support.
Demo: https://trkbt10.github.io/react-wireflow/
Type-safe node definitions, customizable renderers, grid-based layouts, settings persistence, undo/redo, i18n.
Installation
Requires React ^19.2.0 (uses useEffectEvent).
npm install react-wireflowimport "react-wireflow/style.css";Usage
import { NodeEditor, createNodeDefinition } from "react-wireflow";
const MyNode = createNodeDefinition({
type: "my-node",
displayName: "My Node",
ports: [
{ id: "in", type: "input", position: "left" },
{ id: "out", type: "output", position: "right" },
],
});
function App() {
const [data, setData] = useState({ nodes: {}, connections: {} });
return <NodeEditor data={data} onDataChange={setData} nodeDefinitions={[MyNode]} />;
}Changelog
Unreleased
- Breaking:
NodeRenderPropswas removed. UseNodeRendererPropsinstead.
Custom ports and connections
Declare renderPort per port definition to override the visual while keeping editor interactions. The second argument renders the default dot, which you can keep for accessibility hitboxes or replace entirely. Always forward context.handlers and honor context.position (x, y, transform) for correct anchoring.
const CustomPorts = createNodeDefinition({
type: "custom-ports",
displayName: "Custom Ports",
ports: [
{
id: "emit",
type: "output",
label: "Emit",
position: "right",
dataType: ["text", "html"],
renderPort: (context, defaultRender) => {
if (!context.position) return defaultRender();
const { x, y, transform } = context.position;
return (
<div
style={{ position: "absolute", left: x, top: y, transform: transform ?? "translate(-50%, -50%)" }}
onPointerDown={context.handlers.onPointerDown}
onPointerUp={context.handlers.onPointerUp}
onPointerEnter={context.handlers.onPointerEnter}
onPointerMove={context.handlers.onPointerMove}
onPointerLeave={context.handlers.onPointerLeave}
onPointerCancel={context.handlers.onPointerCancel}
data-state={context.isConnectable ? "ready" : context.isHovered ? "hovered" : "idle"}
>
<span className="port-dot" />
<span className="port-label">{context.port.label}</span>
</div>
);
},
renderConnection: (context, defaultRender) => {
// Example: decorate connected lines; fall back to default during previews
if (!context.connection) return defaultRender();
return defaultRender();
},
},
],
});PortRenderContext includes port, node, allNodes, allConnections, booleans (isConnecting, isConnectable, isCandidate, isHovered, isConnected), optional position, and pointer handlers you must preserve. ConnectionRenderContext provides phase, fromPort, toPort, their positions, selection/hover flags, and handlers for pointer/cxtmenu; use it to add badges or halos while keeping hit-testing intact. For dynamic ports, set instances, createPortId, and createPortLabel on the port definition (see src/examples/demos/custom/ports/port-playground for a complete playground).
Panels
Use defaultEditorGridLayers for built-in panels (canvas, inspector, statusbar):
import { defaultEditorGridConfig, defaultEditorGridLayers } from "react-wireflow";
<NodeEditor gridConfig={defaultEditorGridConfig} gridLayers={defaultEditorGridLayers} />Or define custom layouts:
<NodeEditor
gridConfig={{
areas: [["canvas", "inspector"]],
rows: [{ size: "1fr" }],
columns: [{ size: "1fr" }, { size: "300px", resizable: true }],
}}
gridLayers={[
{ id: "canvas", component: <NodeCanvas />, gridArea: "canvas" },
{ id: "inspector", component: <InspectorPanel />, gridArea: "inspector" },
]}
/>Add floating layers:
const layers = [
...defaultEditorGridLayers,
{
id: "minimap",
component: <YourMinimap />,
positionMode: "absolute",
position: { right: 10, bottom: 10 },
draggable: true,
},
];Drawer for mobile:
{ id: "panel", component: <MyPanel />, drawer: { placement: "right", open: isOpen } }See examples for complete implementations.
Custom Inspector Panels
The Inspector panel can be customized at three levels:
1. Per-Node Custom Inspector (renderInspector)
Define renderInspector in your node definition to provide custom inspector content when that node type is selected:
import {
createNodeDefinition,
PropertySection,
InspectorInput,
InspectorDefinitionList,
InspectorDefinitionItem,
type InspectorRenderProps,
} from "react-wireflow";
type PersonNodeData = {
name: string;
email: string;
};
// Custom inspector component (function name starts with uppercase to use hooks)
function PersonInspector({ node, onUpdateNode }: InspectorRenderProps<PersonNodeData>) {
const data = node.data ?? {};
return (
<PropertySection title="Person Details">
<InspectorDefinitionList>
<InspectorDefinitionItem label="Name">
<InspectorInput
value={data.name ?? ""}
onChange={(e) => onUpdateNode({ data: { ...data, name: e.target.value } })}
/>
</InspectorDefinitionItem>
<InspectorDefinitionItem label="Email">
<InspectorInput
type="email"
value={data.email ?? ""}
onChange={(e) => onUpdateNode({ data: { ...data, email: e.target.value } })}
/>
</InspectorDefinitionItem>
</InspectorDefinitionList>
</PropertySection>
);
}
const PersonNode = createNodeDefinition({
type: "person",
displayName: "Person",
renderInspector: PersonInspector,
});InspectorRenderProps provides:
node- The selected node with typeddataonUpdateNode(updates)- Callback to update node propertiesonDeleteNode()- Callback to delete the nodeexternalData,isLoadingExternalData,externalDataError- External data stateonUpdateExternalData(data)- Callback to update external data
2. Custom Inspector Tabs
Replace or extend the default tabs (Layers, Properties, Settings) by passing tabs to InspectorPanel:
import {
InspectorPanel,
InspectorLayersTab,
InspectorPropertiesTab,
InspectorSettingsTab,
InspectorSection,
PropertySection,
type InspectorPanelTabConfig,
} from "react-wireflow";
// Custom tab component
const StatisticsTab = () => (
<InspectorSection>
<PropertySection title="Statistics">
<p>Total nodes: 10</p>
</PropertySection>
</InspectorSection>
);
const customTabs: InspectorPanelTabConfig[] = [
{ id: "layers", label: "Layers", render: () => <InspectorLayersTab /> },
{ id: "properties", label: "Properties", render: () => <InspectorPropertiesTab /> },
{ id: "stats", label: "Stats", render: () => <StatisticsTab /> },
{ id: "settings", label: "Settings", render: () => <InspectorSettingsTab panels={[]} /> },
];
<InspectorPanel tabs={customTabs} />3. Custom Settings Panels
Add panels to the Settings tab using settingsPanels:
import {
InspectorPanel,
InspectorDefinitionList,
InspectorDefinitionItem,
InspectorButton,
type InspectorSettingsPanelConfig,
} from "react-wireflow";
const ExportPanel = () => {
return (
<InspectorDefinitionList>
<InspectorDefinitionItem label="Format">
<select><option>JSON</option><option>YAML</option></select>
</InspectorDefinitionItem>
<InspectorDefinitionItem label="">
<InspectorButton onClick={() => alert("Exporting...")}>Export</InspectorButton>
</InspectorDefinitionItem>
</InspectorDefinitionList>
);
};
const settingsPanels: InspectorSettingsPanelConfig[] = [
{ title: "Export Options", component: ExportPanel },
];
<InspectorPanel settingsPanels={settingsPanels} />Available Inspector UI Components
Build consistent inspector UIs with these components:
Layout Components:
| Component | Description |
|-----------|-------------|
| PropertySection | Titled section with header |
| InspectorSection | Basic section container |
| InspectorSectionTitle | Standalone section title (H4) |
| InspectorDefinitionList | Semantic <dl> wrapper |
| InspectorDefinitionItem | Label-value pair (<dt>/<dd>) |
| InspectorField | Field wrapper with label |
| PositionInputsGrid | Grid layout for position/size inputs |
Form Inputs:
| Component | Description |
|-----------|-------------|
| InspectorInput | Styled text input |
| InspectorNumberInput | Number input with label |
| InspectorTextarea | Multi-line text input |
| InspectorSelect | Styled select dropdown |
| InspectorLabel | Standalone form label |
| ReadOnlyField | Non-editable display field |
Interactive:
| Component | Description |
|-----------|-------------|
| InspectorButton | Button (variants: primary, secondary, danger) |
| InspectorButtonGroup | Segmented button control for options |
| InspectorShortcutButton | Compact button for shortcut settings |
| InspectorShortcutBindingValue | Keyboard/pointer shortcut display |
See the Custom Inspector example for a complete implementation.
