Skip to Content
📣 We just released Svelte Flow 1.0 Alpha — try it out and give us your feedback!
LearnCustomizing Svelte FlowCustom Nodes

Custom Nodes

A powerful feature of Svelte Flow is the ability to create custom nodes. This gives you the flexibility to render anything you want within your nodes. We generally recommend creating your own custom nodes rather than relying on built-in ones. With custom nodes, you can add as many source and target handles as you like—or even embed form inputs, charts, and other interactive elements.

In this section, we’ll walk through creating a custom node featuring an input field that updates text elsewhere in your application. For further examples, we recommend checking out our Custom Node Example.

Creating a Custom Node

To create a custom node, all you need to do is create a Svelte component. Svelte Flow will automatically wrap it in an interactive container that injects essential props like the node’s id, position, and data, and provides functionality for selection, dragging, and connecting handles. For a full reference on all available custom node props, take a look at the Node Props.

Let’s dive into an example by creating a custom node called TextUpdaterNode. For this, we’ve added a controlled input field with a oninput handler. We simply use the ‘text’ property from the node’s data for the input and we update the node’s data via the updateNodeData function, that can be accessed through the useSvelteFlow hook.

<script lang="ts"> import { Position, useSvelteFlow, type NodeProps } from '@xyflow/svelte'; let { id, data }: NodeProps = $props(); let { updateNodeData } = useSvelteFlow(); </script> <div class="text-updater-node"> <div> <label for="text">Text:</label> <input id="text" name="text" value={data.text} oninput={(evt) => { updateNodeData(id, { text: evt.target.value }); }} class="nodrag" /> </div> </div>

Adding the Node Type

Now we need to communicate the new custom node to Svelte Flow. You can add custom nodes by passing the nodeTypes prop.

<script> import { SvelteFlow } from '@xyflow/svelte'; import TextUpdaterNode from './TextUpdaterNode.svelte'; const nodeTypes = { textUpdater: TextUpdaterNode }; // [...] </script> <SvelteFlow bind:nodes bind:edges {nodeTypes} fitView > <!-- [...] --> </SvelteFlow>

After defining your new node type, you can refer to it by using the type node option:

const nodes = $state.raw([ { id: 'node-1', type: 'textUpdater', position: { x: 0, y: 0 }, data: { text: 'some text' }, }, ]);

After putting it all together and adding some basic styles we get a custom node that prints text to the console:

<script> import { SvelteFlow, Background } from '@xyflow/svelte'; import '@xyflow/svelte/dist/style.css'; import TextUpdaterNode from './TextUpdaterNode.svelte'; const nodeTypes = { textUpdater: TextUpdaterNode, }; let nodes = $state.raw([ { id: 'node-1', type: 'textUpdater', position: { x: 0, y: 0 }, data: { text: 'some text' }, }, ]); </script> <SvelteFlow bind:nodes {nodeTypes} fitView> <Background /> </SvelteFlow>

Adding Handles

Svelte Flow provides a Handle component that can be used to add handles to your custom nodes. It’s as easy as mounting the component.

CustomNode.svelte
<script> import { Handle } from '@xyflow/svelte'; </script> <Handle type="target" position={Position.Top} /> <Handle type="source" position={Position.Bottom} />

Multiple Handles

If you need more than just one source and target handle, you can use the id prop to distinguish between them. You only need the id prop if you have multiple handles of the same type.

The id of the handle just needs to be unique within a custom node type.

CustomNode.svelte
<Handle type="target" position={Position.Top} /> <Handle type="source" position={Position.Bottom} id="source-1" /> <Handle type="source" position={Position.Bottom} id="source-2" />

Positioning Handles

Though, the position prop is required for rendering connected edges, you can freely position your handles using CSS!

There are some aggressive styles applied based on the position. You have to override them first, like shown in the snippet below. We are planning to improve this in the future.

