Learn / Theming

Theming

One Theme object drives the canvas chrome, node defaults, link colors and selection states — swap it and everything repaints. Per-node styling stays in the spec, so themes and data never fight.

Built-in themes

// exported from @grafloria/react, @grafloria/vue, or @grafloria/renderer
import { LIGHT_THEME, DARK_THEME } from '@grafloria/react';

// React
<GrafloriaFlow theme={dark ? DARK_THEME : LIGHT_THEME} ... />

// Vue — a computed ref works
<GrafloriaFlow :theme="theme" ... />

// Angular
<grafloria-diagram-canvas [theme]="theme" ... />

// element attribute (strings only here)
<grafloria-flow theme="dark" ...>

Switching is live — no remount, no data loss. At runtime on any instance: instance.setTheme(DARK_THEME).

Live: dark mode toggle →

App-wide defaults (Angular)

bootstrapApplication(App, {
  providers: [provideGrafloria({ theme: DARK_THEME })],
});

Precedence: a canvas-level [theme] binding → the provided default → LIGHT_THEME.

Adopting your design system: the token bridge

If your app already speaks CSS variables — shadcn, MUI, Tailwind — hand them over with tokenBridge and the canvas derives its colors from your tokens instead of its own palette. The diagram stops looking embedded and starts looking native.

Per-node and per-edge styling

Theme = the room; spec styling = the furniture:

{ id: 'a', position: { x: 60, y: 80 },
  shape: { type: 'terminal', fill: '#ecfdf5', stroke: '#059669', cornerRadius: 10 } }

{ id: 'e1', source: 'a', target: 'b',
  style: { stroke: '#9333ea', strokeWidth: 2, strokeDasharray: '6 3' }, label: 'retry' }

And because custom nodes are your own HTML (React components, Vue slots, Angular templates, plain templates), they inherit your app's stylesheet directly — the element renders in light DOM precisely so your CSS cascades in.

The renderer injects one scoped <style> block per instance and exposes --grafloria-* variables — there is no stylesheet you must import, in any framework.

How a selected node looks

By default a selected node does two things: its body takes the theme's selected colours, and a ring is drawn 3px outside it. For card-style nodes the ring is one rectangle too many, so style.selection picks the look:

{ id: 'step', position: { x: 60, y: 80 },
  shape: { type: 'rect', cornerRadius: 12 },
  style: { selection: 'border' } }   // 'both' (default) · 'border' · 'ring'
  • 'both' — the selected fill and border, plus the ring.
  • 'border' — only the node's own border changes; its fill stays and there is no ring.
  • 'ring' — only the ring; the body keeps its own paint.

It is a style property like any other, so it follows the cascade: set it on one node, in a named style (defineStyle('card', { selection: 'border' })), per node type, or for every node with theme.nodes.default.selection. The ring and the border both follow the node's rounded corners. See it on the ManyChat-style chatbot builder.

Where next

  • Export — exports honor the active theme.
  • Demos — themed galleries in light and dark.