Svelte Flow Documentation

website·Indexed 34 days ago

https://svelteflow.dev

Documentation for Svelte Flow, including installation guides for Svelte Flow Vite and SvelteKit templates, implementation examples, and an API reference for the SvelteFlowProvider and SvelteFlow components.

Tokens
23K
Snippets
130
Records
148
Agent score
21%

What's inside Svelte Flow

  1. Add a MiniMap overview to the flow

    The <MiniMap /> component renders a small-scale overview of the entire flow, visualizing the current viewport. It should be placed as a child of the <SvelteFlow /> component.
    <script lang="ts">
      import { SvelteFlow, MiniMap } from '@xyflow/svelte';
      import '@xyflow/svelte/dist/style.css';
    
      let nodes = $state.raw([]);
      let edges = $state.raw([]);
    </script>
    
    <SvelteFlow bind:nodes bind:edges>
      <MiniMap nodeStrokeWidth={3} />
    </SvelteFlow>
  2. Create a custom edge

    Custom edges in Svelte Flow are Svelte components. They typically use the <BaseEdge /> component to render the SVG path. To calculate the path, use utility functions like getStraightPath, getBezierPath, or getSmoothStepPath.

    To register a custom edge, define an edgeTypes object (outside the component or using $derived to avoid re-renders) and pass it to the SvelteFlow component. Then, set the type of an edge to the key defined in edgeTypes.

    <!-- CustomEdge.svelte -->
    <script lang="ts">
      import { BaseEdge, getStraightPath, type EdgeProps } from '@xyflow/svelte';
    
      let { id, sourceX, sourceY, targetX, targetY }: EdgeProps = $props();
    
      let [edgePath] = $derived(
        getStraightPath({
          sourceX, sourceY, targetX, targetY,
        })
      );
    </script>
    
    <BaseEdge {id} path={edgePath} />
    <!-- App.svelte -->
    <script lang="ts">
      import { SvelteFlow, type EdgeTypes } from '@xyflow/svelte';
      import CustomEdge from './CustomEdge.svelte';
    
      const edgeTypes: EdgeTypes = {
        'custom-edge': CustomEdge
      };
    
      let edges = $state.raw([
        { id: 'a->b', type: 'custom-edge', source: 'a', target: 'b' },
      ]);
    </script>
    
    <SvelteFlow {edgeTypes} bind:edges />
  3. Create custom edge paths in Svelte Flow

    Custom edges are created by making a Svelte component that uses the BaseEdge component. You define the SVG path for the edge using the sourceX, sourceY, targetX, and targetY props provided by EdgeProps. To use these custom edges, map them in the edgeTypes object and pass that object to the SvelteFlow component. The type property of an edge in the edges array must match the key defined in edgeTypes.
    <!-- StepEdge.svelte -->
    <script lang="ts">
      import { BaseEdge, type EdgeProps } from '@xyflow/svelte';
    
      let { id, sourceX, sourceY, targetX, targetY }: EdgeProps = $props();
    
      const centerY = $derived((targetY - sourceY) / 2 + sourceY);
      const edgePath = $derived(
        `M ${sourceX} ${sourceY} L ${sourceX} ${centerY} L ${targetX} ${centerY} L ${targetX} ${targetY}`,
      );
    </script>
    
    <BaseEdge {id} path={edgePath} />
    <!-- App.svelte -->
    <script lang="ts">
      import { SvelteFlow, type Node, type Edge, type EdgeTypes } from '@xyflow/svelte';
      import StepEdge from './StepEdge.svelte';
    
      const edgeTypes: EdgeTypes = {
        steps: StepEdge,
      };
    
      let nodes = $state.raw<Node[]>([
        { id: 'a', position: { x: 0, y: 0 }, data: { label: 'Node A' } },
        { id: 'b', position: { x: 200, y: 100 }, data: { label: 'Node B' } },
      ]);
      let edges = $state.raw<Edge[]>([
        { id: 'a->b', type: 'steps', source: 'a', target: 'b' },
      ]);
    </script>
    
    <SvelteFlow bind:nodes bind:edges {edgeTypes} />
  4. Implement a basic Svelte Flow component

    To create a working flow, use the <SvelteFlow /> component. Key requirements include:

    1. Styles: You must import the Svelte Flow stylesheet (@xyflow/svelte/dist/style.css).
    2. Dimensions: <SvelteFlow /> inherits the size of its parent; it must be wrapped in an element with defined dimensions (e.g., 100vw/100vh).
    3. Performance: Use $state.raw for nodes and edges instead of deeply reactive state to avoid performance issues.
    <script>
      import {
        SvelteFlow,
        Controls,
        MiniMap,
        Background,
        BackgroundVariant,
      } from '@xyflow/svelte';
      import '@xyflow/svelte/dist/style.css';
    
      let nodes = $state.raw([
        { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } },
        { id: '2', position: { x: 0, y: 100 }, data: { label: '2' } },
      ]);
    
      let edges = $state.raw([
        { id: 'e1-2', source: '1', target: '2' },
      ]);
    </script>
    
    <div style:width="100vw" style:height="100vh">
      <SvelteFlow bind:nodes bind:edges fitView colorMode="system">
        <Controls />
        <MiniMap />
        <Background variant={BackgroundVariant.Dots} gap={12} size={1} />
      </SvelteFlow>
    </div>
  5. Style custom nodes and edges in Svelte Flow

    Custom nodes and edges are standard Svelte components. Because they have no default styles, you must provide your own. You can use Svelte's scoped styles or global CSS with @apply to keep styling isolated. If using a third-party solution like Tailwind CSS, ensure the Svelte Flow stylesheet is imported into the base layer so Tailwind can override the defaults.
    /* index.css */
    @import '@xyflow/svelte/dist/style.css' layer(base);
    @import 'tailwindcss';
  6. Implement connection handles in custom nodes

    The <Handle /> component is used to define connection points on custom nodes. Handles can be of type source or target. To support multiple handles of the same type on a single node, assign a unique id to each handle. Edges can then target these specific handles using the sourceHandle and targetHandle properties.
    <script lang="ts">
      import { Handle, Position, type NodeProps } from '@xyflow/svelte';
    
      let { data } : NodeProps = $props();
    </script>
    
    <div>
      {data.label}
    </div>
    
    <Handle type="target" position={Position.Left} />
    <Handle type="source" position={Position.Right} />
  7. Create custom edges with TypeScript

    Custom edges extend the Edge type. Use EdgeProps to access coordinates (sourceX, sourceY, targetX, targetY) and getStraightPath to calculate the SVG path for the edge. Render the path using the BaseEdge component.
    <script module>
      import type { Edge } from '@xyflow/svelte';
      export type EdgeType = Edge<{ value: number }, 'custom'>;
    </script>
    
    <script lang="ts">
      import { getStraightPath, BaseEdge, type EdgeProps } from '@xyflow/svelte';
      let { id, sourceX, sourceY, targetX, targetY }: EdgeProps<EdgeType> = $props();
      let [edgePath] = $derived(getStraightPath({ sourceX, sourceY, targetX, targetY }));
    </script>
    
    <BaseEdge {id} path={edgePath} />
  8. Implement custom nodes in Svelte Flow

    Custom nodes are Svelte components that receive NodeProps (including id, position, and data). Svelte Flow wraps these components in an interactive container for dragging and selection. To register a custom node, add it to a nodeTypes object and pass that object to the SvelteFlow component. Use the type property in the node definition to specify which custom component to render. To update node data from within the custom node, use the updateNodeData function from the useSvelteFlow hook.
    <!-- TextUpdaterNode.svelte -->
    <script lang="ts">
      import { useSvelteFlow, type NodeProps } from '@xyflow/svelte';
    
      let { id, data }: NodeProps = $props();
      let { updateNodeData } = useSvelteFlow();
    </script>
    
    <div class="text-updater-node">
      <label for="text">Text:</label>
      <input
        id="text"
        value={data.text}
        oninput={(evt) => {
          updateNodeData(id, { text: evt.target.value });
        }}
        class="nodrag"
      />
    </div>
    <!-- App.svelte -->
    <script>
      import { SvelteFlow } from '@xyflow/svelte';
      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} />
  9. Create and manage nodes in Svelte Flow

    Nodes in Svelte Flow are defined as an array of objects. Each node must have a unique id, a position (x and y coordinates), and a data object for custom information.

    To ensure optimal performance in Svelte, use the $state.raw rune for the nodes array. Using standard $state would make every property of every node reactive, which can lead to performance degradation.

    let nodes = $state.raw([
      {
        id: 'n1',
        position: { x: 0, y: 0 },
        data: { label: 'Node 1' },
        type: 'input',
      },
      {
        id: 'n2',
        position: { x: 100, y: 100 },
        data: { label: 'Node 2' },
        type: 'output',
      },
    ]);
  10. Display a toolbar on custom edges

    The <EdgeToolbar /> component displays a toolbar at specific x and y coordinates on an edge. By default, it is only visible when the edge is selected, but this can be overridden using the isVisible prop.
    <script lang="ts">
    	import { EdgeToolbar, getBezierPath, BaseEdge, type EdgeProps } from '@xyflow/svelte';
    
    	let { id, sourceX, sourceY, targetX, targetY }: EdgeProps = $props();
    
    	let [edgePath, centerX, centerY] = $derived(
    		getBezierPath({ sourceX, sourceY, targetX, targetY })
    	);
    </script>
    
    <BaseEdge {id} path={edgePath} />
    <EdgeToolbar edgeId={id} x={centerX} y={centerY} isVisible>
    	<button>some button</button>
    </EdgeToolbar>
  11. Implement automatic node layout using elkjs

    Svelte Flow can be integrated with elkjs to automatically calculate node positions. Because layout algorithms run asynchronously, you must handle the promises and update the Svelte state. The process involves converting the Svelte Flow graph structure into an ELK-compatible format, calculating the layout, and mapping the resulting coordinates back to the nodes.
    <script>
      import { SvelteFlow, Background, Panel, useSvelteFlow } from '@xyflow/svelte';
      import '@xyflow/svelte/dist/style.css';
      import ELK from 'elkjs/lib/elk.bundled.js';
      import { initialNodes, initialEdges } from './nodes-edges.js';
    
      let nodes = $state.raw([...initialNodes]);
      let edges = $state.raw([...initialEdges]);
      let layouting = $state.raw(false);
      const elk = new ELK();
    
      const { fitView } = useSvelteFlow();
    
      async function getLayoutedElements(nodes, edges, options) {
        if (!elk) return { nodes, edges };
    
        const graph = {
          id: 'root',
          layoutOptions: options,
          children: nodes.map((node) => ({
            id: node.id,
            width: node.measured?.width ?? 150,
            height: node.measured?.height ?? 40,
          })),
          edges: edges.map((edge) => ({
            id: edge.id,
            source: edge.source,
            target: edge.target,
          })),
        };
    
        const elkGraph = await elk.layout(graph);
    
        const layoutedNodes = nodes.map((node) => {
          const elkNode = elkGraph.children.find((n) => n.id === node.id);
          if (elkNode) {
            return { ...node, position: { x: elkNode.x, y: elkNode.y } };
          }
          return node;
        });
    
        return { nodes: layoutedNodes, edges };
      }
    
      async function onLayout(algorithm) {
        if (layouting) return;
        layouting = true;
        try {
          let options = {};
          if (algorithm === 'layered') {
            options = {
              'elk.algorithm': 'layered',
              'elk.direction': 'DOWN',
              'elk.layered.spacing.nodeNodeBetweenLayers': '100',
              'elk.spacing.nodeNode': '80',
            };
          } else if (algorithm === 'force') {
            options = { 'elk.algorithm': 'force', 'elk.force.iterations': '300' };
          } else if (algorithm === 'radial') {
            options = { 'elk.algorithm': 'radial' };
          } else if (algorithm === 'tree') {
            options = { 'elk.algorithm': 'mrtree' };
          }
    
          const layoutedElements = await getLayoutedElements(nodes, edges, options);
          nodes = [...layoutedElements.nodes];
          edges = [...layoutedElements.edges];
          fitView();
        } catch (error) {
          console.error('Error during layout calculation:', error);
        } finally {
          layouting = false;
        }
      }
    </script>
    
    <SvelteFlow bind:nodes bind:edges fitView minZoom={0.01} colorMode="system">
      <Panel position="top-right">
        <div style="display: flex; gap: 5px;">
          <button disabled={layouting} onclick={() => onLayout('layered')}>layered layout</button>
          <button disabled={layouting} onclick={() => onLayout('force')}>force layout</button>
          <button disabled={layouting} onclick={() => onLayout('radial')}>radial layout</button>
          <button disabled={layouting} onclick={() => onLayout('tree')}>tree layout</button>
        </div>
      </Panel>
      <Background />
    </SvelteFlow>