diff --git a/ivette/src/dome/renderer/graph/diagram.tsx b/ivette/src/dome/renderer/graph/diagram.tsx
index 5f111e7322f7893d5d168f5a3e5dd7ff3ceb388f..c80ce63f0ac62a766249cf91cac507a271f8a6ff 100644
--- a/ivette/src/dome/renderer/graph/diagram.tsx
+++ b/ivette/src/dome/renderer/graph/diagram.tsx
@@ -58,6 +58,8 @@ export type Box = Cell | Box[];
 export interface Node {
   /** Node identifier (unique). */
   id: string;
+  /** Cluster identifier */
+  cluster?: string;
   /** Node label */
   label?: string;
   /** Node tooltip */
@@ -102,13 +104,25 @@ export interface Edge {
   tailLabel?: string,
 }
 
+export interface Cluster {
+  /** Identifier */
+  id: string;
+  /** Label (default is none) */
+  label?: string;
+  /** Title (default is none) */
+  title?: string;
+  /** Background color (default is grey) */
+  color?: Color;
+}
+
 /* -------------------------------------------------------------------------- */
 /* --- Graph Component Properties                                         --- */
 /* -------------------------------------------------------------------------- */
 
 export interface DiagramProps {
-  nodes: readonly Node[];
-  edges: readonly Edge[];
+  nodes?: readonly Node[];
+  edges?: readonly Edge[];
+  clusters?: readonly Cluster[];
 
   /**
      Element to focus on.
@@ -192,6 +206,8 @@ const DIR = (h: Arrow, t: Arrow): string | undefined =>
 /* --- Dot Model                                                          --- */
 /* -------------------------------------------------------------------------- */
 
+type cluster = { props: Cluster; nodes: Node[]; }
+
 class Builder {
 
   private selected: string | undefined;
@@ -200,6 +216,7 @@ class Builder {
   private kid = 0;
   private imap = new Map<string, string>();
   private rmap = new Map<string, string>();
+  private cmap = new Map<string, cluster>();
 
   index(id: string): string {
     const n = this.imap.get(id);
@@ -210,6 +227,25 @@ class Builder {
     return m;
   }
 
+  findCluster(id: string): cluster {
+    const c = this.cmap.get(id);
+    if (c !== undefined) return c;
+    const d = { props: { id }, nodes: [] };
+    this.cmap.set(id, d);
+    return d;
+  }
+
+  addClusterNode(n: Node): void {
+    if (n.cluster !== undefined) {
+      this.findCluster(n.cluster).nodes.push(n);
+    }
+  }
+
+  setClusterProps(props: Cluster): void {
+    const c = this.findCluster(props.id);
+    c.props = props;
+  }
+
   nodeId(n: string): string {
     return this.rmap.get(n) ?? n;
   }
@@ -217,6 +253,7 @@ class Builder {
   init(): Builder {
     this.spec = 'digraph {\n';
     this.selected = undefined;
+    this.cmap.clear();
     // Keep node index to fade in & out
     return this;
   }
@@ -244,15 +281,15 @@ class Builder {
     return this.print(a.split('"').join('\\"'));
   }
 
-  value(a: string | number): Builder {
+  value(a: string | number | boolean): Builder {
     if (typeof a === 'string')
       return this.print('"').escaped(a).print('"');
     else
       return this.print(`${a}`);
   }
 
-  attr(a: string, v: string | number | undefined): Builder {
-    return v ? this.print(' ', a, '=').value(v).print('; ') : this;
+  attr(a: string, v: string | number | boolean | undefined): Builder {
+    return v ? this.print(' ', a, '=').value(v).print(';') : this;
   }
 
   // --- Node Table Shape
@@ -281,28 +318,54 @@ class Builder {
 
   // --- Node
   node(n: Node): void {
-    this.print('  ').port(n.id).print(' [');
+    this
+      .print('  ')
+      .port(n.id)
+      .print(' [')
+      .attr('id', n.id);
     if (typeof n.shape === 'object') {
       this
         .attr('shape', 'record')
         .print(' label="')
         .record(n.shape)
-        .print('"; ');
+        .print('";')
+        .attr('tooltip', n.title ?? n.id);
     } else {
       this
         .attr('label', n.label ?? n.id)
-        .attr('shape', n.shape);
+        .attr('shape', n.shape)
+        .attr('tooltip', n.title ?? n.label ?? n.id);
     }
     const color = n.color ?? (n.id === this.selected ? 'selected' : 'white');
     this
-      .attr('id', n.id)
-      .attr('tooltip', n.title)
       .attr('fontcolor', FGCOLOR[color])
       .attr('fillcolor', BGCOLOR[color])
-      .println('];');
+      .println(' ];');
+  }
+
+  cluster(c: cluster): void {
+    const { props: s, nodes } = c;
+    const { color = 'grey' } = s;
+    this
+      .print('  subgraph cluster_', this.index(s.id), ' {\n   ')
+      .attr('style', 'filled')
+      .attr('label', s.label)
+      .attr('tooltip', s.title ?? s.id)
+      .attr('fontcolor', FGCOLOR[color])
+      .attr('fillcolor', BGCOLOR[color])
+      .print('\n   ');
+    nodes.forEach(n => this.print(' ', this.index(n.id), ';'));
+    this.println('\n  }');
+  }
+
+  clusters(cs: readonly Cluster[]): Builder {
+    cs.forEach(c => this.setClusterProps(c));
+    return this;
   }
 
   nodes(ns: readonly Node[]): Builder {
+    ns.forEach(n => this.addClusterNode(n));
+    this.cmap.forEach(c => this.cluster(c));
     ns.forEach(n => this.node(n));
     return this;
   }
@@ -310,6 +373,7 @@ class Builder {
   // --- Edge
   edge(e: Edge): void {
     const { line = 'solid', head = 'arrow', tail = 'none' } = e;
+    const tooltip = e.title ?? e.label ?? `${e.source} -> ${e.target}`;
     this
       .print('  ')
       .port(e.source, e.sourcePort)
@@ -319,7 +383,10 @@ class Builder {
       .attr('label', e.label)
       .attr('headlabel', e.headLabel)
       .attr('taillabel', e.tailLabel)
-      .attr('labeltooltip', e.title)
+      .attr('labeltooltip', e.label ? tooltip : undefined)
+      .attr('headtooltip', e.headLabel ? tooltip : undefined)
+      .attr('tailtooltip', e.tailLabel ? tooltip : undefined)
+      .attr('tooltip', tooltip)
       .attr('dir', DIR(head, tail))
       .attr('color', e.color ? EDCOLOR[e.color] : undefined)
       .attr('style', line === 'solid' ? undefined : line)
@@ -350,19 +417,28 @@ function GraphvizView(props: GraphvizProps): JSX.Element {
   const builder = React.useMemo(() => new Builder, []);
 
   // --- Model Generation
-  const { direction = 'LR', nodes, edges, selected } = props;
+  const {
+    direction = 'LR',
+    clusters = [],
+    nodes = [],
+    edges = [],
+    selected
+  } = props;
+
   const model = React.useMemo(() =>
     builder
       .init()
       .select(selected)
+      .print(' ')
       .attr('rankdir', direction)
       .attr('bgcolor', 'none')
       .attr('width', 0.5)
       .println('node [ style="filled" ];')
+      .clusters(clusters)
       .nodes(nodes)
       .edges(edges)
       .flush()
-    , [builder, direction, nodes, edges, selected]
+    , [builder, direction, clusters, nodes, edges, selected]
   );
 
   // --- Model Update Callback
@@ -442,8 +518,7 @@ export function Diagram(props: DiagramProps): JSX.Element {
             </div>
           )}
         </AutoSizer >
-      )
-      }
+      )}
     </>
   );
 }
diff --git a/ivette/src/sandbox/dotdiagram.tsx b/ivette/src/sandbox/dotdiagram.tsx
index 321036f953cf574a519ada6b2d1ef62671ffc63c..191617f28bc1a6299abcf24c7ad53caf922ebd39 100644
--- a/ivette/src/sandbox/dotdiagram.tsx
+++ b/ivette/src/sandbox/dotdiagram.tsx
@@ -27,7 +27,7 @@
 import React from 'react';
 import { Scroll } from 'dome/layout/boxes';
 import { HSplit } from 'dome/layout/splitters';
-import { Diagram, Node, Edge } from 'dome/graph/diagram';
+import { Diagram, Node, Edge, Cluster } from 'dome/graph/diagram';
 import { registerSandbox } from 'ivette';
 
 // --------------------------------------------------------------------------
@@ -45,17 +45,17 @@ const nodes: Node[] = [
       { label: 'Dashed "e"', port: 'e' },
     ]
   },
-  { id: 'white', color: 'white' },
-  { id: 'grey', color: 'grey' },
-  { id: 'dark', color: 'dark' },
-  { id: 'primary', color: 'primary' },
-  { id: 'selected', color: 'selected' },
-  { id: 'green', color: 'green' },
-  { id: 'orange', color: 'orange' },
-  { id: 'red', color: 'red' },
-  { id: 'yellow', color: 'yellow' },
-  { id: 'blue', color: 'blue' },
-  { id: 'pink', color: 'pink' },
+  { id: 'white', color: 'white', cluster: 'BG' },
+  { id: 'grey', color: 'grey', cluster: 'BG' },
+  { id: 'dark', color: 'dark', cluster: 'BG' },
+  { id: 'primary', color: 'primary', cluster: 'BG' },
+  { id: 'selected', color: 'selected', cluster: 'BG' },
+  { id: 'green', color: 'green', cluster: 'BG' },
+  { id: 'orange', color: 'orange', cluster: 'BG' },
+  { id: 'red', color: 'red', cluster: 'BG' },
+  { id: 'yellow', color: 'yellow', cluster: 'BG' },
+  { id: 'blue', color: 'blue', cluster: 'BG' },
+  { id: 'pink', color: 'pink', cluster: 'BG' },
   { id: 'X' }, { id: 'Y' }
 ];
 
@@ -87,9 +87,15 @@ const edges: Edge[] = [
   },
 ];
 
+function makeCluster(s: string | undefined): Cluster {
+  const color = nodes.find(n => n.id === s)?.color;
+  return { id: 'BG', title: 'Background Cluster', color };
+}
+
 function DiagramSample(): JSX.Element {
   const [model, setModel] = React.useState('');
   const [selected, setSelected] = React.useState<string>();
+  const clusters = React.useMemo(() => [makeCluster(selected)], [selected]);
   return (
     <HSplit settings='sandbox.diagram.split'>
       <Scroll>
@@ -101,6 +107,7 @@ function DiagramSample(): JSX.Element {
       <Diagram
         nodes={nodes}
         edges={edges}
+        clusters={clusters}
         selected={selected}
         onModelChanged={setModel}
         onSelection={setSelected}