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