Composition is the main extension mechanism in TanStack Charts. Marks share a chart specification, scales, theme, and responsive plot rectangle. More complex views combine those same primitives instead of switching to a separate component family.
Marks are rendered in array order. Put broad background geometry first and annotations or direct labels last:
const definition = defineChart({
marks: [
ruleY([0]),
areaY(rows, { x: 'date', y1: 'low', y2: 'high' }),
lineY(rows, { x: 'date', y: 'median' }),
dot(highlights, { x: 'date', y: 'median' }),
text(labels, { x: 'date', y: 'median', text: 'label' }),
],
x,
y,
})Each mark may consume a different datum type. The resulting chart interaction type is the honest union of those datum types. Use ordinary TypeScript narrowing when a callback handles several layers.
The canonical grammar is described in Marks and Layering.
facetChart repeats a mark composition for each group and returns a complete guide-free outer definition:
const definition = facetChart(rows, {
by: 'group',
columns: 2,
gap: 20,
axes: 'outer',
chart(data) {
return {
marks: [dot(data, { x: 'x', y: 'y' })],
x: { scale: scaleLinear().domain(sharedX) },
y: { scale: scaleLinear().domain(sharedY) },
}
},
})Use facet(rows, options) instead when the repeated panels need to be one mark inside a larger custom definition.
The default axes: 'outer' draws shared guides around the complete facet grid. Use axes: 'cell' when each panel needs its own guides. Cell axes and incompatible independent scales cannot be presented as one shared outer axis; choose the option that matches the comparison.
Shared scales make position comparable across panels. Independent domains make local variation easier to see but can exaggerate differences.
Build shared domains once and pass configured scales to every facet. When a panel deliberately uses an independent domain, label that policy in the surrounding UI.
The Scales and D3 page owns scale construction and responsive range rules.
Not every composition is a facet. A focus-and-context chart, scatterplot with marginal histograms, or chart-plus-table contains views with different roles. In that case:
Do not coordinate charts by querying or mutating their SVG nodes.
When several plot regions must live inside one SVG, a custom mark can render scene groups with explicit translations and clips. Use that only when one responsive scene is materially better than several accessible chart hosts.
The Custom Marks and Renderers guide explains the scene-node protocol. The Responsive Charts guide explains how to base region geometry on the final chart bounds.