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

Text and comments

Place text on the canvas that people can edit in place, choose how it sizes itself, and add sticky-note comments.

A text entity is a block of text on the canvas that people can edit where it sits. You choose whether the block grows with its text or keeps the size you give it. A comment is a sticky note: a coloured box of text for notes that sit beside the graph rather than in it.

Place a text entity

Give the entity type: 'text' and put the words in data.content. Give it a width and a height as well, because the canvas measures presses and selection against them.

board.tsx
'use client';import { KookieFlow, resizableForSizingMode, useGraph, type Entity } from '@kushagradhawan/kookie-flow';const initialEntities: Entity[] = [  {    id: 'heading', type: 'text', position: { x: 0, y: 0 }, width: 320, height: 48,    resizable: resizableForSizingMode('auto-width'),    data: { content: 'Onboarding flow', sizingMode: 'auto-width', fontSize: 28, fontWeight: 600 },  },  {    id: 'intro', type: 'text', position: { x: 0, y: 72 }, width: 320, height: 48,    resizable: resizableForSizingMode('auto-height'),    data: { content: 'Each step below runs once for every new account.\nFailed steps retry after an hour.' },  },];export function Board() {  const { entities, edges, onEntitiesChange, onEdgesChange } = useGraph({ initialEntities });  return (    <div style={{ height: 480 }}>      <KookieFlow entities={entities} edges={edges} onEntitiesChange={onEntitiesChange} onEdgesChange={onEdgesChange} />    </div>  );}

Text is drawn with the face the font prop loads, the same face as node titles, and it stays sharp at every zoom level. Fonts and text rendering covers the font prop. A \n in content starts a new line, and a content that is empty or only spaces shows a faint "Type something..." until someone types.

People can also make text themselves. Pressing T while the canvas has focus adds an empty text entity, 200 units wide, in the middle of the view and opens it for editing. The canvas reports the new entity to onEntitiesChange as an add change.

Text entities hold content and compute nothing, so onEvaluate is never called for them. The Evaluation chapter covers onEvaluate.

Choose a sizing mode

data.sizingMode decides which sides of the block follow the text and which sides stay where you put them. The default is 'auto-height'.

sizingModeWidthHeightLine breaksResize handles
'auto-width'Follows the textFollows the textOnly at \nNone
'auto-height'YoursFollows the textAt \n and at the widthWidth only
'fixed'YoursYoursAt \n and at the widthBoth

Use 'auto-width' for a heading or a label, 'auto-height' for a paragraph, and 'fixed' when the text has to fit a box in a layout. Text that overflows a fixed box is cut off at the bottom edge.

When the canvas measures an auto-sized block at a new size, it reports the size to onEntitiesChange as a dimensions change, so your saved graph holds the size people see.

Match the resize handles to the mode

The handles come from the entity's resizable field, not from sizingMode. A new entity doesn't work them out on its own, so set resizable from the mode with resizableForSizingMode, as the code above does. Without it, an 'auto-width' block shows handles that its text immediately undoes.

CallReturns
resizableForSizingMode('auto-width')false
resizableForSizingMode('auto-height'){ width: true, height: false }
resizableForSizingMode('fixed'){ width: true, height: true }

After that, a data change that sets sizingMode updates resizable for you, both on the canvas and in the state useGraph holds. The toolbar's sizing control sends that change. If you apply changes with your own reducer instead of useGraph, call resizableForSizingMode there too.

This example shows the three modes side by side, with a comment beside them. Select a block to see its toolbar, and drag its handles:

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,  Toolbar,  resizableForSizingMode,  useGraph,  type Entity,  type EntityTypeDefinition,  type KookieFlowInstance,  type TextEntityData,  type TextSizingMode,} from '@kushagradhawan/kookie-flow';// `toolbar: true` shows the sizing, font, and colour controls when text is selected.const entityTypes: Record<string, EntityTypeDefinition> = { text: { type: 'text', toolbar: true } };function text(id: string, x: number, y: number, sizingMode: TextSizingMode, data: Partial<TextEntityData>): Entity {  return {    id,    type: 'text',    position: { x, y },    width: 220,    height: 64,    // A new entity doesn't derive its resize handles from its sizing mode, so state them.    resizable: resizableForSizingMode(sizingMode),    data: { content: '', sizingMode, ...data },  };}const initialEntities: Entity[] = [  text('title', 0, 0, 'auto-width', { content: 'Release notes', fontSize: 28, fontWeight: 600 }),  text('body', 0, 120, 'auto-height', { content: 'Auto height wraps at the width you drag, and grows downward as you type.' }),  text('box', 260, 120, 'fixed', { content: 'Fixed keeps both sides where you put them, and clips anything that overflows.', textColor: '#8a8f98' }),  {    id: 'note', type: 'comment', position: { x: 520, y: 0 }, width: 200, height: 140,    data: { content: 'Double-click a text block to edit it. Press Escape to finish.' },  },];export default function TextEntitiesExample() {  const { entities, edges, onEntitiesChange, onEdgesChange } = useGraph({ initialEntities });  const flowRef = useRef<KookieFlowInstance>(null);  useEffect(() => {    flowRef.current?.fitView({ padding: 48, maxZoom: 1 });  }, []);  return (    <div style={{ width: '100%', height: 360 }}>      <KookieFlow        ref={flowRef}        entities={entities}        edges={edges}        entityTypes={entityTypes}        onEntitiesChange={onEntitiesChange}        onEdgesChange={onEdgesChange}      >        <Toolbar />      </KookieFlow>    </div>  );}