CustomNode.svelte
<div class="handle-container"> <Handle type="source" position={Position.Bottom} id="source-1" /> <Handle type="source" position={Position.Bottom} id="source-2" /> </div> <style> .handle-container { display: flex; width: 100%; justify-content: space-around; transform: translateY(100%); } .handle-container > :global(.svelte-flow__handle) { top: auto !important; bottom: auto !important; left: auto !important; right: auto !important; position: relative !important; transform: none !important; } </style>

Connecting to Handles

An edge is defined by a source node and a target node. However, if you have multiple source or target handles on a node, you need to specify a sourceHandleId or targetHandleId so the edge knows which handle to connect to.

Here, we only have a single target handle, so we just need to specify the sourceHandleId on the edges.

App.svelte
let edges = $state.raw([ { id: 'edge-1', source: 'node-1', sourceHandle: 'source-1', target: 'node-2' }, { id: 'edge-2', source: 'node-1', sourceHandle: 'source-2', target: 'node-3' }, ]);

Putting it all together we end up with a flow like this:

<script> import { SvelteFlow, Background } from '@xyflow/svelte'; import '@xyflow/svelte/dist/style.css'; import TextUpdaterNode from './TextUpdaterNode.svelte'; const nodeTypes = { textUpdater: TextUpdaterNode, }; let nodes = $state.raw([ { id: 'node-1', type: 'textUpdater', position: { x: 0, y: 0 }, data: { value: 123 }, }, { id: 'node-2', position: { x: 0, y: 200 }, data: { label: 'node 2' }, }, { id: 'node-3', position: { x: 200, y: 200 }, data: { label: 'node 3' }, }, ]); let edges = $state.raw([ { id: 'edge-1', source: 'node-1', sourceHandle: 'source-1', target: 'node-2' }, { id: 'edge-2', source: 'node-1', sourceHandle: 'source-2', target: 'node-3' }, ]); </script> <SvelteFlow bind:nodes bind:edges {nodeTypes} fitView> <Background /> </SvelteFlow>

Updating Handle Positions

Svelte Flow figuring out where all your Handles are might seem like magic, but it’s just boundingClientRect. Because it might cause reflows, it can be computationally expensive at times, so we make sure it is only called when the node resizes.

However, if you want to programmatically change the position or number of handles in your custom node, you have to call the useUpdateNodeInternals context function to notify Svelte Flow of the changes.

CustomNode.svelte
<script> import { useUpdateNodeInternals } from '@xyflow/svelte'; const updateNodeInternals = useUpdateNodeInternals(); </script>

When calling updateNodeInternals outside of a custom node, you have to pass a nodeId or an array of nodeIds you’d like to update.

Utility Classes

Svelte Flow provides several built-in utility CSS classes to help you fine-tune how interactions work within your custom nodes.

nodrag

In the example above, we added the class nodrag to the input. This ensures that interacting with the input field doesn’t trigger a drag, allowing you to select the text within the field.

Nodes have a drag class name in place by default. However, this class name can affect the behaviour of the event listeners inside your custom nodes. To prevent unexpected behaviours, add a nodrag class name to elements with an event listener. This prevents the default drag behavior as well as the default node selection behavior when elements with this class are clicked.

CustomNode.svelte
<div> <input class="nodrag" type="range" min={0} max={100} /> </div>

nowheel

If your custom node contains scrollable content, you can apply the nowheel class. This disables the canvas’ default pan behavior when you scroll inside your custom node, ensuring that only the content scrolls instead of moving the entire canvas.

CustomNode.svelte
<div class="nowheel" style="overflow: auto"> <p>Scrollable content...</p> </div>

Applying these utility classes helps you control interaction on a granular level. You can customize these class names through Svelte Flow style props.

When creating your own custom nodes, you will also need to remember to style them! Unlike the built-in nodes, custom nodes have no default styles, so feel free to use any styling method you prefer, such as Svelte’s scoped CSS.

Last updated on