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

Entity styling

Choose the size, variant, corner radius and title placement every node is drawn with, mark nodes with a line of colour, and override the resolved style where you need to.

Six props on KookieFlow set how every node on the board is drawn: size, variant, radius, header, accentHeader and entityStyle. Each one applies to the whole board. The only per-entity setting is color, which marks one node with a line of colour. The colours, padding and corners these props pick come from the theme's tokens, so the nodes keep matching your app in light and dark mode.

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

'use client';import { useState } from 'react';import { KookieFlow, useGraph, type Entity } from '@kushagradhawan/kookie-flow';import type { EntitySize, EntityVariant, HeaderPosition } from '@kushagradhawan/kookie-flow';const image = { name: 'Image', type: 'image' };const initialEntities: Entity[] = [  { id: 'load', type: 'default', position: { x: 0, y: 0 }, data: { label: 'Load image' },    outputs: [{ id: 'out', ...image }] },  { id: 'blur', type: 'default', position: { x: 300, y: 0 }, color: 'teal', data: { label: 'Blur' },    inputs: [{ id: 'in', ...image }, { id: 'radius', name: 'Radius', type: 'float' }],    outputs: [{ id: 'out', ...image }] },  { id: 'save', type: 'default', position: { x: 600, y: 0 }, data: { label: 'Save' },    inputs: [{ id: 'in', ...image }] },];function Choice<T extends string>(props: { value: T; options: T[]; onChange: (value: T) => void }) {  const pick = (value: string) => props.options.find((option) => option === value);  return (    <select value={props.value} onChange={(e) => { const next = pick(e.target.value); if (next) props.onChange(next); }}>      {props.options.map((option) => <option key={option}>{option}</option>)}    </select>  );}export default function EntityStylingExample() {  const [size, setSize] = useState<EntitySize>('2');  const [variant, setVariant] = useState<EntityVariant>('surface');  const [header, setHeader] = useState<HeaderPosition>('inside');  const { entities, edges, onEntitiesChange, onEdgesChange, onConnect } = useGraph({    initialEntities,    initialEdges: [      { id: 'e1', source: 'load', sourceSocket: 'out', target: 'blur', targetSocket: 'in' },      { id: 'e2', source: 'blur', sourceSocket: 'out', target: 'save', targetSocket: 'in' },    ],  });  return (    <div style={{ width: '100%' }}>      <div style={{ display: 'flex', gap: 12, paddingBottom: 12 }}>        <Choice value={size} options={['1', '2', '3', '4', '5']} onChange={setSize} />        <Choice value={variant} options={['surface', 'outline', 'soft', 'classic', 'ghost']} onChange={setVariant} />        <Choice value={header} options={['inside', 'outside', 'none']} onChange={setHeader} />      </div>      <div style={{ height: 360 }}>        <KookieFlow          entities={entities}          edges={edges}          onEntitiesChange={onEntitiesChange}          onEdgesChange={onEdgesChange}          onConnect={onConnect}          size={size}          variant={variant}          header={header}          defaultViewport={{ x: 32, y: 72, zoom: 0.75 }}        />      </div>    </div>  );}
PropTypeDefaultWhat it sets
sizeEntitySize'2'Padding, socket row and widget heights, and the corner when radius is unset
variantEntityVariant'surface'The body's fill, border and shadow
radiusEntityRadiusFollows sizeThe corner of the body and of its widgets
headerHeaderPosition'inside'Where the title is drawn, if at all
accentHeaderbooleanfalseA line of the theme's accent along the top edge of every node
entityStylePartial<EntityStyleOverrides>NoneColour, border, corner and shadow overrides

These props style nodes. Text, images, video, models, comments, reroutes and ink draw themselves, so a title, a body fill and an accent line never appear on them.

Pick a size

size takes '1', '2', '3', '4' or '5'. It picks a step on KookieUI's surface tokens for the padding (--surface-p-N) and the corner (--radius-surface-N), and the same step on its control tokens for the height and inset of widgets. Sizes '1' and '2' share a step.

sizeSurface and control step
'1'1, with smaller sockets, so socket rows can sit closer together
'2'1
'3'2
'4'3
'5'4

The default '2' uses KookieUI's first step, one below a page card's default. That's a judgment: a node is narrower than a page card, and you usually read a board zoomed out, so the page's default step spends too much of the node on padding.

size doesn't change the text. Titles and socket labels stay at a 12-pixel font size in world units, and zooming is what makes them larger.

Pick a variant

variant sets how the body is painted.

variantFillBorderShadow
'surface'--neutral-1 in light, --neutral-3 in dark--neutral-5A long, soft shadow
'outline'None--neutral-5None
'soft'--neutral-2 in light, --neutral-3 in darkNoneA long, soft shadow
'classic'--color-surfaceNoneA short drop shadow
'ghost'NoneNoneNone

