# Style a diagram

Use this page when the diagram's appearance belongs to the host application: start with a built-in theme, add per-node or per-edge styling, and connect the canvas to the application's design tokens. The examples render a small workflow, so you can see the fill, stroke, and theme changes on a mounted canvas.

## Choose the styling layer

- Use `theme` for the canvas-wide palette and defaults. [`LIGHT_THEME`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-themes-constants#light_theme) and [`DARK_THEME`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-themes-constants#dark_theme) are shipped themes.
- Use `style.strokeWidth`, `style.fill`, and `style.stroke` for one node or edge.
- Use `style.styleClass` with `defineStyle()` for a reusable named style. Named styles follow the cascade `theme < type-default < named-class < element-inline < state`.
- Use a token bridge when the host already exposes CSS variables. A bridge makes the diagram read the host's shadcn, MUI, or Tailwind variables instead of maintaining a second palette.

The host element must have a resolved height. A canvas inside a container with no height appears blank; give it `100vh`, a flex size, or a sized grid cell.

## JavaScript

Install the element and engine packages:

```bash
npm install @grafloria/element @grafloria/engine @grafloria/renderer
```

Mount a real spec with [`render`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-element-core#render), then keep the returned [`DiagramInstance`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-instance-diagraminstance#diagraminstance) for live changes. This sample starts dark, makes the middle node thicker, applies a named orange style to the first node, and connects the canvas to shadcn tokens.

```html
<button id="toggle">Toggle theme</button>
<div id="diagram" style="height:400px"></div>
```

```js
import { render, DARK_THEME, shadcnBridge, defineStyle } from '@grafloria/element';

const host = document.getElementById('diagram');
if (!host) throw new Error('Missing #diagram');
host.style.height = '400px';
defineStyle('warning-step', { fill: '#fed7aa', stroke: '#c2410c', strokeWidth: 2 });

const nodes = [
  { id: 'start', position: { x: 40, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Start' }, style: { styleClass: 'warning-step' } },
  { id: 'review', position: { x: 280, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Review' }, style: { strokeWidth: 5 } },
  { id: 'done', position: { x: 520, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Done' } },
];
const edges = [
  { id: 'start-review', source: 'start', target: 'review' },
  { id: 'review-done', source: 'review', target: 'done', style: { strokeWidth: 3 } },
];

const instance = render({ nodes, edges }, host, { theme: DARK_THEME });
instance.setTokenBridge(shadcnBridge());
instance.renderNow();
```

The host now contains three visible nodes and two links. The first node uses the named orange style, the second node has a 5px border, and the second link has a 3px stroke. `setTokenBridge()` changes the instance's palette without changing the node data; `renderNow()` repaints synchronously.

## Use a built-in theme

Set the component's `theme` prop when the framework owns the diagram. The following snippets use the same nodes and edges in each framework; each host has a real height. The JavaScript example below shows a live theme swap through the instance.

The framework entry points are [`GrafloriaFlow`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-react#grafloriaflow) for React, [`GrafloriaFlow`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-vue#grafloriaflow) for Vue, [`GrafloriaFlow`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-qwik#grafloriaflow) for Qwik, and [`DiagramCanvasComponent`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-angular-components-diagramcanvascomponent#diagramcanvascomponent) for Angular.

:::code-group
```tsx title="React"
import { GrafloriaFlow, DARK_THEME } from '@grafloria/react';
import type { NodeSpec, EdgeSpec } from '@grafloria/react';

const nodes: NodeSpec[] = [
  { id: 'a', position: { x: 60, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Order' } },
  { id: 'b', position: { x: 300, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Ship' } },
];
const edges: EdgeSpec[] = [{ id: 'e1', source: 'a', target: 'b' }];

export default function StyledFlow() {
  return (
    <div style={{ height: '100vh' }}>
      <GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} theme={DARK_THEME} />
    </div>
  );
}
```
```vue title="Vue"
<script setup lang="ts">
import { GrafloriaFlow, DARK_THEME } from '@grafloria/vue';
import type { NodeSpec, EdgeSpec } from '@grafloria/vue';

const nodes: NodeSpec[] = [
  { id: 'a', position: { x: 60, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Order' } },
  { id: 'b', position: { x: 300, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Ship' } },
];
const edges: EdgeSpec[] = [{ id: 'e1', source: 'a', target: 'b' }];
</script>

<template>
  <div style="height:100vh">
    <GrafloriaFlow :default-nodes="nodes" :default-edges="edges" :theme="DARK_THEME" />
  </div>
</template>
```
```ts title="Angular"
import { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import { DARK_THEME, type NodeSpec, type EdgeSpec, type Theme } from '@grafloria/renderer';

@Component({
  standalone: true,
  imports: [DiagramCanvasComponent],
  template: `<grafloria-diagram-canvas [nodes]="nodes" [edges]="edges" [theme]="theme" style="display:block;height:100vh" />`,
})
export class StyledFlowComponent {
  theme: Theme = DARK_THEME;
  nodes: NodeSpec[] = [
    { id: 'a', position: { x: 60, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Order' } },
    { id: 'b', position: { x: 300, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Ship' } },
  ];
  edges: EdgeSpec[] = [{ id: 'e1', source: 'a', target: 'b' }];
}
```
```tsx title="Qwik"
import { component$ } from '@builder.io/qwik';
import { GrafloriaFlow, DARK_THEME } from '@grafloria/qwik';
import type { NodeSpec, EdgeSpec } from '@grafloria/qwik';

const nodes: NodeSpec[] = [
  { id: 'a', position: { x: 60, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Order' } },
  { id: 'b', position: { x: 300, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Ship' } },
];
const edges: EdgeSpec[] = [{ id: 'e1', source: 'a', target: 'b' }];

export default component$(() => {
  return <div style={{ height: '100vh' }}>
    <GrafloriaFlow defaultNodes={nodes} defaultEdges={edges} theme={DARK_THEME} />
  </div>;
});
```
```js title="JavaScript"
import { render, LIGHT_THEME, DARK_THEME } from '@grafloria/element';

const host = document.getElementById('diagram') ?? document.body.appendChild(document.createElement('div'));
host.id = 'diagram';
host.style.height = '400px';
const instance = render({
  nodes: [
    { id: 'a', position: { x: 60, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Order' } },
    { id: 'b', position: { x: 300, y: 80 }, size: { width: 170, height: 70 }, data: { label: 'Ship' } },
  ],
  edges: [{ id: 'e1', source: 'a', target: 'b' }],
}, host, { theme: DARK_THEME });

let dark = true;
const toggle = document.getElementById('toggle');
if (!toggle) throw new Error('Missing #toggle');
toggle.addEventListener('click', () => {
  dark = !dark;
  instance.setTheme(dark ? DARK_THEME : LIGHT_THEME);
  instance.renderNow();
});
```
:::

In Angular, `DiagramCanvasComponent` is the canvas element. In the other framework bindings, `GrafloriaFlow` is rendered as a component. The JavaScript example uses the instance's `setTheme()` method; retain the instance rather than recreating the canvas.

## Add named styles and a token bridge

Register a named style once, then name it from a node's `style.styleClass`. Namespace names in applications because the style registry is process-wide. An inline property outranks the named class, so this node is green even though its class declares orange:

```js
import { defineStyle } from '@grafloria/element';

defineStyle('review-step', { fill: '#fed7aa', stroke: '#c2410c', strokeWidth: 2 });
const node = {
  id: 'review',
  position: { x: 80, y: 80 },
  size: { width: 180, height: 72 },
  data: { label: 'Review' },
  style: { styleClass: 'review-step', fill: '#22c55e' },
};
```

For a host that uses CSS variables, import one of the shipped bridges and pass it to the live instance:

```js
import { render, LIGHT_THEME, shadcnBridge, defineStyle } from '@grafloria/element';

const host = document.getElementById('diagram') ?? document.body.appendChild(document.createElement('div'));
host.id = 'diagram';
host.style.cssText = 'height:400px;width:800px;display:block';
defineStyle('review-step', { fill: '#fed7aa', stroke: '#c2410c', strokeWidth: 2 });
const node = {
  id: 'review',
  position: { x: 80, y: 80 },
  size: { width: 180, height: 72 },
  data: { label: 'Review' },
  style: { styleClass: 'review-step', fill: '#22c55e' },
};
const instance = render({ nodes: [node], edges: [] }, host, { theme: LIGHT_THEME });
instance.setTokenBridge(shadcnBridge());
instance.renderNow();
```

`shadcnBridge()`, `muiBridge()`, and `tailwindBridge()` map the corresponding host vocabulary. The bridge is instance-scoped: two diagrams can use different themes on one page without one instance overwriting the other's CSS variables. To change the host palette, update the host's variables or class, then repaint the instance.

## Pitfalls

- A host without a resolved height produces a blank canvas. Size the host or its flex/grid parent before mounting.
- A named style does not beat an element-inline property. Put the property on the node when that node must win.
- Do not use a Mermaid-style text string as the `render()` spec. `render()` mounts a data object; text import is a separate API.
- Use `./create-custom-nodes.md` when you need a custom node; omit `custom: true` and the renderer uses a stock rectangle. Register its renderer before mount.
- Use `./build-er-and-uml-diagrams.md` for custom-node update behavior; custom renderers run at mount, so update DOM you own or repaint the dashboard widget.

## See also

- [The live dark-mode demo](https://grafloria.com/demos/styling/dark-mode.html)
- [The live CSS-variable demo](https://grafloria.com/demos/styling/css-variables.html)
- [The live named-style demo](https://grafloria.com/demos/styling/named-style-classes.html)
- [The live themes and tokens demo](https://grafloria.com/demos/styling/themes-and-tokens.html)
- [How Grafloria works](https://atloria.dev/p/grafloria-h7YM7amryF/developer/how-grafloria-works)
- [Auto-layout a diagram](https://atloria.dev/p/grafloria-h7YM7amryF/developer/auto-layout-a-diagram)
