# Edit UML classes

Use the UML kit when your data describes classes, members and relationships. Build the diagram with [`umlDiagram`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-element-diagram-kit-functions#umldiagram), then edit its mounted classes with [`umlClass`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-element-diagram-kit-functions#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`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-element-diagram-kit-interfaces#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.

```ts title="uml-example.ts"
import { 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`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-element-diagram-kit-interfaces#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`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-renderer-instance-diagraminstance#diagraminstance).

### JavaScript / TypeScript

[`render`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-element-core#render) mounts the kit and returns the instance. It runs the kit's wiring step automatically, including multiplicity labels; do not add them yourself.

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

```html title="index.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.

```ts title="src/main.ts"
import { 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`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-react#grafloriadiagram) and edit through `onReady`. The component owns disposal on unmount.

```bash
npm install @grafloria/react @grafloria/element @grafloria/renderer @grafloria/engine react react-dom
```

```tsx title="App.tsx"
import { 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>
  );
}
```

![Four rows contain seven UML card pairs with the italic AbstractShape title, triangles, diamonds, multiplicity labels and member-add controls.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/21a19a42fe24285613092b5d4f8d3564.png)

### Vue

Use [`GrafloriaDiagram`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-vue#grafloriadiagram)'s `ready` event to run `editShape()` on the mounted UML classes; see [Documents and kits](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/documents-and-kits#vue) for the shared kit-host setup and lifecycle.

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

```vue title="App.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`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-angular-components-classes#grafloriadiagramcomponent) into your standalone component. Its `ready` output supplies the instance; the host disposes it on destruction.

```bash
npm install @grafloria/angular @grafloria/element @grafloria/renderer @grafloria/engine @angular/common @angular/core @angular/forms @angular/platform-browser rxjs
```

```ts title="app.component.ts"
import { 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`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-qwik#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](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/documents-and-kits) for resumable kit state.

```bash
npm install @grafloria/qwik @grafloria/element @grafloria/renderer @grafloria/engine @builder.io/qwik
```

```tsx title="uml-view.tsx"
import { 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`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-element-diagram-kit-classes#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()` or `methods.list()`, read their count with `length`, and read one with `at(index)`.
- Append with `add(member)`, or insert with `add(member, { at: index })`.
- Replace a member string with `renameAt(index, member)` and delete one with `removeAt(index)`.
- Change the title with `rename()`, the italic flag with `setAbstract()`, and the displayed stereotype with `setStereotype()`.
- Cap the body with `resize({ height: 170 })`, as the shared callback does. You can also declare `height` in 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](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/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`](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/grafloria-element-diagram-kit-interfaces#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 `spec` copy. 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](https://grafloria.com/demos/diagrams/class-uml.html) — three-compartment cards, inheritance and aggregation.
- [UML relationships](https://grafloria.com/demos/diagrams/uml-relationships.html) — all seven relationship kinds, plus a hand-composed self-association. [Source](https://github.com/grafloria/grafloria/blob/cccb505e76c7af26c8bafecaba8146db7d8df928/demos/diagrams/uml-relationships.html).
- [Scrollable cards](https://grafloria.com/demos/diagrams/scrollable-cards.html) — capped card bodies.
- [Documents and kits](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/documents-and-kits) — how kit specs compose with the shared document model.
- [Save and restore documents](https://bench-grafloria-61d.atloria.app/p/bench-grafloria-61d-mQCmWpDrxv/developer/save-and-restore-documents) — persist the live diagram rather than its seed spec.