A 'ghost' node draws no body at all, so its title, sockets and widgets sit straight on the canvas.

Node shadows don't read the Theme's depth setting, so a depth="flat" Theme still shows them. For a flat board, use 'outline', or set entityStyle.shadow to 'none'.

Set the corner radius

radius takes 'none', 'small', 'medium', 'large' or 'full'. From 'small' to 'full', the body reads --radius-surface-1 to --radius-surface-4, and 'none' is a square corner. The Theme's own radius setting decides what each of those tokens is worth.

Leave radius unset and the corner follows size: 'small' at sizes '1' and '2', then 'medium', 'large' and 'full' for '3', '4' and '5'. The same corner reaches the selection outline and every image, video and model on the board.

'full' is the largest card corner, not a pill, because the surface tokens stop at a rounded card.

Widgets read KookieUI's control corners instead. With radius set, 'small', 'medium' and 'large' read --radius-1 to --radius-3, and 'full' reads --radius-full. The Theme's own radius level decides what those are worth, so under a Theme at radius="full", KookieUI's default, every one of them draws a pill, and under radius="none" every one is square. With radius unset, a widget takes the Theme's control corner at the node's size.

Place the title

header decides where a node's title goes.

  • 'inside' draws the title in a band at the top of the body, above the first socket row.
  • 'outside' draws the title above the body. The title isn't inside the node's box, so leave room above each node for it.
  • 'none' draws no title and reserves no space for one.

The title is the entity's data.label, or its type when there's no label. A title that's wider than the node ends in an ellipsis. It's drawn in the semibold weight of the canvas font, which the Fonts and text rendering chapter covers.

Mark nodes with colour

There are two ways to mark nodes with colour, and both draw the same thin line inside the top edge of the body:

  • accentHeader draws the line on every node, in the theme's --accent-9, whatever header is.
  • color on an entity draws the line in a hue you choose, for that node only. It replaces the accentHeader line on that node.
pipeline.tsx
import type { Entity } from "@kushagradhawan/kookie-flow";const entities: Entity[] = [  {    id: "load",    type: "default",    position: { x: 0, y: 0 },    color: "teal",    data: { label: "Load image" },    outputs: [{ id: "image", name: "Image", type: "image" }],  },  {    id: "upscale",    type: "default",    position: { x: 300, y: 0 },    color: "orange",    data: { label: "Upscale" },    inputs: [{ id: "image", name: "Image", type: "image" }],  },];

color takes an AccentColor: gray, gold, bronze, brown, yellow, amber, orange, tomato, red, ruby, crimson, pink, plum, purple, violet, iris, indigo, blue, cyan, teal, jade, green, grass, lime, mint or sky. These hues ship with the package and look the same in light and dark mode, except gray, which reads the theme's --neutral-9. The theme's accent doesn't change them. A red node isn't your destructive colour either, so it stays the same red if your theme's destructive colour changes.

The line needs a body to sit on. It appears on 'surface', 'soft' and 'classic' nodes, and 'outline' and 'ghost' nodes draw no line. A name that isn't in the list logs one warning, and the node draws as if it had no colour. color doesn't tint the body, the selection outline or the widgets.

Override the resolved style

entityStyle replaces individual values that the other props resolved. It applies to every node.

FieldTypeWhat it replaces
backgroundstringThe body fill. Any CSS colour.
borderColorstringThe border colour. Any CSS colour.
borderWidthnumberThe border width, in pixels
borderRadiusnumberThe body corner, in pixels. It wins over radius and size, and leaves widget corners alone.
shadow'1' to '6', or 'none'The shadow

Write a colour as a token, such as var(--neutral-4). The canvas resolves it inside the Theme, and resolves it again whenever the theme changes, so the override follows light and dark mode. A hex value stays the same in both modes.

board.tsx
<KookieFlow  entities={entities}  edges={edges}  onEntitiesChange={onEntitiesChange}  onEdgesChange={onEdgesChange}  variant="soft"  radius="medium"  entityStyle={{    background: "var(--neutral-4)",    borderColor: "var(--accent-9)",    borderWidth: 1,    shadow: "none",  }}/>

Some overrides depend on the variant:

  • background has no effect on 'outline' or 'ghost', because those variants draw no fill.
  • borderColor shows only where the border has a width. 'soft', 'classic' and 'ghost' have none until you set borderWidth.
  • shadow from '1' to '5' picks one of five fixed drop shadows, from small to large. '6' draws no shadow.

You can write the object inline. The canvas compares its five fields, so a new object with the same values doesn't re-resolve any styles.

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
  • Pick a size
  • Pick a variant
  • Set the corner radius
  • Place the title
  • Mark nodes with colour
  • Override the resolved style