Skip to content

React Flow 13 and Svelte Flow 2 - #5866

Draft
moklick wants to merge 327 commits into
mainfrom
renovate
Draft

React Flow 13 and Svelte Flow 2#5866
moklick wants to merge 327 commits into
mainfrom
renovate

Conversation

@moklick

@moklick moklick commented Jul 8, 2026

Copy link
Copy Markdown
Member

React Flow 13 and Svelte Flow 2

This is the PR for the next major versions of both libraries. We will collect the most important changes in this description, so that you can follow the process. Many updates affect both libraries. These are not migration guides, just WIP lists of the most important changes.

React Flow 13

For React Flow this is mostly a maintenance release. One of the driving factors for this was the need to update Zustand to the latest version.

  • Set minimum Zustand version to 5
  • Set minimum React version to 18 (we might even drop 18 this is TBD)
  • rename useStore to useReactFlowStore
  • make "@xyflow/react/style.css" available
  • drop colorMode prop but always use system/website mode via CSS only (overwritable via forceColorMode)
  • lib CSS is on "xyflow" layer
  • add hooks like useNode / useEdge
  • add a new theme
  •  add helper for changing a parent node

Svelte Flow 2

  • change API for hooks that take params (from $derived(useNodeConnections({ id: 'a' })) to useNodeConnections(() => { id : 'a' })
  • drop colorMode prop but always use system/website mode via CSS only (overwritable via forceColorMode)
  • rename useStore to useSvelteFlowStore
  • make "@xyflow/svelte/style.css" available
  • lib CSS is on "xyflow" layer
  • add onnodeschange / onedgeschange
  • add hooks like useNode / useEdge
  • add a new theme
  •  add helper for changing a parent node

General updates

We wanted to use this release to do some general cleanup and maintenance.

  • Revise testing (WIP)
  • Move from custom rollup script to vite library mode + tsc
  • Add publint checking
  • Cleanup eslint config & update to v10

https://github.com/xyflow/internal/issues/198

🤫 Psst — @xyflow/vue is in the works, and it's ready for early testing! Take a look and let us know what you think.

moklick and others added 30 commits June 22, 2026 14:35
Handle is the highest-multiplicity element (~2/node). Consolidate the 7
connection-state `toRef`s (all derived from the same global `connection*`
store state, used only in the class binding) into one `connectionClasses`
computed, and drop the redundant isConnectableStart/isConnectableEnd refs
(props default to true). ~13 → ~5 reactive effects per handle, identical
rendered classes.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…nders

`connectionClasses` returned a fresh object on every recompute, so Vue's
reference-identity gate re-ran the class binding's render effect each time.
During a connection drag `connectionEndHandle` invalidates every handle's
computed on each intermediate target change, even though only the two
endpoints' classes actually change — so every visible handle re-rendered
repeatedly, scaling with on-screen handle count.

Reuse the previous object when all class flags are unchanged (via the
computed's `prev` getter arg — the Vue equivalent of React's `useShallow`).
Measured in vue-flow on a 12-node / 24-handle connection drag: 312 -> 48
handle re-renders (~6.5x), with no change to the emitted classes.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
refactor(vue): consolidate Handle's per-instance reactivity
chore(colormode):use system color mode by default, add forceColorMode
`NodeWrapper`'s `getStyle` pxifies top-level `node.width`/`node.height` but
spreads `node.style` verbatim. `@xyflow/react` relies on React auto-appending
`px` to numeric CSS dimensions, so `style: { width: 380 }` sizes a node there;
Vue's `:style` binding does not auto-append units, so the numeric value is
invalid CSS and silently dropped — the node falls back to its content size.
This breaks porting react examples that size nodes via `style` (e.g. the
Overview example's group container + sub-nodes), and `@xyflow/vue`'s own
Overview example relies on numeric `style.width`.

Coerce numeric `style.width`/`style.height` to px in `getStyle`, mirroring the
existing top-level width/height handling (string values like '50%' untouched).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Arrow-key node movement (`useUpdateNodePositions`) used a custom
`calcNextPosition` that ignored `nodeOrigin` and reused the node's stale
`positionAbsolute`. With a non-default origin (e.g. `node-origin="[0.5, 0]"`)
the origin offset re-applied on every key press, so the node drifted by
`origin * dimensions` each time — jumping (e.g.) left by half its width
regardless of which arrow was pressed, and further than the intended step.

Mirror xyflow/react's `useMoveSelectedNodes`: use the origin-aware
`calculateNodePosition` from @xyflow/system (which derives both `position`
and `positionAbsolute`, accounting for origin, extent + parent) and push the
recomputed `positionAbsolute` instead of the stale one. Also snap the next
position when `snapToGrid` is set, matching react.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…e v-model getter

`commitEdges` wrote `state.edges = next` then passed `state.edges` to
`updateConnectionLookup`. Because edges are backed by a `defineModel` v-model
ref, that write round-trips through the parent asynchronously — so on the same
tick the `state.edges` getter still returns the pre-commit value. The connection
lookup was therefore rebuilt from stale (usually empty) edges and left empty
until the *next* commit, so `useNodeConnections` (and anything derived from it,
e.g. a connection-limit `isConnectable`) only updated a frame late — in practice
only after an unrelated re-render such as selecting the node.

Feed `updateConnectionLookup` the local `next` array we just committed instead,
mirroring how `commitNodes` adopts its local array into `nodeLookup`. `edgeLookup`
was already synced from `next`, so this makes the two lookups consistent.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…yflow/react

`ConnectionLine` recomputed the line's `from`/`to` points with
`getHandlePosition(node, handle, position)` — omitting the `center` argument, so
the points landed on the handle's `position` edge (e.g. the right-center for a
`Position.Right` handle). xyflow/react and `@xyflow/system` always resolve
connection endpoints with `center: true` (`XYHandle` computes `connection.from`
via `getHandlePosition(..., Position.Left, true)`, and closest-handle matching
uses center too), i.e. the geometric center of the handle bounds.

For a normal dot-sized handle center ≈ edge, so this was invisible. But when the
handle covers the whole node (the "easy connect" pattern where the entire node
is a handle), the line anchored to the node's right/left edge instead of its
center. Pass `center: true` for both endpoints to match react/system.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
peterkogo and others added 30 commits August 6, 2026 12:24
fix(vue): bind XYDrag on every create so node dragging works
test(vue): add Playwright generic-tests + vue config
chore(changeset): carry over xyflow/vue changesets
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment