Diagrams
DiagramCanvas
A frame that a picture larger than it can be panned and zoomed inside.
Usage
When to reach for it
Ships from@misoto22/design/diagrams
import { DiagramCanvas } from '@misoto22/design/diagrams'Notes
A frame that a picture larger than it can be moved around inside.
Pan with a drag, zoom with the controls or with ⌘/Ctrl and the wheel, and reset with a key. That is the whole of it — this is deliberately a VIEWPORT and not a diagram editor: nothing here knows what a node is, so it works for any oversized figure, an SVG, an image, a table that will not fold.
Three decisions here are not obvious.
A plain wheel scrolls the page, not the diagram. A canvas that swallows the wheel is a scroll trap: a reader flicking down an article hits the figure and the page stops moving for no reason they can see. Zooming needs the modifier — which is also the platform gesture for zoom everywhere else, and is what a trackpad pinch already sends.
The transform is on a wrapper, not on the content. The child keeps its own coordinate space, so a figure inside can still be measured, exported and read by anything that walks it. Scaling the child directly would make every getBoundingClientRect inside it a lie.
Keyboard first, and the frame is a real tab stop. + / - / 0 and the arrow keys move the view; the frame takes focus so they can be pressed at all. A canvas that only answers a drag is a canvas half the readers cannot operate.
Anatomy
| Element | Description |
|---|---|
| Framerequired | The outer box: a fixed height — 24rem unless height says otherwise — a hairline, the diagram ground, and overflow hidden. It is the window the artwork is bigger than. |
| Viewportrequired | The focusable layer inside it: role="group" with a name, tabIndex 0, the drag handlers, the key handler and a focus ring drawn inside the frame. This is the tab stop, and it is what makes the keys pressable at all. |
| Stagerequired | The wrapper the translate and scale are applied to. The transform is on the wrapper and never on the child, so the figure inside keeps its own coordinate space and every measurement taken inside it stays true. |
| Zoom controls | The cluster pinned to the bottom corner: zoom out, the current percentage — which is also the reset button, named for what it does — and zoom in. controls={false} removes the cluster and leaves the keys. |
| Keyboard hintrequired | A visually hidden paragraph, referenced by aria-describedby, saying that the frame drags and which keys pan, zoom and reset. Nothing on screen carries that sentence. |
Best practices
Do
- Name it. label is the group’s accessible name and it defaults to "Diagram canvas", so two canvases on one page announce the same thing until each is given its own.
- Strip the figure inside to its artwork with heading={false}, legend="hidden" and cards={false}. Everything in the frame pans and zooms together, so a title left on travels away from the diagram it names and a key leaves the frame at exactly the zoom that made a reader want it.
- Move the view through the ref — zoomIn, zoomOut, reset, centerOn — rather than re-rendering the child at a new size. centerOn takes a point in the content’s own coordinates, which is what a minimap reports back.
- Hand onViewChange straight to a minimap. Each view carries the frame it was measured against as well as the scale and the offset, and that pair is the whole of the arithmetic a viewport rectangle is — the frame being the one number nothing outside the canvas can measure.
Don’t
- Nothing clamps the pan. The offset is whatever the drag or the arrow keys left it at, so the artwork can be pushed entirely outside the frame; 0 and the reset button are the whole way back, and controls={false} without a replacement takes the pointer’s half of that away.
- The scale stops at 0.35. A figure more than about three times the frame cannot be zoomed out far enough to be seen whole, so the frame has to be sized for the diagram — a minimap answers where you are, never what is there.
- The wheel is left alone and the thumb is not: the frame sets touch-action to none, so a finger dragged inside it pans the diagram and never scrolls the page. A full-width canvas in an article is a band a touch reader has to swipe around rather than through, which is the argument for giving it a height short enough to leave page beside it.
Examples
default
A figure wider than the room it is given, in a frame that can be moved around it. A drag pans, the plate in the corner zooms, and the percentage between its two buttons is the reset; a plain wheel is deliberately left to the page, so only Command or Control with the wheel zooms. The frame is a real tab stop, so plus, minus, zero and the arrow keys do the same work with no pointer at all. The figure’s own heading, key and cards are turned off here because the canvas is holding artwork, not an article.
4 elements and 3 relationships.
- Browser (external)
- CloudFront (cloud) — CDN
- API (backend) — FastAPI
- Postgres (database)
- Browser → CloudFront: HTTPS
- CloudFront → API
- API → Postgres: SQL
Drag to pan. Plus and minus zoom, zero resets, the arrow keys pan.
your own controls
controls={false} removes the plate in the corner, and everything it did has to be put back somewhere: zoomIn, zoomOut and reset live on the handle, and a ref is the only way to reach them. Turn the built-in controls off when the page already has a toolbar and two sets of zoom buttons would compete for the same job. Turning them off and supplying nothing leaves zoom to the modifier-wheel and the keyboard, neither of which a reader has any way to discover.
3 elements and 2 relationships.
- CloudFront (cloud) — CDN
- API (backend) — FastAPI
- Postgres (database)
- CloudFront → API: HTTPS
- API → Postgres: SQL
Drag to pan. Plus and minus zoom, zero resets, the arrow keys pan.
following the view
onViewChange fires on every pan, zoom and reset with the same three numbers the canvas is transforming by, which is what a percentage readout, a minimap or a deep link into the diagram is built out of. centerOn goes the other way and takes a point in the artwork’s own coordinate space, not a node id — the canvas is a viewport and knows nothing about nodes, so a caller that wants to jump to one has to know where it was drawn.
4 elements and 3 relationships.
- Browser (external)
- CloudFront (cloud)
- API (backend)
- Postgres (database)
- Browser → CloudFront
- CloudFront → API
- API → Postgres: SQL
Drag to pan. Plus and minus zoom, zero resets, the arrow keys pan.
Keyboard
| Key | Does |
|---|---|
| += | Zooms in about the centre of the frame. |
| - | Zooms out. |
| 0 | Resets the scale and the offset together. |
| ←→↑↓ | Pans. Shift pans further per press. |
Accessibility
- The frame is a real tab stop, so the keyboard controls can be pressed at all.
- A plain wheel scrolls the page. Zoom needs the platform modifier, so the canvas is never a scroll trap in the middle of an article.