Composable React node editor canvas with configurable nodes, connections, and inspector UI.
npm install react-wireflowReact 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.
Requires React ^19.2.0 (uses useEffectEvent).
``bash`
npm install react-wireflow
`tsx`
import "react-wireflow/style.css";
`tsx
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
}
`
- Breaking: NodeRenderProps was removed. Use NodeRendererProps instead.
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.
`tsx
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 (
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"}
>
{context.port.label}
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):`tsx
import { defaultEditorGridConfig, defaultEditorGridLayers } from "react-wireflow";
`Or define custom layouts:
`tsx
gridConfig={{
areas: [["canvas", "inspector"]],
rows: [{ size: "1fr" }],
columns: [{ size: "1fr" }, { size: "300px", resizable: true }],
}}
gridLayers={[
{ id: "canvas", component: , gridArea: "canvas" },
{ id: "inspector", component: , gridArea: "inspector" },
]}
/>
`Add floating layers:
`tsx
const layers = [
...defaultEditorGridLayers,
{
id: "minimap",
component: ,
positionMode: "absolute",
position: { right: 10, bottom: 10 },
draggable: true,
},
];
`Drawer for mobile:
`tsx
{ id: "panel", component: , drawer: { placement: "right", open: isOpen } }
`See examples for complete implementations.
Custom Inspector Panels
The Inspector panel can be customized at three levels:
$3
Define
renderInspector in your node definition to provide custom inspector content when that node type is selected:`tsx
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) {
const data = node.data ?? {};
return (
value={data.name ?? ""}
onChange={(e) => onUpdateNode({ data: { ...data, name: e.target.value } })}
/>
type="email"
value={data.email ?? ""}
onChange={(e) => onUpdateNode({ data: { ...data, email: e.target.value } })}
/>
);
}
const PersonNode = createNodeDefinition({
type: "person",
displayName: "Person",
renderInspector: PersonInspector,
});
`InspectorRenderProps provides:
- node - The selected node with typed data
- onUpdateNode(updates) - Callback to update node properties
- onDeleteNode() - Callback to delete the node
- externalData, isLoadingExternalData, externalDataError - External data state
- onUpdateExternalData(data) - Callback to update external data$3
Replace or extend the default tabs (Layers, Properties, Settings) by passing
tabs to InspectorPanel:`tsx
import {
InspectorPanel,
InspectorLayersTab,
InspectorPropertiesTab,
InspectorSettingsTab,
InspectorSection,
PropertySection,
type InspectorPanelTabConfig,
} from "react-wireflow";// Custom tab component
const StatisticsTab = () => (
Total nodes: 10
);
const customTabs: InspectorPanelTabConfig[] = [
{ id: "layers", label: "Layers", render: () => },
{ id: "properties", label: "Properties", render: () => },
{ id: "stats", label: "Stats", render: () => },
{ id: "settings", label: "Settings", render: () => },
];
`$3
Add panels to the Settings tab using
settingsPanels:`tsx
import {
InspectorPanel,
InspectorDefinitionList,
InspectorDefinitionItem,
InspectorButton,
type InspectorSettingsPanelConfig,
} from "react-wireflow";const ExportPanel = () => {
return (
alert("Exporting...")}>Export
);
};
const settingsPanels: InspectorSettingsPanelConfig[] = [
{ title: "Export Options", component: ExportPanel },
];
`$3
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 wrapper |
| InspectorDefinitionItem | Label-value pair (/) |
| 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.