# Theme and style diagrams

Use a [theme](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-types-interfaces-t-v) 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](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-themes-constants) or [DARK_THEME](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-themes-constants) for the overall palette. A node's spec supplies local styling, and a mounted [DiagramInstance](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-diagraminstance) can update the theme or reconcile node specs without replacing the diagram. For plain JavaScript, [`render`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-element-core) mounts the spec and returns that instance.

## Register styles and mount a styled diagram

Call [`defineStyle`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-themes-functions) before mounting the diagram, and refer to each named style in a node's `style.styleClass`. The JavaScript sample uses [`NodeSpec`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-nodespec) values and an [`EdgeSpec`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-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`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-element-core) 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](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-qwik) provides the React and Vue canvas; Angular uses [DiagramCanvasComponent](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-angular-components-diagramcanvascomponent). Each canvas has a resolved height.

:::code-group
```ts title="Angular"
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;
  }
}
```
```tsx title="Qwik"
import {
  $,
  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>
  );
});
```
```tsx title="React"
import { 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 title="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](https://grafloria.com/demos/styling/named-style-classes.html) 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](https://grafloria.com/demos/styling/dark-mode.html) demonstrates the theme palette change on a rendered diagram.

## Related

- [Lay out diagrams](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/lay-out-diagrams)
- [Resize and size nodes](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/resize-and-size-nodes)
