Skip to content

Auto layout for React Flow sub-flows

React Flow has groups (sub-flows): a node with parentId is inside its parent node. React Flow does not lay out the nodes, and its layout guide lists dagre, d3-hierarchy, d3-force and ELK. A group needs a size that holds its children, and the children need positions inside it. layoutFlow gives both, with one strategy for each group.

Drag a node: its edges go straight between the handles. Select Lay out to put the nodes back on their routes.

Terminal window
npm install ireko @xyflow/react

ireko/xyflow needs @xyflow/react 12. The core of ireko does not.

Give layoutFlow your nodes and edges. It gives new nodes with positions, new groups with sizes, and new edges with routes. It keeps all other properties of your nodes and edges.

import "@xyflow/react/dist/style.css";
import { ReactFlow, type Edge, type Node } from "@xyflow/react";
import { edgeTypes, layoutFlow, nodeTypes } from "ireko/xyflow";
const nodes: Node[] = [
{ id: "server", type: "group", data: { label: "server" }, position: { x: 0, y: 0 } },
{ id: "api", parentId: "server", extent: "parent", data: { label: "api" }, position: { x: 0, y: 0 }, width: 84, height: 34 },
{ id: "auth", parentId: "server", extent: "parent", data: { label: "auth" }, position: { x: 0, y: 0 }, width: 84, height: 34 },
{ id: "web", data: { label: "web" }, position: { x: 0, y: 0 }, width: 84, height: 34 },
];
const edges: Edge[] = [
{ id: "web-api", source: "web", target: "api" },
{ id: "api-auth", source: "api", target: "auth" },
];
const laid = layoutFlow(nodes, edges, {
rootLayout: "grid",
nodeSpec: (n) => (n.id === "server" ? { layout: "radial" } : undefined),
});
export function Diagram() {
return <ReactFlow nodes={laid.nodes} edges={laid.edges} nodeTypes={nodeTypes} edgeTypes={edgeTypes} fitView />;
}
  • A node with children (through parentId) becomes a nested container. Its size holds its children.
  • A node with no children keeps its size: measured, then width and height, then the nodeSize option.
  • With hierarchy: "edges" (the default), an edge between two nodes of the same group also makes a tree. The target becomes a linked child of the source, if it has no parent yet. With hierarchy: "parent", only parentId makes the tree.
  • The other edges are extra edges. They connect nodes in different groups.
  • The nodes with no parent go into an invisible root, with the strategy rootLayout.

layoutFlow takes all layout options, and these:

OptionDefaultMeaning
rootLayout"grid"The strategy of the nodes with no parent.
nodeSpecnoneA function that gives the strategy, the mode and the fit of a node.
hierarchy"edges""edges": the edges in a group make a tree. "parent": only parentId makes the tree.
enginethe default engineAn engine with your strategies.
edgeType"ireko"The type of the edges that get a route.
  • edgeTypes registers PolylineEdge as the type ireko. It draws the route of ireko. React Flow keeps its markers, its labels and its selection. Without a route, the edge is a straight line between the handles.
  • nodeTypes registers GroupNode as the type group. It draws the label of the group, and has hidden handles, so an edge can end on a group.

Keep nodeTypes and edgeTypes outside your components. A new object on each render makes React Flow draw each node again, and React Flow gives a warning.

React Flow measures each node after the first render. For nodes with content of a variable size, lay out again after the measurement:

import { useNodesInitialized, useReactFlow } from "@xyflow/react";
function AutoLayout() {
const ready = useNodesInitialized();
const flow = useReactFlow();
useEffect(() => {
if (!ready) return;
const laid = layoutFlow(flow.getNodes(), flow.getEdges());
flow.setNodes(laid.nodes);
flow.setEdges(laid.edges);
flow.fitView();
}, [ready, flow]);
return null;
}

To draw a layout that you made with the core, use toXYFlow. It gives the nodes (a parent before its children, positions relative to the container) and the edges with routes.

import { layout } from "ireko";
import { toXYFlow, nodeTypes, edgeTypes } from "ireko/xyflow";
const { nodes, edges } = toXYFlow(layout(input));
<ReactFlow nodes={nodes} edges={edges} nodeTypes={nodeTypes} edgeTypes={edgeTypes} fitView />;

The playground uses toXYFlow for its React Flow view.

irekodagreELK (elkjs)
Groups (sub-flows)Yes, each group with its own strategyClusters, with known problems for sub-flowsYes, with many options
Strategiesgrid, radial, tidy tree, layers, yourslayeredlayered, stress, tree, radial, force and more
Edge routesstraight, orthogonal, around boxes, yourspolylineorthogonal, splines, polyline
APIsynchronoussynchronousasynchronous
Size (min + gzip)approximately 13 KBapproximately 17 KBapproximately 470 KB

For a large directed graph with layers, use dagre or ELK. For nested groups with a layout for each group, use ireko. The comparison page has more detail.