Overview
At its core, React Flow is about creating interactive flowgraphs — a collection of nodes connected by edges. To help you understand the terminology we use throughout the documentation, let’s take a look at the example flow below.
Example: learn/basic-terms
AnnotationNode.jsx
import { memo } from 'react';
import { MoveDown } from 'lucide-react';
function AnnotationNode({ data }) {
return (
<>
<div className="annotation-content">
<div>{data.label}</div>
</div>
{data.arrowStyle && (
<div className={`annotation-arrow ${data.arrowStyle}`}>
<MoveDown />
</div>
)}
</>
);
}
export { AnnotationNode };
export default memo(AnnotationNode);App.jsx
import {
ReactFlow,
ReactFlowProvider,
MarkerType,
Background,
Panel,
useViewport,
useConnection,
} from '@xyflow/react';
import { useCallback, useState } from 'react';
import { AnnotationNode } from './AnnotationNode';
import NodeWrapper from './NodeWrapper';
import '@xyflow/react/dist/style.css';
const nodeTypes = {
annotation: AnnotationNode,
};
const connectionAnnotation = {
id: 'connection-annotation',
type: 'annotation',
selectable: false,
data: {
label: 'this is a "connection"',
arrowStyle: 'arrow-top-left',
},
position: { x: 0, y: 0 },
};
const initialNodes = [
{
id: 'annotation-1',
type: 'annotation',
draggable: false,
selectable: false,
data: {
label: 'This is a "node"',
arrowStyle: 'arrow-bottom-right',
},
position: { x: -65, y: -50 },
},
{
id: '1-1',
type: 'default',
data: {
label: 'node label',
},
position: { x: 150, y: 0 },
},
{
id: 'annotation-2',
type: 'annotation',
draggable: false,
selectable: false,
data: {
label: 'This is a "handle"',
arrowStyle: 'arrow-top-left',
},
position: { x: 235, y: 35 },
},
{
id: 'annotation-3',
type: 'annotation',
draggable: false,
selectable: false,
data: {
level: 2,
label: 'This is an "edge"',
arrowStyle: 'arrow-top-right',
},
position: { x: 20, y: 120 },
},
{
id: '1-2',
type: 'default',
data: {
label: 'node label',
},
position: { x: 350, y: 200 },
},
{
id: 'annotation-4',
type: 'annotation',
draggable: false,
selectable: false,
data: {
label: 'Try dragging the handle',
arrowStyle: 'arrow-top-left',
},
position: { x: 430, y: 240 },
},
];
const initialEdges = [
{
id: 'e1-2',
source: '1-1',
target: '1-2',
label: 'edge label',
type: 'smoothstep',
},
{
id: 'e2-2',
source: '1-2',
target: '2-2',
type: 'smoothstep',
markerEnd: {
type: MarkerType.ArrowClosed,
},
},
];
function ViewportWithAnnotation() {
const viewport = useViewport();
return (
<>
<NodeWrapper bottom={0} left={90} width={420}>
<AnnotationNode
data={{
label:
'The viewport is defined by x, y and zoom, which is the transform & scale applied to the flow.',
}}
/>
</NodeWrapper>
<div
style={{
fontFamily: 'monospace',
background: 'var(--xy-theme-panel-bg)',
color: 'var(--xy-theme-panel-text)',
padding: '5px',
borderRadius: '3px',
}}
>
<div style={{ fontFamily: 'monospace' }}>
<div>x: {viewport.x.toFixed(2)}</div>
<div>y: {viewport.y.toFixed(2)}</div>
<div>zoom: {viewport.zoom.toFixed(2)}</div>
</div>
</div>
</>
);
}
function Flow() {
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
const connection = useConnection();
const onMouseMove = useCallback(() => {
if (connection.inProgress) {
const { from, to } = connection;
const nodePosition = {
x: to.x,
y: to.y,
};
setNodes((prevNodes) => {
const nodeExists = prevNodes.some((node) => node.id === 'connection-annotation');
if (nodeExists) {
return prevNodes.map((node) =>
node.id === 'connection-annotation'
? {
...node,
position: nodePosition,
hidden: Math.abs(to.y - from.y) < 25 && Math.abs(to.x - from.x) < 25,
}
: node,
);
} else {
return [
...prevNodes,
{
...connectionAnnotation,
position: nodePosition,
hidden: Math.abs(to.y - from.y) < 25 && Math.abs(to.x - from.x) < 25,
},
];
}
});
}
}, [connection]);
const onConnectEnd = useCallback(() => {
setNodes((prevNodes) =>
prevNodes.filter((node) => node.id !== 'connection-annotation'),
);
}, []);
return (
<div style={{ width: '100%', height: '100%' }} onMouseMove={onMouseMove}>
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
onConnectEnd={onConnectEnd}
fitView
preventScrolling={false}
colorMode="system"
>
<Background />
<Panel position="bottom-left">
<ViewportWithAnnotation />
</Panel>
</ReactFlow>
</div>
);
}
function FlowWithProvider() {
return (
<ReactFlowProvider>
<Flow />
</ReactFlowProvider>
);
}
export default FlowWithProvider;NodeWrapper.jsx
const NodeWrapper = ({ children, bottom, top, left, right, width, height }) => {
const toPx = (value) => (value !== undefined ? `${value}px` : undefined);
return (
<div
className="react-flow__node-annotation"
style={{
position: 'absolute',
fontSize: '12px',
bottom: toPx(bottom),
left: toPx(left),
width: toPx(width),
height: toPx(height),
top: toPx(top),
right: toPx(right),
}}
>
{children}
</div>
);
};
export default NodeWrapper;xy-theme.css
/* xyflow theme files. Delete these to start from our base */
.react-flow {
--xy-background-color: #f7f9fb;
/* Custom Variables */
--xy-theme-selected: #f57dbd;
--xy-theme-hover: #c5c5c5;
--xy-theme-edge-hover: black;
--xy-theme-color-focus: #e8e8e8;
/* Built-in Variables see https://reactflow.dev/learn/customization/theming */
--xy-node-border-default: 1px solid #ededed;
--xy-node-boxshadow-default:
0px 3.54px 4.55px 0px #00000005, 0px 3.54px 4.55px 0px #0000000d,
0px 0.51px 1.01px 0px #0000001a;
--xy-node-border-radius-default: 8px;
--xy-handle-background-color-default: #ffffff;
--xy-handle-border-color-default: #aaaaaa;
--xy-edge-label-color-default: #505050;
--xy-node-background-color-default: #ffffff;
--xy-theme-panel-bg: #ffffff;
--xy-theme-panel-text: #111827;
--xy-theme-resize-handle-bg: #ffffff;
--xy-theme-focus-border: #d9d9d9;
--xy-theme-muted-bg: #f3f4f6;
--xy-theme-subtle-border: #d1d5db;
}
.react-flow.dark {
--xy-background-color: #101012;
--xy-node-background-color-default: #1a1b1e;
--xy-node-border-default: 1px solid #303238;
--xy-handle-background-color-default: #141519;
--xy-handle-border-color-default: #5a5d66;
--xy-edge-label-color-default: #e6e7ea;
--xy-theme-hover: #7a7d86;
--xy-theme-edge-hover: #f3f4f6;
--xy-theme-panel-bg: #141519;
--xy-theme-panel-text: #f3f4f6;
--xy-theme-resize-handle-bg: #141519;
--xy-theme-focus-border: #5f6470;
--xy-theme-muted-bg: #24262c;
--xy-theme-subtle-border: #3f434c;
--xy-node-boxshadow-default:
0px 6px 18px 0px rgba(0, 0, 0, 0.35), 0px 2px 6px 0px rgba(0, 0, 0, 0.3),
0px 1px 2px 0px rgba(0, 0, 0, 0.35);
--xy-theme-color-focus: #4b4e57;
--color-background: #1a1b1e;
--color-hover-bg: #24262c;
--color-disabled: #8b9099;
}
.react-flow {
background-color: var(--xy-background-color);
}
/* Customizing Default Theming */
.react-flow__node {
box-shadow: var(--xy-node-boxshadow-default);
border-radius: var(--xy-node-border-radius-default);
background-color: var(--xy-node-background-color-default);
display: flex;
justify-content: center;
align-items: center;
text-align: center;
padding: 10px;
font-size: 12px;
flex-direction: column;
border: var(--xy-node-border-default);
color: var(--xy-node-color, var(--xy-node-color-default));
}
.react-flow__node.selectable:focus {
box-shadow: 0px 0px 0px 4px var(--xy-theme-color-focus);
border-color: var(--xy-theme-focus-border);
}
.react-flow__node.selectable:focus:active {
box-shadow: var(--xy-node-boxshadow-default);
}
.react-flow__node.selectable:hover,
.react-flow__node.draggable:hover {
border-color: var(--xy-theme-hover);
}
.react-flow__node.selectable.selected {
border-color: var(--xy-theme-selected);
box-shadow: var(--xy-node-boxshadow-default);
}
.react-flow__node-group {
background-color: rgba(207, 182, 255, 0.4);
border-color: #9e86ed;
}
.react-flow.dark .react-flow__node-group {
background-color: rgba(123, 107, 148, 0.22);
border-color: #7b6b94;
}
.react-flow__edge.selectable:hover .react-flow__edge-path,
.react-flow__edge.selectable.selected .react-flow__edge-path {
stroke: var(--xy-theme-edge-hover);
}
.react-flow__handle {
background-color: var(--xy-handle-background-color-default);
}
.react-flow__handle.connectionindicator:hover {
pointer-events: all;
border-color: var(--xy-theme-edge-hover);
background-color: var(--xy-handle-background-color-default);
}
.react-flow__handle.connectionindicator:focus,
.react-flow__handle.connectingfrom,
.react-flow__handle.connectingto {
border-color: var(--xy-theme-edge-hover);
}
.react-flow__node-resizer {
border-radius: 0;
border: none;
}
.react-flow__resize-control.handle {
background-color: var(--xy-theme-resize-handle-bg);
border-color: #9e86ed;
border-radius: 0;
width: 5px;
height: 5px;
}
/*
Custom Example CSS - This CSS is to improve the example experience.
You can remove it if you want to use the default styles.
New Theme Classes:
.xy-theme__button - Styles for buttons.
.xy-theme__input - Styles for text inputs.
.xy-theme__checkbox - Styles for checkboxes.
.xy-theme__select - Styles for dropdown selects.
.xy-theme__label - Styles for labels.
Use these classes to apply consistent theming across your components.
*/
:root {
--color-primary: #ff0073;
--color-background: #fefefe;
--color-hover-bg: #f6f6f6;
--color-disabled: #76797e;
}
.xy-theme__button-group {
display: flex;
align-items: center;
.xy-theme__button:first-child {
border-radius: 100px 0 0 100px;
}
.xy-theme__button:last-child {
border-radius: 0 100px 100px 0;
margin: 0;
}
}
/* Custom Button Styling */
.xy-theme__button {
display: inline-flex;
align-items: center;
justify-content: center;
height: 2.5rem;
padding: 0 1rem;
border-radius: 100px;
border: 1px solid var(--color-primary);
background-color: var(--color-background);
color: var(--color-primary);
transition:
background-color 0.2s ease,
border-color 0.2s ease;
box-shadow: var(--xy-node-boxshadow-default);
cursor: pointer;
}
.xy-theme__button.active {
background-color: var(--color-primary);
color: white;
border-color: var(--color-primary);
}
.xy-theme__button.active:hover,
.xy-theme__button.active:active {
background-color: var(--color-primary);
opacity: 0.9;
}
.xy-theme__button:hover {
background-color: var(--xy-controls-button-background-color-hover-default);
}
.xy-theme__button:active {
background-color: var(--color-hover-bg);
}
.xy-theme__button:disabled {
color: var(--color-disabled);
opacity: 0.8;
cursor: not-allowed;
border: 1px solid var(--color-disabled);
}
.xy-theme__button > span {
margin-right: 0.2rem;
}
/* Add gap between adjacent buttons */
.xy-theme__button + .xy-theme__button {
margin-left: 0.3rem;
}
/* Example Input Styling */
.xy-theme__input {
padding: 0.5rem 0.75rem;
border: 1px solid var(--color-primary);
border-radius: 7px;
background-color: var(--color-background);
transition:
background-color 0.2s ease,
border-color 0.2s ease;
font-size: 1rem;
color: var(--xy-theme-panel-text);
}
.xy-theme__input:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(255, 0, 115, 0.3);
}
/* Specific Checkbox Styling */
.xy-theme__checkbox {
appearance: none;
-webkit-appearance: none;
width: 1.25rem;
height: 1.25rem;
border-radius: 7px;
border: 2px solid var(--color-primary);
background-color: var(--color-background);
transition:
background-color 0.2s ease,
border-color 0.2s ease;
cursor: pointer;
display: inline-block;
vertical-align: middle;
margin-right: 0.5rem;
}
.xy-theme__checkbox:checked {
background-color: var(--color-primary);
border-color: var(--color-primary);
}
.xy-theme__checkbox:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(255, 0, 115, 0.3);
}
/* Dropdown Styling */
.xy-theme__select {
padding: 0.5rem 0.75rem;
border: 1px solid var(--color-primary);
border-radius: 50px;
background-color: var(--color-background);
transition:
background-color 0.2s ease,
border-color 0.2s ease;
font-size: 1rem;
color: var(--xy-theme-panel-text);
margin-right: 0.5rem;
box-shadow: var(--xy-node-boxshadow-default);
}
.xy-theme__select:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(255, 0, 115, 0.3);
}
.xy-theme__label {
margin-top: 10px;
margin-bottom: 3px;
display: inline-block;
color: var(--xy-theme-panel-text);
}index.css
@import url('./xy-theme.css');
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
/* Annotation Node */
.react-flow__node-annotation {
font-size: 16px;
max-width: 300px;
color: #683bfa;
position: absolute;
box-shadow: none;
font-family: monospace;
text-align: left;
background-color: transparent;
border: none;
pointer-events: none;
}
.react-flow__node-annotation .annotation-content {
padding: 10px;
display: flex;
}
.react-flow__node-annotation .annotation-level {
margin-right: 4px;
}
.react-flow__node-annotation .annotation-arrow {
position: absolute;
font-size: 24px;
}
.arrow-top-right {
right: 0;
bottom: 0;
transform: translate(10px, -25px) rotate(-140deg);
position: absolute;
}
.arrow-top-left {
left: 0;
bottom: 0;
transform: translate(-5px, -25px) rotate(145deg) scale(-1, 1);
position: absolute;
}
.arrow-bottom-left {
left: 0;
bottom: 0;
transform: translate(0px, -20px) rotate(90deg);
position: absolute;
}
.arrow-bottom-right {
right: 0;
bottom: 0;
transform: translate(10px, 5px) rotate(-60deg);
position: absolute;
}
.arrow-handle {
left: 0;
bottom: 0;
transform: translate(-15px, -25px) rotate(160deg) scale(-1, 1);
position: absolute;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);Nodes
React Flow has a few default node types out of the box, but customization is where the magic of React Flow truly happens. You can design your nodes to work exactly the way you need them to—whether that’s embedding interactive form elements, displaying dynamic data visualizations, or even incorporating multiple connection handles. React Flow lays the foundation, and your imagination does the rest.
We have a guide on creating your own Custom Nodes and you can find all the options for customizing your nodes in the Node reference.
Handles
A handle (also known as a “port” in other libraries) is the attachment point where an edge
connects to a node. By default, they appear as grey circles on the top, bottom, left, or
right sides of a node. But they are just div elements, and can be positioned and styled
any way you’d like. When creating a custom node, you can include as many handles as
needed. For more information, refer to the Handle page.
Edges
Edges connect nodes. Every edge needs a target and a source node. React Flow comes with
four built-in edge types: default (bezier), smoothstep,
step, and straight. An edge is a SVG path that can be styled with CSS and is
completely customizable. If you are using multiple handles, you can reference them
individually to create multiple connections for a node.
Like custom nodes, you can also customize edges. Things that people do with custom edges include:
- Adding buttons to remove edges
- Custom routing behavior
- Complex styling or interactions that cannot be solved with just one SVG path
For more information, refer to the Edges page.
Selection
You can select an edge or a node by clicking on it. If you want to select multiple
nodes/edges via clicking, you can hold the Meta/Control key and click on multiple
elements to select them. If you want to change the keyboard key for multiselection to
something else, you can use the
multiSelectionKeyCode prop.
You can also select multiple edges/nodes by holding down Shift and dragging the mouse to
make a selection box. When you release the mouse, any node or edge that falls within the
selection box is selected. If you want to change the keyboard key for this behavior, you
can use the selectionKeyCode prop.
Selected nodes and edges are elevated (assigned a higher zIndex than other elements), so
that they are shown on top of all the other elements.
For default edges and nodes, selection is shown by a darker stroke/border than usual. If
you are using a custom node/edge, you can use the selected prop to customize selection
appearance inside your custom component.
Connection line
React Flow has built-in functionality that allows you to click and drag from one handle to another to create a new edge. While dragging, the placeholder edge is referred to as a connection line. The connection line comes with the same four built-in types as edges and is customizable. You can find the props for configuring the connection line in the connection props reference.
Viewport
All of React Flow is contained within the viewport. Each node has an x- and y-coordinate, which indicates its position within the viewport. The viewport has x, y, and zoom values. When you drag the pane, you change the x and y coordinates. When you zoom in or out, you alter the zoom level. You can read more about the viewport in the Viewport page.