Import view composition from the exact subpath:
import {
alignX,
alignY,
composeViews,
fill,
grid,
inset,
layer,
shareX,
shareY,
viewGrid,
} from '@tanstack/charts/view'composeViews(options) combines named composable chart definitions into one static chart definition. A child may be static or responsive. Responsive builders receive the allocated child frame and the outer runtime's defaultTheme. Each child retains its own marks, scales, guides, margins, color, clipping, and static legends.
The public types are ComposeViewsOptions, ViewDefinitions, ViewLayout, ViewAnchor, ViewInsetOptions, ViewGridCell, ViewAxis, ViewScaleLinkMode, ViewScaleLink, ViewTrack, ViewLink, ViewGridItem, ViewGridOptions, ComposableChartDefinition, ComposableStaticChartDefinition, and ComposableResponsiveChartDefinition.
fill, layer, and inset can place a polar chart over a Cartesian chart:
import { defineChart } from '@tanstack/charts'
import { pie, polar, radialArc } from '@tanstack/charts/polar'
import { composeViews, fill, inset, layer } from '@tanstack/charts/view'
const summaryRows = [
{ status: 'Complete', count: 72 },
{ status: 'Remaining', count: 28 },
]
const slices = pie(summaryRows, { value: 'count' })
const summaryDefinition = defineChart({
marks: [
polar({
inset: 8,
marks: [
radialArc(slices, {
innerRadius: ({ radius }) => radius * 0.58,
color: 'status',
key: 'status',
}),
],
}),
],
guides: false,
margin: 0,
})
const definition = composeViews({
views: {
detail: scatterDefinition,
summary: summaryDefinition,
},
layout: layer(
fill('detail'),
inset('summary', {
relativeTo: 'detail',
anchor: 'top-right',
width: 160,
height: 160,
offset: 12,
}),
),
})layer resolves children in paint order, so the summary paints after the detail chart. Every child is clipped to its resolved frame. Transparent summary space and the donut hole add no interaction target, leaving detail geometry behind them eligible for outer-chart focus.
An inset is relative to the complete resolved frame named by relativeTo, not that child's inner plot rectangle. Its preferred width, height, and offset shrink proportionally when the referenced frame is too small. The referenced view must appear earlier in paint order.
Every key in views must be placed exactly once. Unknown, missing, and duplicate view placements fail instead of producing a partial scene.
grid accepts fixed and flexible tracks:
Fixed sizes and minimums shrink deterministically when the host is smaller than their preferred total. One view may occupy each row-and-column cell.
const definition = composeViews({
views: {
main: scatterDefinition,
top: xHistogramDefinition,
right: yHistogramDefinition,
},
layout: grid({
rows: [
{ id: 'top', size: 72 },
{ id: 'main', grow: 1 },
],
columns: [
{ id: 'main', grow: 1 },
{ id: 'right', size: 72 },
],
gap: 8,
cells: {
main: { row: 'main', column: 'main' },
top: { row: 'top', column: 'main' },
right: { row: 'main', column: 'right' },
},
}),
links: [shareX('top', 'main'), shareY('right', 'main')],
})Scale links are separate from layout:
Linked views must have equal allocated frames along the linked axis. For example, x-linked views must have the same frame x position and width.
Sharing does not copy or infer another view's domain. Configure the intended domain on both child definitions so a mismatch fails visibly.
The composed definition has one chart host, accessible chart label, tooltip, keyboard model, focus strategy, and animation lifecycle. Child points keep their datum identity and receive stable view-prefixed keys and mark IDs. One outer default focus layer covers every child; explicit child focus and mark-state layers remain part of their child scenes.
Apply host options to the composed definition:
const interactiveDefinition = defineChart(definition, {
keyboard: true,
maxFocusDistance: 40,
})Children must satisfy ComposableChartDefinition. Host-owned options and resources that cannot be adopted into the outer scene are rejected by the public type, including selection, controls, tooltips, keyboard options, definition-level motion, gradients, and scene backgrounds. Runtime validation still protects JavaScript and deliberately widened TypeScript values. Guide motion is also rejected after a responsive child resolves. Mark-local motion and static legends remain supported. Use an ordinary mark for a child background.
Use separate chart hosts when panels need independent host behavior or independent accessible labels.
viewGrid remains an ergonomic wrapper for a non-overlapping grid. It lowers to the same composition, layout, and scale-link machinery:
const definition = viewGrid({
rows: [
{ id: 'top', size: 72 },
{ id: 'main', grow: 1 },
],
columns: [{ id: 'main', grow: 1 }],
gap: 8,
views: [
{
id: 'top',
row: 'top',
column: 'main',
share: { x: 'main' },
chart: xHistogramDefinition,
},
{
id: 'main',
row: 'main',
column: 'main',
chart: scatterDefinition,
},
],
})Use composeViews when the layout combines grids, layers, or insets. Use viewGrid when one named view per grid cell is the clearest expression.