Skip to content
D
Documentation

Theme and style diagrams

how-to
3 min readUpdated

Use a theme for the diagram-wide palette, then use node specs for individual differences. A named style class lets you reuse a set of node styles; strokeWidth sets a node's border width.

Start with LIGHT_THEME or DARK_THEME for the overall palette. A node's spec supplies local styling, and a mounted DiagramInstance can update the theme or reconcile node specs without replacing the diagram. For plain JavaScript, render mounts the spec and returns that instance.

Register styles and mount a styled diagram

Call defineStyle before mounting the diagram, and refer to each named style in a node's style.styleClass. The JavaScript sample uses NodeSpec values and an EdgeSpec list. It mounts three nodes: the priority node combines warn and bold; the second node overrides the named class's fill with an inline green fill; the third keeps the theme's node styling. Click Toggle theme to switch the mounted diagram's palette. Click Emphasize priority to change only the node whose id is priority to a 9px stroke.

The names are prefixed to avoid collisions with other named styles in the application. Named styles are applied in order, so the later bold definition overrides warn's strokeWidth; an element's own inline style overrides a named class. The cascade is theme, type default, named class, element inline, then state.

ts
import type { DiagramInstance, EdgeSpec, NodeSpec } from '@grafloria/renderer';

async function mountStyledDiagram(): Promise<void> {
const { render, defineStyle } = await import('@grafloria/element');
const { DARK_THEME, LIGHT_THEME } = await import('@grafloria/renderer');

const WARN = 'guide-warn';
const BOLD = 'guide-bold';
defineStyle(WARN, { fill: '#f97316', stroke: '#9a3412', strokeWidth: 2 });
defineStyle(BOLD, { strokeWidth: 6 });

const nodes: NodeSpec[] = [
  {
    id: 'priority',
    position: { x: 50, y: 60 },
    size: { width: 190, height: 78 },
    data: { label: 'warn + bold' },
    style: { styleClass: `${WARN} ${BOLD}` },
  },
  {
    id: 'override',
    position: { x: 280, y: 60 },
    size: { width: 190, height: 78 },
    data: { label: 'warn + inline fill' },
    style: { styleClass: WARN, fill: '#22c55e' },
  },
  {
    id: 'plain',
    position: { x: 510, y: 60 },
    size: { width: 190, height: 78 },
    data: { label: 'theme default' },
  },
];
const edges: EdgeSpec[] = [];
const themeButton = document.createElement('button');
themeButton.type = 'button';
themeButton.textContent = 'Toggle theme';
const emphasizeButton = document.createElement('button');
emphasizeButton.type = 'button';
emphasizeButton.textContent = 'Emphasize priority';
const canvas = document.createElement('div');
canvas.style.height = '220px';
document.body.append(themeButton, emphasizeButton, canvas);

let themeIsDark = false;
let emphasized = false;
const instance: DiagramInstance = render(
  { nodes, edges },
  canvas,
  { theme: LIGHT_THEME },
);

themeButton.onclick = () => {
  themeIsDark = !themeIsDark;
  instance.setTheme(themeIsDark ? DARK_THEME : LIGHT_THEME);
};

emphasizeButton.onclick = () => {
  emphasized = !emphasized;
  instance.setNodes(nodes.map((node) =>
    node.id === 'priority'
      ? { ...node, style: { ...node.style, strokeWidth: emphasized ? 9 : 6 } }
      : node,
  ));
  instance.renderNow();
};
}

void mountStyledDiagram();

The JavaScript example creates a sized canvas and its controls, then uses render to get the live instance. setTheme() changes that instance's theme, and setNodes() reconciles the full node list. Keep the other nodes in the array: setNodes() reconciles the diagram, including removing nodes that are missing from the next list.

Use the diagram in Angular, React, Vue, and Qwik

These versions render the same named-class and inline-style examples. Angular and React switch themes by changing the component's theme input; Vue mounts with DARK_THEME as its component input. The Qwik sample mounts through render() in a visible task and keeps the live instance out of serialized state. GrafloriaFlow provides the React and Vue canvas; Angular uses DiagramCanvasComponent. Each canvas has a resolved height.

ts
import { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import { defineStyle } from '@grafloria/renderer';
import {
  DARK_THEME,
  LIGHT_THEME,
  type EdgeSpec,
  type NodeSpec,
  type Theme,
} from '@grafloria/renderer';

const WARN = 'guide-angular-warn';
const BOLD = 'guide-angular-bold';
defineStyle(WARN, { fill: '#f97316', stroke: '#9a3412', strokeWidth: 2 });
defineStyle(BOLD, { strokeWidth: 6 });

@Component({
  standalone: true,
  imports: [DiagramCanvasComponent],
  template: `
    <button type="button" (click)="toggleTheme()">Toggle theme</button>
    <grafloria-diagram-canvas
      [(nodes)]="nodes"
      [(edges)]="edges"
      [theme]="theme"
      style="display: block; height: 220px" />
  `,
})
export class StyledDiagramComponent {
  theme: Theme = LIGHT_THEME;
  nodes: NodeSpec[] = [
    {
      id: 'priority', position: { x: 50, y: 60 }, size: { width: 190, height: 78 },
      data: { label: 'warn + bold' }, style: { styleClass: `${WARN} ${BOLD}` },
    },
    {
      id: 'override', position: { x: 280, y: 60 }, size: { width: 190, height: 78 },
      data: { label: 'warn + inline fill' }, style: { styleClass: WARN, fill: '#22c55e' },
    },
    {
      id: 'plain', position: { x: 510, y: 60 }, size: { width: 190, height: 78 },
      data: { label: 'theme default' },
    },
  ];
  edges: EdgeSpec[] = [];

  toggleTheme(): void {
    this.theme = this.theme === LIGHT_THEME ? DARK_THEME : LIGHT_THEME;
  }
}

In each component, the priority node's id and style travel together in its spec. Change a mounted node by id through the instance's setNodes() method, as in the JavaScript sample; pass the full node list so reconciliation keeps the other nodes. The frameworks use the same theme and node-style data because each binding feeds the same diagram model.

Options that affect the result

OptionTypeDefaultWhat it does
themeThemeLIGHT_THEMESupplies the canvas palette and default node and link styles.
style.styleClassstringNoneNames one or more registered styles for a node; separate multiple names with spaces.
style.strokeWidthnumberTheme's node defaultSets the node's border width; a later cascade layer can override it.

The cascade makes conflicts predictable: an inline node style overrides its named class, while selection and other state styling sit above both. An unknown style-class name contributes no style. Keep named-style names distinctive: named styles use an application-wide registry.

See the demos

The Named style classes demo shows class stacking, inline overrides, and selection styling. Its initial view has an orange warn node, a green inline-fill override, and a thicker warn bold node.

The Dark mode demo demonstrates the theme palette change on a rendered diagram.

Was this page helpful?

Theme and style diagrams — Grafloria · GPT-6 Luna