Elements
In @joint/react, an element has two parts:
- an element record in graph state, which stores layout and metadata
- a
renderElementfunction onPaper, which turns the element'sdatainto UI
Most custom elements start with HTMLHost or HTMLBox. HTMLHost is the style-neutral primitive. HTMLBox wraps it with the built-in jj-box styling. Reach for the useHTMLOverlay prop when you need real HTML outside SVG, and return raw SVG when you need to render complicated shapes that are unobtainable with pure HTML elements.
This chapter stays at the element-rendering level. Lower-level measurement details are covered later in Measuring Elements.
The example below is complete; the rest of the chapter breaks it down piece by piece.
Size Paper with CSS via className or style. Without a visible paper area, there is nowhere to render the diagram.
This example uses HTMLHost with a custom demo-card class, so all of the styling comes from your own CSS in example.css. If you want the built-in themed look, use HTMLBox or omit renderElement and rely on the default renderer.
ElementRecord​
ElementRecord<D> is the shape of one element in graph state. These are the fields you will use most often:
{
id: 'task-1', // stable identifier
type: 'element', // marks this cell as an element
position: { x: 40, y: 40 }, // where the element sits
size: { width: 220, height: 88 }, // optional layout constraints
angle: 0, // rotation in degrees
data: { label: 'Design API', assignee: 'Alice' }, // your custom props
portMap: { out: { cx: 'calc(w)', cy: 'calc(0.5 * h)' } },
portStyle: { color: '#131e29' },
}
data is the bridge between graph state and your React renderer. Whatever you put in data is passed to renderElement.
Element records are not limited to these fields. They extend JointJS element attributes, so advanced native properties can still pass through when needed. For day-to-day React usage, though, position, size, angle, data, portMap, and portStyle are the main ones to know. See Ports for declarative port definitions.
renderElement and hooks​
Paper.renderElement receives the element's data, not the full layout object. That keeps your renderer focused on user data while layout stays in graph state.
When a renderer needs the current element's layout or identity, read it with hooks from inside the rendered component:
useCell(selectElementSize)for the current width and heightuseCell(selectElementPosition)for the current x/y positionuseCellId()for the current element IDuseCell(selectElementData<T>)if you prefer readingdatafrom context instead of props
@joint/react ships these as predefined selector helpers (see Accessing State for the full list). In practice, it is usually cleaner to pass a component to renderElement and use hooks inside that component.
The default renderer​
If you do not provide renderElement, Paper uses its built-in default element renderer.
That renderer:
- reads
data.label - renders it inside
HTMLBox - applies the built-in
jj-boxclass and--jj-box-*theme styling
This is the zero-config path for simple label-based nodes.
<Paper className="h-full" />
The snippet above shows the minimal API shape with the paper sized via Tailwind. The preview below shows two nodes rendered entirely by the built-in HTMLBox-based default renderer:
HTMLHost​
HTMLHost is the style-neutral building block for custom HTML elements. It renders a div inside an SVG <foreignObject> and measures the content for you by default.
Use it first when you want custom nodes with regular JSX, CSS, Tailwind classes, event handlers, and automatic sizing.
<HTMLHost className="my-node" style={{ padding: 12 }}>
<h3>{title}</h3>
<p>{description}</p>
</HTMLHost>
It accepts standard div props like children, style, className, event handlers, and data-* attributes.
Important distinction: HTMLHost does not apply the built-in jj-box class. If you want the built-in themed appearance, use HTMLBox instead.
HTMLBox​
HTMLBox is the convenience wrapper for the built-in themed element look. It wraps HTMLHost, adds the jj-box class, and applies the base styles used by the default renderer.
<Paper renderElement={({ label }) => <HTMLBox>{label}</HTMLBox>} />
Use HTMLBox when you want the built-in element styling but still want to keep an explicit renderElement.
The snippet above is the minimal API shape. The result below looks the same as the default renderer, but now the rendering stays in your own component boundary:
Sizing​
By default, HTMLHost measures its own content and syncs that size back to the graph element.
Use useModelGeometry when you want the host to render against the element size already stored in the model.
HTMLBox follows the same rule, because it wraps HTMLHost.
useModelGeometry is specific to HTMLHost and HTMLBox. It does not apply to regular HTML returned through useHTMLOverlay, and raw SVG sizing follows its own model size plus useCell(selectElementSize) flow.
| Host configuration | Behavior |
|---|---|
<HTMLHost> or <HTMLBox> | Measures content by default |
<HTMLHost useModelGeometry> | Uses the element's model size |
<HTMLBox useModelGeometry> | Uses the element's model size with built-in styling |
Examples:
Auto-sized content
{
id: 'badge',
type: 'element',
position: { x: 100, y: 100 },
// no size -> content decides the final size
data: { label: 'New' },
}
Fixed width and height from the model
{
id: 'card',
type: 'element',
position: { x: 100, y: 100 },
size: { width: 200, height: 72 },
data: { label: 'Stable' },
}
<HTMLHost useModelGeometry>
Stable
</HTMLHost>
Themed box with model geometry
{
id: 'tag',
type: 'element',
position: { x: 100, y: 100 },
size: { width: 160, height: 48 },
data: { label: 'Stable' },
}
<HTMLBox useModelGeometry>
Stable
</HTMLBox>
When you want the content itself to determine the node size, keep the default measured mode. When you want the rendered host to follow the graph element's current size, switch to useModelGeometry.
In the example below, the left node shrinks to its content while the right node fills the fixed size stored on the element record:
Real HTML with the useHTMLOverlay prop​
useHTMLOverlay is experimental, with known issues in HTML rendering, and its API may change in future releases. Use it at your own risk.
Paper normally renders element content into SVG. If you need real HTML outside SVG, enable the useHTMLOverlay prop.
<Paper useHTMLOverlay renderElement={OverlayCard} />
Use useHTMLOverlay when you need full browser HTML/CSS behavior or when foreignObject is not a good fit for your node.
Important differences from HTMLHost:
- Return regular HTML, not
HTMLHost - Keep
sizeon the element record when you want a fixed box - Treat explicit
sizeas the simplest default for overlay-rendered nodes
The snippet above is the core API shape. The example below uses regular HTML cards with explicit size on each element record:
Going full SVG​
For shapes and effects that HTML + CSS can't cleanly produce (composite silhouettes, SVG filters, selector-based SVG content), return SVG directly from renderElement.
For fixed-size SVG elements, give the element a size and read it with useCell(selectElementSize):
const { width, height } = useCell(selectElementSize);
If the SVG content should determine the size instead of the model size, continue to Measuring Elements for the measuring flow.
The example below composes a database-cylinder from a <rect> and two <ellipse> elements. The same silhouette would require stacked <div>s with clip-path hacks in plain HTML, whereas in SVG it falls out of a few primitives bound to useCell(selectElementSize):
SVG paints children in document order: each later element renders on top of the previous ones. In the cylinder above, the body <rect> comes first, the bottom <ellipse> overlays it to round the lower edge, and the top <ellipse> sits on top as the rim. Swap the order and the rim disappears behind the body. There is no z-index in SVG, reorder your JSX instead.
Quick reference​
Cells are an array of records. Each element record carries id, type: 'element', and the fields below.
Layout​
position: element coordinates on the canvas. Omitted fields default to0.size: optional dimensions. MeasuredHTMLHost/HTMLBoxcan derive size from content,useModelGeometryreads the size already stored in the model, and raw SVG can be either fixed-size or content-driven when you measure it yourself.angle: rotation in degrees around the center. Default0.
Data​
data: custom element data passed torenderElement.
Ports​
portMap: declarative port definitions keyed by port ID.portStyle: shared defaults applied to every port inportMap.