Edit text on the canvas

Double-click a text entity to edit it. Editing starts with all of its text selected, so typing replaces it. Pressing T starts editing a new block straight away.

While someone is editing, these gestures work inside the text:

  • One click places the cursor, and a drag selects from there.
  • Two clicks select a word, three select a line as it wraps on screen, and four select all of the text.
  • Up, Down, Home, and End move along the lines as they wrap on screen.
  • Enter starts a new line. Copy, paste, and input methods for other scripts work as they do in a text field.

Keys go to the text while someone edits, so Delete and Backspace remove characters rather than the entity. Press Escape, or click outside the text, to finish.

Nothing is reported while someone types. When they finish, and only if the text changed, onEntitiesChange receives a data change with the new content and a dimensions change with the block's final size.

Style text

Every style is a field on the entity's data, and you change a style by changing its field.

FieldTypeDefaultMeaning
contentstringrequiredThe text. \n starts a new line.
sizingModeTextSizingMode'auto-height'Which sides follow the text. See Choose a sizing mode.
fontSizenumber16The size of the text, in the same units as position.
fontWeightnumber400600 or more draws the font's semibold weight when the font has one. Anything lower draws regular.
textColorstringThe theme's text colourA hex colour such as '#1f6feb', or an rgb() colour with commas such as 'rgb(31, 111, 235)'. Any other form draws white, and alpha is ignored.
textAlign'left' | 'center' | 'right''left'How each line sits within the block.
lineHeightnumber1.5The distance between lines, as a multiple of fontSize.
letterSpacingnumber0Extra space between characters, in the same units as fontSize.
fontFamilystringnoneStored with the entity, but not drawn. The canvas draws every text entity in the font prop's face.

TextEntityData is the type of this data, TextSizingMode is the union of the three modes, and TextEntity is Entity<TextEntityData> with type: 'text'.

To give people controls for these fields, set toolbar: true on the text entry of entityTypes and render a Toolbar inside KookieFlow, as the example does. The Selection toolbar chapter lists every control.

Read a style with its defaults

resolveTextStyle(data, defaults?) returns every style for a text entity, with defaults filled in where data leaves a field out. Use it when you draw the same text somewhere else, such as a list of notes beside the canvas.

note-preview.tsx
import { resolveTextStyle, type TextEntity } from '@kushagradhawan/kookie-flow';export function NotePreview({ entity }: { entity: TextEntity }) {  // Pass the colour your page uses, because the canvas's default comes from its theme.  const style = resolveTextStyle(entity.data, { textColor: 'currentColor' });  return (    <p      style={{        margin: 0,        whiteSpace: 'pre-wrap',        fontSize: style.fontSize,        fontWeight: style.fontWeight,        lineHeight: style.lineHeight,        letterSpacing: style.letterSpacing,        textAlign: style.textAlign,        color: style.textColor,        padding: style.padding,      }}    >      {entity.data.content}    </p>  );}

defaults takes an optional fontFamily and textColor. The result is a TextStyleConfig:

FieldTypeValue when data and defaults leave it out
fontFamilystring'"Inter", system-ui, -apple-system, sans-serif'
fontSizenumber16
fontWeightnumber400
lineHeightnumber1.5
letterSpacingnumber0
textAlign'left' | 'center' | 'right''left'
textColorstring'#e0e0e0'
paddingnumberAlways 4: the space inside each edge of the block.

Add a comment

A comment is an entity with type: 'comment'. Put its text in data.content, and give it a width and a height.

const note: Entity = {  id: 'review-note', type: 'comment', position: { x: 520, y: -40 }, width: 220, height: 120,  data: { content: 'Check the retry limit with the payments team.', color: 'green' },};
FieldTypeDefaultMeaning
contentstringrequiredThe note. Line breaks are kept and long lines wrap.
colorAccentColorthe entity's color, then 'yellow'The note's hue. Its fill, edge and text are tinted from it, and follow light and dark.
backgroundColorstringtinted from colorReplaces the fill. Any CSS colour.
textColorstringtinted from colorReplaces the text colour. Any CSS colour.
fontSizenumber14The size of the text. It scales with zoom, like the note.

CommentEntityData is the type of this data, and CommentEntity is Entity<CommentEntityData> with type: 'comment'.

Comments differ from text entities in three ways:

  • A comment is a page element laid over the canvas. It draws above every other entity, whatever order they were pressed in. Its text uses the font your theme's --font-body variable names, or the system font when your page has no such variable. Because it's a page element, a var(--token) colour works in backgroundColor and textColor.
  • People can't edit a comment on the canvas. They can select, move, and resize it, down to 80 × 40, but its text changes only through data.content. Use a text entity when people should type.
  • Comments hide when the zoom drops below 0.1, so a zoomed-out graph isn't covered in notes.

A comment inside a collapsed frame is hidden, as any entity is. Set toolbar: true on the comment entry of entityTypes to give people controls for a note's colour and font size. Like text, comments are never sent to onEvaluate.

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
  • Place a text entity
  • Choose a sizing mode
  • Match the resize handles to the mode
  • Edit text on the canvas
  • Style text
  • Read a style with its defaults
  • Add a comment