Use the UML kit when your data describes classes, members and relationships. Build the diagram with umlDiagram, then edit its mounted classes with umlClass. You get class cards with name, attribute and method compartments, all seven relationship notations, and a scrolling body for a capped card.
1. Declare the classes and relationships
Put this shared file beside the framework sample you choose below. UmlDiagramOptions types the data; member signatures are strings, including their visibility prefixes. The kit returns nodes, edges and a wiring step rather than a separate UML model.
The sample places seven pairs of cards in four rows. Shape has enough members to demonstrate scrolling after the ready callback caps its height.
tsimport { umlDiagram, umlClass, type UmlDiagramOptions } from '@grafloria/element';
import type { DiagramInstance } from '@grafloria/renderer';
const data: UmlDiagramOptions = {
editable: true,
classes: [
{ id: 'Shape', position: { x: 40, y: 40 }, width: 220,
attributes: ['# x: float', '# y: float', '# width: float', '# height: float',
'# rotation: float', '# visible: bool'],
methods: ['+ area(): float', '+ draw(): void', '+ bounds(): Rect'] },
{ id: 'Circle', position: { x: 350, y: 40 }, width: 220,
attributes: ['+ radius: float'], methods: ['+ area(): float'] },
{ id: 'Drawable', stereotype: 'interface', position: { x: 670, y: 40 }, width: 220,
methods: ['+ render(): void'] },
{ id: 'Button', position: { x: 980, y: 40 }, width: 220,
attributes: ['+ label: String'], methods: ['+ render(): void'] },
{ id: 'Playlist', position: { x: 40, y: 300 }, width: 220,
attributes: ['+ name: String'], methods: ['+ add(s): void'] },
{ id: 'Song', position: { x: 350, y: 300 }, width: 220,
attributes: ['+ title: String'], methods: ['+ play(): void'] },
{ id: 'Window', position: { x: 670, y: 300 }, width: 220,
attributes: ['+ title: String'], methods: ['+ close(): void'] },
{ id: 'TitleBar', position: { x: 980, y: 300 }, width: 220,
attributes: ['+ text: String'], methods: ['+ paint(): void'] },
{ id: 'Student', position: { x: 40, y: 560 }, width: 220,
attributes: ['+ name: String'], methods: ['+ enroll(): void'] },
{ id: 'Course', position: { x: 350, y: 560 }, width: 220,
attributes: ['+ code: String'], methods: ['+ start(): void'] },
{ id: 'Order', position: { x: 670, y: 560 }, width: 220,
attributes: ['+ id: int'], methods: ['+ total(): Money'] },
{ id: 'Product', position: { x: 980, y: 560 }, width: 220,
attributes: ['+ sku: String'], methods: ['+ price(): Money'] },
{ id: 'OrderService', position: { x: 40, y: 820 }, width: 220,
methods: ['+ checkout(o): void'] },
{ id: 'Logger', position: { x: 350, y: 820 }, width: 220,
methods: ['+ log(msg): void'] },
],
relationships: [
{ from: 'Circle', to: 'Shape', kind: 'inheritance',
fromSide: 'left', toSide: 'right' },
{ from: 'Button', to: 'Drawable', kind: 'realization',
fromSide: 'left', toSide: 'right' },
{ from: 'Playlist', to: 'Song', kind: 'aggregation',
fromSide: 'right', toSide: 'left' },
{ from: 'Window', to: 'TitleBar', kind: 'composition',
fromSide: 'right', toSide: 'left' },
{ from: 'Student', to: 'Course', kind: 'association',
multiplicity: ['0..*', '1..*'], fromSide: 'right', toSide: 'left' },
{ from: 'Order', to: 'Product', kind: 'directed-association',
multiplicity: ['1', '0..*'], fromSide: 'right', toSide: 'left' },
{ from: 'OrderService', to: 'Logger', kind: 'dependency', label: '«uses»',
fromSide: 'right', toSide: 'left' },
],
};
export function buildUmlExample() {
return umlDiagram(data);
}
export async function editShape(api: DiagramInstance): Promise<void> {
const shape = umlClass(api, 'Shape');
await shape.rename('AbstractShape');
await shape.setAbstract(true);
await shape.attributes.renameAt(0, '# left: float');
await shape.methods.add('+ move(): void');
await shape.resize({ height: 170 });
api.fitView(40);
}
Read the relationship notation
Each UmlRelationshipSpec names class ids in from and to. Set the whole at from for aggregation and composition; set the parent or interface at to for inheritance and realization. The kit supplies orthogonal routing and markers—you do not need to configure arrow shapes yourself.
kind | Line | Marker |
|---|---|---|
inheritance | Solid | Hollow triangle at to (generalization) |
realization | Dashed | Hollow triangle at to |
association | Solid | No arrowheads |
directed-association | Solid | Open arrow at to |
aggregation | Solid | Hollow diamond at from |
composition | Solid | Filled diamond at from |
dependency | Dashed | Open arrow at to |
multiplicity supplies the chips at the [from, to] ends. fromSide and toSide pin the attachment sides; the sample uses facing sides for each horizontal pair.
2. Mount the kit in your framework
Choose one installation command and one host sample. These samples use the same shared data and run editShape() only after the diagram mounts. The ready callback receives a live DiagramInstance.
JavaScript / TypeScript
render mounts the kit and returns the instance. It runs the kit's wiring step automatically, including multiplicity labels; do not add them yourself.
bashnpm install @grafloria/element @grafloria/renderer @grafloria/engine
html<div id="uml" style="height: 720px"></div>
<script type="module" src="/src/main.ts"></script>
Place uml-example.ts in src with this file. Keep the returned cleanup function for the close or unmount hook of the view that owns the diagram.
tsimport { render } from '@grafloria/element';
import { buildUmlExample, editShape } from './uml-example';
export function mountUml(container: HTMLElement): () => void {
container.style.height = '720px';
const api = render(buildUmlExample(), container);
void editShape(api);
return () => api.dispose();
}
const container = document.getElementById('uml');
if (!container) throw new Error('Missing #uml container');
export const unmountUml = mountUml(container);
React
Pass the kit to GrafloriaDiagram and edit through onReady. The component owns disposal on unmount.
bashnpm install @grafloria/react @grafloria/element @grafloria/renderer @grafloria/engine react react-dom
tsximport { GrafloriaDiagram } from '@grafloria/react';
import { buildUmlExample, editShape } from './uml-example';
const spec = buildUmlExample();
export default function App() {
return (
<div style={{ height: '720px' }}>
<GrafloriaDiagram spec={spec} onReady={editShape} />
</div>
);
}
Vue
Use GrafloriaDiagram's ready event to run editShape() on the mounted UML classes; see Documents and kits for the shared kit-host setup and lifecycle.
bashnpm install @grafloria/vue @grafloria/element @grafloria/renderer @grafloria/engine vue
vue<script setup lang="ts"> import { GrafloriaDiagram } from '@grafloria/vue'; import { buildUmlExample, editShape } from './uml-example'; const spec = buildUmlExample(); </script> <template> <GrafloriaDiagram :spec="spec" @ready="editShape" style="height: 720px" /> </template>
Angular
Import GrafloriaDiagramComponent into your standalone component. Its ready output supplies the instance; the host disposes it on destruction.
bashnpm install @grafloria/angular @grafloria/element @grafloria/renderer @grafloria/engine @angular/common @angular/core @angular/forms @angular/platform-browser rxjs
tsimport { Component } from '@angular/core';
import { GrafloriaDiagramComponent } from '@grafloria/angular';
import { buildUmlExample, editShape } from './uml-example';
@Component({
selector: 'app-root',
standalone: true,
imports: [GrafloriaDiagramComponent],
template: `
<grafloria-diagram [spec]="spec" (ready)="editShape($event)"
style="display: block; height: 720px" />
`,
})
export class AppComponent {
readonly spec = buildUmlExample();
readonly editShape = editShape;
}
Qwik
Use GrafloriaDiagram with spec$ to build the function-bearing kit spec in the browser, then edit through onReady$. No live instance enters resumable state in this sample. The component owns disposal on unmount. See Documents and kits for resumable kit state.
bashnpm install @grafloria/qwik @grafloria/element @grafloria/renderer @grafloria/engine @builder.io/qwik
tsximport { component$ } from '@builder.io/qwik';
import { GrafloriaDiagram } from '@grafloria/qwik';
import { buildUmlExample, editShape } from './uml-example';
export default component$(() => (
<div style={{ height: '720px' }}>
<GrafloriaDiagram
spec$={() => buildUmlExample()}
onReady$={(api) => { void editShape(api); }}
/>
</div>
));
3. Read and edit the live class
After the ready callback completes, Shape displays the italic title AbstractShape. Its first attribute reads # left: float, and its methods include + move(): void. The 170-pixel card keeps its name compartment outside a scrolling body: scroll inside the card to reach its lower members. The other pairs show triangles, diamonds, plain and directed associations, multiplicity chips and a dashed dependency.
umlClass(api, 'Shape') returns a typed UmlClass handle. The id remains Shape after a rename. Getters read the live class; spec returns a deep copy, so changing that copy does not edit the diagram.
- Read members with
attributes.list()ormethods.list(), read their count withlength, and read one withat(index). - Append with
add(member), or insert withadd(member, { at: index }). - Replace a member string with
renameAt(index, member)and delete one withremoveAt(index). - Change the title with
rename(), the italic flag withsetAbstract(), and the displayed stereotype withsetStereotype(). - Cap the body with
resize({ height: 170 }), as the shared callback does. You can also declareheightin the initial class data.
The edit methods return Promise<boolean>. On this mounted instance each call enters the engine's command history and repaints the card; await one edit before deriving the next from the live members. The five calls in editShape() are five history steps, not one transaction. For toolbar undo and redo, see Commands and history.
Because editable: true is set, you also get the kit's in-canvas editing: double-click a class name or member to rename it, use + attribute or + method to add a member, and use the member's × control to delete it. These edits use the same undoable update path. Typed-handle edits do not require this editing chrome.
Options that matter
UmlClassSpec controls each card's data and dimensions.
| Option | Type | Default | What it does |
|---|---|---|---|
editable | boolean | false | Adds rename, add and delete interactions to the cards. |
rowSelection | boolean | Enabled unless false | Enables selectable members. |
Class name | string | Class id | Sets the displayed title without changing its id. |
Class stereotype | string | None | Displays «stereotype» above the title; abstract and interface also italicize the title. |
Class height | number | Derived from members | Fixes the card height and enables a scrolling body when content exceeds it. |
Class width | number | Derived from content, between 200 and 420 pixels | Sets an explicit width instead of auto-sizing. |
Relationship kind | Seven string literals listed above | 'association' | Chooses the line and marker notation. |
Relationship multiplicity | [string, string] | None | Adds chips at the from and to ends. |
Pitfalls
- Acquire handles after mounting.
umlClass()throws if the id is absent or the node is not a kit UML class. - Use the handle to edit, not a modified
speccopy. A changed kit spec passed to the React, Vue or Angular host replaces the diagram; keep the seed spec stable while editing the live instance. - A fixed height caps the body, not the member list: members remain in the data and are reached by scrolling. Long member strings stay on one line and ellipsize when they exceed the available width; set an explicit width when you need more space.
- The kit suppresses canvas resize handles on its cards. Use class dimensions or the typed
resize()method to size them.
Live demos and related guides
- Class diagram — three-compartment cards, inheritance and aggregation.
- UML relationships — all seven relationship kinds, plus a hand-composed self-association. Source.
- Scrollable cards — capped card bodies.
- Documents and kits — how kit specs compose with the shared document model.
- Save and restore documents — persist the live diagram rather than its seed spec.
Was this page helpful?