astro react flow

published: 6/15/2026

written by: Stefan Johansson

7 min read

Post hero imagePost hero image

React Flow is a mature open-source library for building node-and-edge diagrams — flowcharts, architecture maps, pipelines — with panning, zooming, and dragging built in. It is React, though, and Astro pages are mostly static HTML. @sjohansson/astro-reactflow bridges that gap: it wraps React Flow in a single component you can drop into any Astro page, and an integration that handles the React wiring for you.

Every diagram in this post is live — drag the nodes, zoom with the scroll wheel, open one in fullscreen, and switch the site theme to watch the colors follow. It’s part of the Astro Components collection.

What the wrapper adds

You could wire React Flow into Astro yourself — I did exactly that in an earlier post that builds the component from scratch. The package packages up that work, plus the polish, into one component:

And, importantly, an Astro integration that registers React and applies the build settings React Flow needs — so you don’t have to.

Setup: the integration does the wiring

Install the package alongside its peers:

pnpm add @sjohansson/astro-reactflow @astrojs/react @xyflow/react react react-dom

Then register the integration once:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import reactFlow from '@sjohansson/astro-reactflow/integration';

export default defineConfig({
  integrations: [reactFlow()],
});

That single line does two jobs. First, it auto-registers @astrojs/react if it isn’t already there — so you don’t have to add react() yourself. (If it’s already registered, the integration detects that and skips it.) Second, it applies the Vite SSR settings React Flow needs: it adds @xyflow/react to ssr.noExternal and pre-bundles the CommonJS dependencies React Flow and Zustand pull in, which otherwise trip up the build.

Note

If @astrojs/react is missing and auto-registration is turned off, the integration throws a clear error at config-load time — instead of letting the build fail later with React Flow’s cryptic NoMatchingRenderer message.

Then use the component, with one rule:

---
import { ReactFlowWrapper } from '@sjohansson/astro-reactflow';
---

<ReactFlowWrapper
  client:only="react"
  title="Basic Process Flow"
  nodes={[
    { id: '1', type: 'input', label: 'Start', position: { x: 250, y: 0 } },
    { id: '2', label: 'Process Data', position: { x: 250, y: 110 } },
    { id: '3', type: 'output', label: 'Complete', position: { x: 250, y: 220 } },
  ]}
  edges={[
    { id: 'e1-2', source: '1', target: '2', animated: true },
    { id: 'e2-3', source: '2', target: '3' },
  ]}
  height={400}
/>
Important

client:only="react" is required. React Flow measures the DOM and has no server-rendered output, so the diagram must render entirely in the browser as an Astro island. Use client:only, not client:load — there is nothing to hydrate from.

A first diagram

Here is that pattern rendered for real — the lifecycle of one of these diagrams on an Astro page, with a minimap turned on:

Tip

Click the expand icon in the top-right corner to open the diagram in fullscreen focus mode — handy for larger diagrams. Press Esc to return.

The data model

A diagram is just two arrays: nodes and edges. The wrapper uses a friendly, flat shape so you rarely touch React Flow’s internals directly.

A node needs an id, a label, and a position. The type is optional — input and output get distinct styling and minimap colors:

interface DiagramNode {
  id: string;
  type?: 'default' | 'input' | 'output' | 'group';
  label: string;                      // hoisted to React Flow's data.label for you
  position: { x: number; y: number };
}

An edge connects two node ids and can be animated, labelled, or restyled:

interface DiagramEdge {
  id: string;
  source: string;
  target: string;
  label?: string;
  animated?: boolean;
  type?: 'default' | 'straight' | 'step' | 'smoothstep' | 'bezier';   // default: smoothstep
  markerEnd?: 'arrow' | 'arrowclosed' | boolean;   // overrides the diagram default
  strokeWidth?: number;                            // overrides the diagram default
}
Note

If you read the earlier from-scratch post, the shape will look familiar — but the API differs. That local component takes a single definition={{ nodes, edges }} object; the published wrapper takes nodes and edges as separate top-level props. Same building blocks, slightly tidier ergonomics.

Branching diagrams, arrows, and backgrounds

Nodes can fan out, edges can carry arrowheads, and the canvas background has three variants. This one maps how this very page is built, using defaultMarkerEnd for arrows on every edge and the lines background:

The description prop renders in a footer bar under the canvas — useful for a caption that travels with the diagram.

Make it interactive

By default diagrams are static to read: you can pan and zoom, but nodes stay put. Set interactive and readers can drag nodes, select them, and draw new connections between handles:

Tip

Drag from the small handle on the edge of a node to another node to draw a new connection. Interactive diagrams are great for editors and playgrounds; leave interactive off for documentation you want readers to study, not rearrange.

Export to PNG or SVG

Set enableExport and the diagram gains PNG and SVG export buttons in the top-left — so a diagram on the page can become an image in a slide deck or doc:

Theming: it follows the page

By default the wrapper runs colorMode="auto", which reads the host page’s theme and re-evaluates whenever it changes. It looks at <html> for a dark/scheme-dark class, a data-theme-scheme="dark" attribute, or a data-theme value containing "dark", and falls back to the OS prefers-color-scheme.

That means it works out of the box with the @sjohansson/astro-theme-toggle component this site uses — flip the theme in the footer and every diagram above recolors with no extra code. Pin it with colorMode="light" or colorMode="dark" if you’d rather it not follow along.

The colors themselves are CSS custom properties on .reactflow-wrapper, so you can map the diagram onto your own design tokens:

.reactflow-wrapper {
  --arf-surface: var(--my-surface-color);       /* canvas background */
  --arf-chrome-surface: var(--my-chrome-color); /* title / footer / controls */
  --arf-border: var(--my-border-color);         /* borders and node outlines */
  --arf-text: var(--my-text-color);             /* text and edge strokes */
}

The resolved mode is also mirrored as data-color-mode="light|dark" on the wrapper, so you can target either state from your own CSS.

Props worth knowing

The full list is in the package README; these are the ones you’ll reach for most:

PropTypeDefaultPurpose
nodes / edgesarraysThe diagram itself.
title / descriptionstringHeader bar / footer caption.
height / widthnumber | string400 / "100%"Container size (numbers are px).
showMiniMapbooleanfalseShow the color-coded minimap.
backgroundVariant"dots" | "lines" | "cross""dots"Canvas background pattern.
interactivebooleanfalseAllow drag / connect / select.
allowFocusModebooleantrueShow the fullscreen expand button.
enableExportbooleanfalseShow PNG / SVG export buttons.
colorMode"auto" | "light" | "dark""auto"Theme tracking.
defaultMarkerEnd"arrow" | "arrowclosed" | booleanfalseArrowheads for all edges.
Note

React, React Flow, and @astrojs/react are all peer dependencies — you bring your own versions and the package slots into them, so there’s never a second copy of React in your bundle.

Wrapping up

@sjohansson/astro-reactflow turns React Flow into something you can use on an Astro page in two steps: one integration line, one client:only component. You get focus mode, a minimap, theming, and export for free, and the diagram stays a client island so the rest of your page is still plain static HTML.

If you’d like to see how the same result is built by hand — the React component, the Astro wrapper, the theming glue — that’s the subject of the from-scratch diagrams post. And for the theme switching these diagrams ride along with, see astro-theme-toggle.

Tip

MIT-licensed and open source — npm · source.