Installation
Add Kookie Flow and its peer dependencies to a React 19 app, then give the canvas a box with a height so it has room to draw.
Kookie Flow is a React component that draws a node graph with WebGL. You install it with React Three Fiber and three.js, which do the drawing, and KookieUI v2, which supplies the theme and the controls. It needs React 19 and a browser with WebGL 2.
Choose the package
@kushagradhawan/kookie-flow-react is the normal React entry. The renderer lives in
@kushagradhawan/kookie-flow-webgl; graph logic lives in @kushagradhawan/kookie-flow-core.
The old @kushagradhawan/kookie-flow import forwards to the new packages.
These 0.2.0 candidates are not published. Build all sibling packages and pack them with pnpm:
Terminalpnpm install --frozen-lockfilepnpm --filter @kushagradhawan/kookie-flow... run buildpnpm test:packagespnpm --filter '@kushagradhawan/kookie-flow*' -r pack --pack-destination ./artifactsInstall all four archives in your app, together with
vendor/kushagradhawan-kookie-ui-react-0.0.0-flow.2.tgz. This UI archive preserves the tested Flow
appearance. Current UI v2 source is a separate upgrade and is not interchangeable with it.
Install the renderer peers
| Package | Supported range |
|---|---|
react, react-dom | ^19.2.8 |
three | ~0.186.0 |
@react-three/fiber | ^9.7.0 |
@react-three/drei | ^10.7.8 |
@kushagradhawan/kookie-ui-react | 0.0.0-flow.2 from the supplied archive |
The core alone needs no React, Three, KookieUI or browser globals. Node tooling and CJS require Node 24+. Both module formats and their declarations are tested outside the monorepo.
KookieFlow uses hooks and WebGL. In Next.js App Router, import it from a "use client" file.
The canvas has no stylesheet of its own: its appearance comes from the KookieUI stylesheet.
Add the theme
Import the stylesheet once and put a Theme at the root of your app. The canvas reads its
colours and corners from its own inherited Theme scope, so the graph follows the theme's
light or dark appearance and its radius. The Theming chapter covers what
the canvas reads.
app/layout.tsximport "@kushagradhawan/kookie-ui-react/styles.css";import { Theme } from "@kushagradhawan/kookie-ui-react";export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <Theme>{children}</Theme> </body> </html> );}Give the canvas a height
The canvas fills its parent: its container is 100% wide and 100% tall. A parent with no height of its own resolves that 100% to zero, and the graph draws into a box you can't see. Give the parent a height, or place it in a layout that hands it one.
app/editor/page.tsx"use client";import { KookieFlow, type Edge, type Entity } from "@kushagradhawan/kookie-flow-react";const entities: Entity[] = [ { id: "a", type: "note", position: { x: 0, y: 0 }, data: { label: "First node" } },];const edges: Edge[] = [];export default function EditorPage() { return ( <div style={{ display: "grid", gridTemplateRows: "auto 1fr", height: "100dvh" }}> <header>My editor</header> <KookieFlow entities={entities} edges={edges} /> </div> );}The grid gives its second row whatever height the header leaves, and the canvas fills that
row. A fixed height such as height: 480 on a wrapping div works too. The canvas watches
its box, so it redraws when the box changes size.
entities and edges sit at module scope here on purpose. The canvas rebuilds its copy of
the graph whenever it receives a new array, so an array written inline in JSX would rebuild
it on every render. Controlled and uncontrolled state explains how the canvas
holds your graph.
Import from the plugins subpath
Three hooks live in a second entry point, @kushagradhawan/kookie-flow-react/plugins:
useClipboardcopies, cuts and pastes the selection. See Clipboard and cloning.useKeyboardShortcutsbinds your own keys. See Keyboard.useContextMenuholds the state of a right-click or long-press menu you draw yourself. See Context menus.
useClipboard reads the canvas's internal store, so call it from a component you render as a
child of KookieFlow. Called anywhere else, it throws.
import { KookieFlow, type Edge, type Entity } from "@kushagradhawan/kookie-flow-react";import { useClipboard, useKeyboardShortcuts } from "@kushagradhawan/kookie-flow-react/plugins";function ClipboardKeys() { const { copy, cut, paste } = useClipboard(); useKeyboardShortcuts({ bindings: { "mod+c": copy, "mod+x": cut, "mod+v": () => paste() }, }); return null;}export function Board({ entities, edges }: { entities: Entity[]; edges: Edge[] }) { return ( <KookieFlow entities={entities} edges={edges}> <ClipboardKeys /> </KookieFlow> );}These bindings listen on the whole page, so mod+c copies the graph's selection even while
someone copies text elsewhere on it. Clipboard and cloning shows how to limit
them to the canvas.
A paste adds nodes to the canvas without reporting them to onEntitiesChange. Read
what is reported before you pair the
clipboard with state you hold yourself.
Use it with TypeScript
Both entry points ship their own type declarations. The declarations import types from
three, which ships none of its own, so add @types/three beside @types/react:
Terminalpnpm add -D @types/threeImport types with import type, so they leave no trace in your bundle:
import type { Edge, Entity, KookieFlowInstance } from "@kushagradhawan/kookie-flow-react";import type { UseClipboardReturn } from "@kushagradhawan/kookie-flow-react/plugins";Next, the Quick start builds a two-node graph you can wire up by dragging.