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

Fonts and text rendering

Choose the font the canvas draws its labels with, use the platform's own font, or bring an MSDF font atlas of your own.

The canvas draws its titles, labels and widget values itself, as glyphs taken from a font atlas. An atlas is an image that holds every character, together with the measurements that place each one on a line. Your page's CSS fonts don't reach the canvas, so the font prop chooses which atlas it uses.

Choose a font

font takes a FontPreset name or a FontConfig object. The default is 'inter'.

ValueWhat the canvas draws withWhat it costs
'inter'Inter, in a regular and a semibold weightA separate chunk, downloaded the first time a canvas uses it
'system'The platform's own interface font, built into an atlas when the canvas mountsNo font file to download. The atlas is built on the main thread at mount.
'roboto', 'source-serif'Inter, with a console warning, because these atlases aren't bundledThe same as 'inter'
Any other stringInter, with a console warning that lists the valid presets. TypeScript rejects the name, so this happens only in JavaScript.The same as 'inter'
A FontConfigYour own atlasYour atlas images and metrics
<KookieFlow entities={entities} edges={edges} font="system" />

The canvas draws no text until the atlas is ready, so titles and labels appear a moment after the nodes. To match the canvas to the rest of your page, point KookieUI's --font-body token at the same face.

Because Inter loads as its own chunk, an app that uses 'system' or a FontConfig never downloads it. A CommonJS build can't split chunks, so with CommonJS the atlas is part of the package entry.

Use the platform font

font="system" draws the platform's interface font: system-ui, falling back through -apple-system, Segoe UI, Roboto, Helvetica Neue and Arial. The canvas draws each character onto a 2D canvas in a regular and a semibold weight, and turns the result into an atlas. So the board looks native on every operating system, and it looks different on each one.

The two atlases hold different characters. Both cover ASCII, curly quotes, en and em dashes, the ellipsis, ©, °, ×, ±, €, £ and ¥. Beyond that:

  • Inter's atlas adds accented Latin letters, such as é and ü, and ®, ™, ÷ and ¢.
  • The system atlas adds the four arrows, the bullet, the middle dot, ≤, ≥, ∞, « and ».

A character that isn't in the atlas doesn't draw, so with font="system" a title such as "Café" draws as "Caf", and with Inter an arrow in a label is left out.

The system atlas is a single-channel distance field, where Inter's is multi-channel, so sharp corners on large glyphs come out slightly rounded. Where no 2D canvas is available, as in some test environments, 'system' logs a warning and uses Inter.

Bring your own font

A custom font takes two steps: generating an atlas for each weight, and passing a FontConfig that points at the files.

Generate the atlases with msdf-bmfont-xml. These settings match the bundled Inter atlas:

Terminal
npx -p msdf-bmfont-xml msdf-bmfont IBMPlexSans-Regular.ttf -f json -s 48 -r 4 -m 1024,1024 --pot -i charset.txt

The command writes a PNG atlas and a JSON file of metrics. charset.txt lists every character your labels use, and a character that isn't in it won't draw. Keep each weight on a single image, because the canvas loads one image per weight. If the tool writes a second PNG, raise the size you pass to -m.

Then load the metrics and pass the config. This component waits for both metrics files, and falls back to the platform font if they fail to load:

board.tsx
"use client";import { useEffect, useState } from "react";import {  KookieFlow,  type FontConfig,  type FontMetrics,  type FontPreset,  type KookieFlowProps,} from "@kushagradhawan/kookie-flow";async function loadMetrics(url: string): Promise<FontMetrics> {  const response = await fetch(url);  if (!response.ok) throw new Error(`${url} returned ${response.status}`);  return response.json();}export function Board(props: Omit<KookieFlowProps, "font">) {  const [font, setFont] = useState<FontConfig | FontPreset | null>(null);  useEffect(() => {    Promise.all([loadMetrics("/fonts/plex-regular.json"), loadMetrics("/fonts/plex-semibold.json")])      .then(([regular, semibold]) =>        setFont({          name: "IBM Plex Sans",          weights: {            regular: { metrics: regular, atlasUrl: "/fonts/plex-regular.png" },            semibold: { metrics: semibold, atlasUrl: "/fonts/plex-semibold.png" },          },        }),      )      .catch((error: unknown) => {        console.error("Canvas font failed to load", error);        setFont("system");      });  }, []);  if (!font) return null;  return <KookieFlow {...props} font={font} />;}

The component renders nothing until the font is known. If it rendered the canvas straight away, the canvas would download Inter first and then replace it.

FontConfig fieldTypeWhat it is
namestringA name for the font
weights.regularFontWeightConfigThe weight for socket labels, widget values, edge labels, and text below weight 600
weights.semiboldFontWeightConfig, optionalThe weight for titles, and text at weight 600 or above. Without it, those use regular.

A FontWeightConfig has two fields. metrics is a FontMetrics object, and atlasUrl is the atlas image, as a URL or a data URL.

FontMetrics is the BMFont JSON that msdf-bmfont-xml writes, with pages, chars, info, common, and optional kernings and distanceField. The canvas places glyphs with the entries in chars and the pairs in kernings, and scales them with info.size, common.lineHeight, common.scaleW and common.scaleH.

The canvas tells two configs apart by name and the atlas URLs, so writing the config inline doesn't reload anything. To switch fonts, change the name or a URL. Each atlas image stays cached for the life of the page.

How the canvas draws text

Each glyph is a small square that samples the atlas. The atlas stores the distance to each character's outline rather than its pixels, so the canvas works out a sharp edge at any zoom level, and zooming in doesn't blur the text the way an enlarged image would.

Text on the canvas is sized in world units, so it scales with the zoom. Titles, socket labels and widget values use a 12-pixel font size, and an error message under a node uses 11. Edge labels use 11 unless the label sets its own size, which the Edges chapter covers. Page CSS, the Theme's type steps and the browser's font size setting don't change these sizes.

To keep a zoomed-out board fast and readable, the canvas stops drawing smaller text first:

Zoom belowWhat stops drawing
50%Widget values
35%Socket labels, and the message under a failed node
25%Edge labels
15%Node titles

A title or label that's too wide for its node ends in an ellipsis. The canvas measures it with the atlas metrics, not with the browser. A character that isn't in the atlas is skipped, and characters outside the Basic Multilingual Plane, including most emoji, never draw.

Two parts of the canvas involve DOM text:

  • Editing a widget value puts a real input over the widget while you type. Whatever font is, the input asks for Inter first, then the Theme's --font-body. With the default font, load Inter on the page so the value keeps its shape while you edit.
  • Text entities draw with the same atlas as everything else. A text entity's fontWeight of 600 or more picks the semibold weight, and its fontFamily doesn't change the face the canvas draws. The Text and comments chapter covers text entities.
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
  • Choose a font
  • Use the platform font
  • Bring your own font
  • How the canvas draws text