Skip to content
D
Documentation

Theme diagrams

how-to
3 min readUpdated

Use a theme for shared diagram styling, put an exception such as strokeWidth on the node or edge spec, and keep each mounted diagram's theme in its own instance. This page covers JavaScript, Angular, React, and Vue.

When to use a theme

Use a theme when several diagrams share palette, typography, spacing, effects, and default node, link, or port styling. A Theme is data: pass the built-in LIGHT_THEME or DARK_THEME, or derive a theme by copying one and changing its identity or supported values. Keep a one-off visual difference in the spec instead of making a second global theme.

The examples below use four nodes and three links, so the result is a visible flow rather than an empty canvas. Give the host a height; without one there is no area in which to paint. See Edit Mermaid diagrams for that pitfall.

JavaScript: mount two themed diagrams

Call render with a spec, a host, and options. It returns a DiagramInstance, so you can switch the theme and repaint the live diagram without rebuilding its data.

js
import { render } from '@grafloria/element';
import { DARK_THEME, LIGHT_THEME } from '@grafloria/renderer';

const spec = () => ({
  nodes: [
    { id: 'order', position: { x: 60, y: 60 }, size: { width: 160, height: 70 }, data: { label: 'Order' } },
    { id: 'payment', position: { x: 300, y: 60 }, size: { width: 160, height: 70 }, data: { label: 'Payment' } },
    { id: 'fulfil', position: { x: 540, y: 60 }, size: { width: 160, height: 70 }, data: { label: 'Fulfil' } },
    { id: 'refund', position: { x: 300, y: 190 }, size: { width: 160, height: 70 }, data: { label: 'Refund' } },
  ],
  edges: [
    { id: 'to-payment', source: 'order', target: 'payment', style: { strokeWidth: 3 } },
    { id: 'to-fulfil', source: 'payment', target: 'fulfil' },
    { id: 'to-refund', source: 'payment', target: 'refund' },
  ],
});

const host = (id) => {
  const existing = document.getElementById(id);
  if (existing instanceof HTMLElement) return existing;
  const created = document.createElement('div');
  created.id = id;
  document.body.appendChild(created);
  return created;
};
const lightHost = host('light');
const darkHost = host('dark');
lightHost.style.height = '300px';
darkHost.style.height = '300px';
const light = render(spec(), lightHost, { theme: LIGHT_THEME });
const dark = render(spec(), darkHost, { theme: DARK_THEME });

const derivedDark = { ...DARK_THEME, name: 'Acme dark' };
dark.setTheme(derivedDark);
dark.renderNow();

// The light diagram remains light; the dark diagram changes independently.
light.renderNow();
html
<div id="light" style="height: 300px"></div>
<div id="dark" style="height: 300px"></div>

The page shows the same graph twice, with the first diagram light and the second dark. The first link is visibly thicker because its spec sets strokeWidth: 3. Calling setTheme() changes only the instance on which you call it. The renderer stamps each root with an instance-specific scope for its injected variables and style element, so the two diagrams do not overwrite one another.

The same pattern in each framework

Each binding mounts the component, supplies graph data typed with the library's types, and keeps the host tall enough to render. React and Vue store the instance from their init callback. Angular changes its bound theme value directly from the button.

tsx
import { useRef, useState } from 'react';
import { GrafloriaFlow, DARK_THEME, LIGHT_THEME } from '@grafloria/react';
import type { DiagramInstance } from '@grafloria/react';
import type { NodeSpec, EdgeSpec } from '@grafloria/renderer';

const nodes: NodeSpec[] = [
  { id: 'a', position: { x: 40, y: 60 }, size: { width: 150, height: 70 }, data: { label: 'Order' } },
  { id: 'b', position: { x: 280, y: 60 }, size: { width: 150, height: 70 }, data: { label: 'Payment' } },
];
const edges: EdgeSpec[] = [{ id: 'e', source: 'a', target: 'b', style: { strokeWidth: 3 } }];

export default function ThemedDiagrams() {
  const instance = useRef<DiagramInstance | null>(null);
  const [dark, setDark] = useState(false);
  const switchTheme = () => {
    const next = !dark;
    setDark(next);
    instance.current?.setTheme(next ? DARK_THEME : LIGHT_THEME);
    instance.current?.renderNow();
  };
  return <div style={{ height: 300 }}>
    <button onClick={switchTheme}>Switch theme</button>
    <GrafloriaFlow defaultNodes={nodes} defaultEdges={edges}
      theme={dark ? DARK_THEME : LIGHT_THEME}
      onInit={(api) => { instance.current = api; }}
      style={{ display: 'block', height: '260px' }} />
  </div>;
}

In every tab, the button changes the rendered palette while the two-node graph remains in place, and the edge remains three pixels wide. In React and Vue, retain the initialized instance when later code needs an imperative call. In Angular, the button changes the bound theme property.

Options that matter

OptionTypeDefaultWhat it does
themeThemeLIGHT_THEMESupplies the palette and defaults for the mounted diagram.
colorMode'light' | 'dark' | 'system'not statedSelects a pinned colour mode or follows the OS when set to 'system'.
themesThemeSetnot statedSupplies the theme set used with colour-mode resolution.
strokeWidthnumber in node/link stylenot statedSets the painted stroke width for that styled node or link.

setTheme(theme) performs a live theme swap and schedules a repaint. It forwards the theme to the renderer and schedules the instance for painting. Use renderNow() when code must measure the updated DOM immediately; otherwise the scheduled repaint is sufficient.

Pitfalls

  • A host with no resolved height renders blank. Give it a fixed height, a flex size, or 100vh; see Edit Mermaid diagrams.
  • render() accepts a data spec, not Mermaid text. Use the text import page for the DSL.
  • Theme changes are instance operations. Do not store a mutable global “current theme” and expect existing diagrams to coordinate; call setTheme() on each instance that should change.
  • Custom nodes need the opt-in described in Customize nodes.

Live demo

Try Themes & design tokens to switch a mounted graph between the library theme and host design tokens. Its source is the themes-and-tokens demo.

Was this page helpful?