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.
tsimport 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.
tsimport { 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;
}
}
tsximport {
$,
component$,
noSerialize,
useSignal,
useVisibleTask$,
type NoSerialize,
} from '@builder.io/qwik';
import { defineStyle, render } from '@grafloria/element';
import { DARK_THEME, LIGHT_THEME } from '@grafloria/renderer';
import type { DiagramInstance, EdgeSpec, NodeSpec } from '@grafloria/renderer';
const WARN = 'guide-qwik-warn';
const BOLD = 'guide-qwik-bold';
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[] = [];
export default component$(() => {
const host = useSignal<HTMLElement>();
const instance = useSignal<NoSerialize<DiagramInstance>>();
const dark = useSignal(true);
const emphasized = useSignal(false);
useVisibleTask$(({ cleanup }) => {
const element = host.value;
if (!element) return;
defineStyle(WARN, { fill: '#f97316', stroke: '#9a3412', strokeWidth: 2 });
defineStyle(BOLD, { strokeWidth: 6 });
const api = render({ nodes, edges }, element, { theme: DARK_THEME });
instance.value = noSerialize(api);
cleanup(() => api.dispose());
});
const toggleTheme = $(() => {
const api = instance.value;
if (!api) return;
dark.value = !dark.value;
api.setTheme(dark.value ? DARK_THEME : LIGHT_THEME);
});
const emphasizePriority = $(() => {
const api = instance.value;
if (!api) return;
emphasized.value = !emphasized.value;
api.setNodes(nodes.map((node) =>
node.id === 'priority'
? { ...node, style: { ...node.style, strokeWidth: emphasized.value ? 9 : 6 } }
: node,
));
api.renderNow();
});
return (
<div>
<button type="button" onClick$={toggleTheme}>Toggle theme</button>
<button type="button" onClick$={emphasizePriority}>Emphasize priority</button>
<div ref={host} style={{ height: '220px' }} />
</div>
);
});
tsximport { useState } from 'react';
import { GrafloriaFlow } from '@grafloria/react';
import { defineStyle } from '@grafloria/element';
import { DARK_THEME, LIGHT_THEME } from '@grafloria/renderer';
import type { EdgeSpec, NodeSpec, Theme } from '@grafloria/renderer';
const WARN = 'guide-react-warn';
const BOLD = 'guide-react-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[] = [];
export function StyledDiagram() {
const [theme, setTheme] = useState<Theme>(LIGHT_THEME);
return (
<div style={{ height: 260 }}>
<button type="button" onClick={() => {
setTheme((current) => current === LIGHT_THEME ? DARK_THEME : LIGHT_THEME);
}}>Toggle theme</button>
<GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} theme={theme} />
</div>
);
}
vue<script setup lang="ts"> import { GrafloriaFlow } from '@grafloria/vue'; import { defineStyle } from '@grafloria/element'; import { DARK_THEME } from '@grafloria/renderer'; import type { EdgeSpec, NodeSpec } from '@grafloria/renderer'; const WARN = 'guide-vue-warn'; const BOLD = 'guide-vue-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[] = []; </script> <template> <div style="height: 260px"> <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" :theme="DARK_THEME" /> </div> </template>
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
| Option | Type | Default | What it does |
|---|---|---|---|
theme | Theme | LIGHT_THEME | Supplies the canvas palette and default node and link styles. |
style.styleClass | string | None | Names one or more registered styles for a node; separate multiple names with spaces. |
style.strokeWidth | number | Theme's node default | Sets 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?