Getting started
InstallationQuick startControlled and uncontrolled state
Entities
The entity modelSockets and widgetsEntity typesFrames and groupsText and commentsImages, video and models
Edges and connections
EdgesConnections
Data
EvaluationSaving and exportingUndo and redoLayout and alignmentThe flow storeGraph queriesClipboard and cloning
Interaction
Canvas and cameraSelectionKeyboardSelection toolbarContext menusDrawingDropping and pasting media
Styling
ThemingEntity stylingFonts and text renderingMinimap
API reference
KookieFlowThe ref instanceHooksUtilitiesTypes
StartEntitiesDataAPI
MarkdownOpenAIClaude

Sockets and widgets

Give an entity typed inputs and outputs. An unconnected input can show a control, such as a slider or a text field, that sets its value.

A socket is a typed connection point on an entity. Inputs sit on the left edge and outputs on the right, and an edge always runs from an output to an input. When nothing is wired into an input, the input can show a widget, such as a slider or a text field, so a person can set its value by hand.

Arrow keys move between nodes. Press Enter to reach the focused node's controls, and Escape to come back here.

'use client';import { useEffect, useRef } from 'react';import { KookieFlow, useGraph, type Entity, type KookieFlowInstance } from '@kushagradhawan/kookie-flow';const initialEntities: Entity[] = [  {    id: 'value',    type: 'math/value',    position: { x: 0, y: 120 },    width: 160,    data: { label: 'Value' },    outputs: [{ id: 'out', name: 'Value', type: 'float' }],  },  {    id: 'render',    type: 'image/render',    position: { x: 240, y: 0 },    width: 300,    data: {      label: 'Render',      values: { steps: 30, mode: 'Cubic', live: true, fit: 'Fill', offset: [0, 1.5, -2], size: [1024, 768], seed: 42 },    },    inputs: [      {        id: 'prompt',        name: 'Prompt',        type: 'string',        layout: 'stacked',        widget: 'textarea',        rows: 3,        placeholder: 'Describe the image',      },      // Wire Value into Strength and its slider goes away.      { id: 'strength', name: 'Strength', type: 'float', defaultValue: 0.5 },      { id: 'steps', name: 'Steps', type: 'int' },      { id: 'mode', name: 'Mode', type: 'enum', options: ['Linear', 'Cubic'] },      { id: 'tiled', name: 'Tiled', type: 'boolean' },      { id: 'tint', name: 'Tint', type: 'color', defaultValue: '#3e63dd' },      { id: 'live', name: 'Live', type: 'boolean', widget: 'switch' },      { id: 'fit', name: 'Fit', type: 'enum', widget: 'segmented', options: ['Fit', 'Fill', 'Crop'] },      // Drag a component sideways to change it, or click it to type.      { id: 'offset', name: 'Offset', type: 'vec3', step: 0.1 },      { id: 'size', name: 'Size', type: 'vec2', step: 1, min: 64, max: 4096 },      { id: 'seed', name: 'Seed', type: 'seed' },    ],    outputs: [{ id: 'image', name: 'Image', type: 'image' }],  },];export default function NodesExample() {  const { entities, edges, onEntitiesChange, onEdgesChange, onConnect } = useGraph({ initialEntities });  const flowRef = useRef<KookieFlowInstance>(null);  useEffect(() => {    flowRef.current?.fitView({ padding: 40, maxZoom: 1 });  }, []);  return (    <div style={{ width: '100%', height: 640 }}>      <KookieFlow        ref={flowRef}        entities={entities}        edges={edges}        onEntitiesChange={onEntitiesChange}        onEdgesChange={onEdgesChange}        onConnect={onConnect}      />    </div>  );}

The Render node's inputs show ten of the eleven built-in widgets. Drag a wire from Value into Strength and the slider disappears, because the value now comes down the wire.

Add inputs and outputs

List sockets in an entity's inputs and outputs. Each socket needs an id, a name and a type:

import type { Entity } from '@kushagradhawan/kookie-flow';const resize: Entity = {  id: 'resize-1',  type: 'image/resize',  position: { x: 0, y: 0 },  data: { label: 'Resize' },  inputs: [    { id: 'image', name: 'Image', type: 'image' },    { id: 'width', name: 'Width', type: 'int', min: 1, max: 4096, defaultValue: 1024 },    { id: 'keepAspect', name: 'Keep aspect', type: 'boolean', defaultValue: true },  ],  outputs: [{ id: 'resized', name: 'Image', type: 'image' }],};

An edge names the sockets at its two ends by id, so keep ids stable once you save a graph. The name is the label the canvas draws beside the socket.

Outputs are laid out first, from the top of the body, and inputs follow underneath them. A node with no height grows to fit every row.

FieldTypeDefaultMeaning
idstringrequiredThe socket's id. Edges refer to it.
namestringrequiredThe label drawn beside the socket, and the accessible name of its widget.
typestringrequiredA key in the socket type table. Sets the colour, the compatibility and the default widget.
widgetWidgetType | falsefrom the typeThe widget to show. false shows none.
minnumberfrom the typeThe lowest value of a slider.
maxnumberfrom the typeThe highest value of a slider.
stepnumberfrom the typeThe increment a slider snaps to, and the decimal places a slider or number field shows.
optionsstring[]noneThe choices in a select.
optionLabelsRecord<string, string>noneWhat each option reads as, by value, such as { png: 'PNG' }. The value is still what's stored. An option with no entry reads as its value.
placeholderstringnoneThe text an empty text, text area, number or select widget shows.
defaultValueunknownnoneThe value the widget shows until the entity holds one.
layoutSocketLayoutMode'inline''inline' puts the widget beside the label. 'stacked' puts it under the label.
rowsnumber1Multiplies the widget's height.
heightnumbernoneThe row's height in world pixels. Overrides rows.
positionnumbernoneWhere the dot sits, as a fraction of the entity's height from 0 to 1. The label and widget keep their row.

Define socket types

A socket type gives every socket of that type a colour, a default widget and a rule about what it connects to. Pass your types to KookieFlow as socketTypes:

import { KookieFlow, type Edge, type Entity, type SocketType } from '@kushagradhawan/kookie-flow';// Declared outside the component, so the object is the same on every render.const socketTypes: Record<string, SocketType> = {  audio: { name: 'Audio', color: '#e5484d' },  gain: { name: 'Gain', color: '--jade-10', widget: 'slider', min: 0, max: 2, step: 0.05 },  frequency: { name: 'Frequency', color: '--sky-10', widget: 'number', step: 1 },  control: { name: 'Control', color: '--iris-10', compatibleWith: ['gain', 'frequency'] },};export function Synth({ entities, edges }: { entities: Entity[]; edges: Edge[] }) {  return <KookieFlow entities={entities} edges={edges} socketTypes={socketTypes} />;}
FieldTypeMeaning
namestringThe type's display name.
colorstringThe socket's colour.
compatibleWithstring[] | '*'Other types this one can connect to. '*' connects to every type.
widgetWidgetTypeThe widget an input of this type shows by default.
min, max, stepnumberThe values a socket of this type uses when it doesn't set its own.

color takes a hex colour such as '#e5484d'. It also takes a colour name from the graph's own palette, written as step 9 or step 10 of any AccentColor except gray, such as '--jade-10'. Step 10 is adjusted for light and dark mode. '--neutral-9' and '--neutral-10' read the neutral grey from your KookieUI theme.

Declare the table outside your component, or memoise it. A new object on every render makes the canvas rebuild every edge and socket on every render.

Compatibility

Two sockets are compatible when they have the same type, when either type is any, or when either type lists the other in compatibleWith. The rule works in both directions, so listing gain on control also lets a gain output connect to a control input.

In the default 'loose' connection mode, an incompatible wire still connects and is marked invalid. In 'strict' mode, the canvas refuses it. The Connections chapter covers both modes and the other ways to validate a wire.

The built-in socket types

DEFAULT_SOCKET_TYPES is always available, and your socketTypes are merged over it. An entry of yours with the same key replaces the built-in entry whole, so a float you define without a widget has no slider.

TypeColourWidget
floatblueslider, from 0 to 1 in steps of 0.01
intbluenumber, with min 0, max 100 and step 1
stringambertext
enumamberselect
booleanorangecheckbox
colorpinkcolor
image, maskpurplenone
meshvioletnone
signalcyannone
latent, model, conditioning, clip, vaetealnone
anyneutral greynone

A socket whose type isn't in the table draws in the colour of any.

Show a widget on an unconnected input

An input shows a widget when its socket names one in widget, or when its socket type has a default widget. Only inputs have widgets. When an edge lands on the input, the widget disappears and the input takes its value from the edge.

The socket's own settings win over the type's. min, max, step and dimensions fall back to the socket type, while options, placeholder, defaultValue and rows come only from the socket. Set widget: false on a socket to show no widget even when its type has one.

WidgetTypeWhat a person doesValue
sliderPresses or drags along the track. The value snaps to step.number
numberTypes into a number field.number, or '' when the field is empty
textTypes into a text field.string
textareaTypes into a multi-line field. The node shows the first line.string
selectPicks from a list of options. A select with no options doesn't open.string
checkboxPresses to toggle.boolean
colorPicks a colour in the browser's colour picker.string, such as '#3e63dd'
switchPresses anywhere on the row to toggle.boolean
segmentedPresses one of the options, shown side by side. Best with two to four.string
vectorDrags a component sideways to change it, or clicks it to type. dimensions sets 2, 3 or 4 components.number[]
seedTypes a whole number, or presses the die to roll one between min and max.number

The canvas draws every built-in widget itself. A text, text area, number or seed widget, or one component of a vector, opens a real input over itself while a person edits it, and closes it when they press Enter or Escape or click elsewhere. In a text area, Enter adds a new line. Built-in widgets aren't drawn, and can't be pressed, below 40% zoom.

showWidgets on KookieFlow defaults to true. Set it to false to hide every widget on the canvas, including the ones you supply.

Lay out a widget row

An inline row puts the socket's name on the left and the widget to its right. The widget starts after a gutter of socketLabelWidth, which defaults to 96 (SOCKET_LABEL_WIDTH), and the canvas shortens a socket name that doesn't fit the gutter. Widen the gutter when your names are long:

<KookieFlow entities={entities} edges={edges} socketLabelWidth={140} />

A stacked row puts the name above the widget and gives the widget the full width of the body. Use it for a text area, or for any widget that needs room:

{  id: 'prompt',  name: 'Prompt',  type: 'string',  layout: 'stacked',  widget: 'textarea',  rows: 4,  placeholder: 'Describe the image',}

rows multiplies the widget's height, in either layout. height sets the whole row's height in world pixels and takes precedence over rows.

showSocketLabels defaults to true. Set it to false to hide socket names on every entity.

Read widget changes

onWidgetChange reports every change a person makes, with the entity id, the socket id and the new value. A text field reports on every keystroke, and a slider reports on every pointer move while it's dragged.

A widget reads its value from entity.data.values, keyed by socket id, and falls back to the socket's defaultValue. Write each change back there:

'use client';import { useCallback } from 'react';import { KookieFlow, useGraph, type Entity } from '@kushagradhawan/kookie-flow';export function Editor({ initialEntities }: { initialEntities: Entity[] }) {  const { entities, edges, setEntities, onEntitiesChange, onEdgesChange, onConnect } = useGraph({    initialEntities,  });  const onWidgetChange = useCallback(    (entityId: string, socketId: string, value: unknown) => {      setEntities((current) =>        current.map((entity) => {          if (entity.id !== entityId) return entity;          const values = entity.data.values instanceof Object ? entity.data.values : {};          return { ...entity, data: { ...entity.data, values: { ...values, [socketId]: value } } };        })      );    },    [setEntities]  );  return (    <KookieFlow      entities={entities}      edges={edges}      onEntitiesChange={onEntitiesChange}      onEdgesChange={onEdgesChange}      onConnect={onConnect}      onWidgetChange={onWidgetChange}    />  );}

A built-in widget draws the value it finds in data.values, so a change you don't write back can disappear from the widget on its next redraw. For an unconnected input with a built-in widget, onEvaluate reads the value the widget shows, as the Evaluation chapter describes.

Custom controls

Socket widgets render in WebGL. Set widget to a supported WidgetType, or false to hide it. Inline React components, widgetTypes and ThemeComponent are no longer supported. To draw a new kind of node control, implement it in the WebGL renderer package. App toolbars and inspectors can use ordinary React components outside the graph and update data.values.

Getting started
  • Installation
  • Quick start
  • Controlled and uncontrolled state
Entities
  • The entity model
  • Sockets and widgets
  • Entity types
  • Frames and groups
  • Text and comments
  • Images, video and models
Edges and connections
  • Edges
  • Connections
Data
  • Evaluation
  • Saving and exporting
  • Undo and redo
  • Layout and alignment
  • The flow store
  • Graph queries
  • Clipboard and cloning
Interaction
  • Canvas and camera
  • Selection
  • Keyboard
  • Selection toolbar
  • Context menus
  • Drawing
  • Dropping and pasting media
Styling
  • Theming
  • Entity styling
  • Fonts and text rendering
  • Minimap
API reference
  • KookieFlow
  • The ref instance
  • Hooks
  • Utilities
  • Types
Reference
  • Live demo
  • llms.txt
  • GitHub
MIT licensed. © 2026 Kushagra Dhawan.
On this page
  • Add inputs and outputs
  • Define socket types
  • Compatibility
  • The built-in socket types
  • Show a widget on an unconnected input
  • Lay out a widget row
  • Read widget changes
  • Custom controls