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:
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.
<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.
<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.
<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.
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:
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.
<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.
<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.
<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.