TanStack
Mark Reference

Text, Frame, and Facet Marks

text annotates scaled positions, frame paints the resolved inner chart bounds, and facet composes complete child specs into responsive small multiples.

ts
import { facet, facetChart, frame, lineY, text } from '@tanstack/charts'

text

ts
text(rows, {
  x: 'date',
  y: 'value',
  text: 'label',
  z: 'series',
  dy: -8,
})
ts
function text<TDatum>(
  source: Iterable<TDatum>,
  options?: TextOptions<TDatum>,
): ChartMark<TDatum, InferredX, InferredY>

Options

OptionTypeDefaultMeaning
idstringLayer-derivedStable mark ID
xChannel<TDatum, ChartValue?>Row indexHorizontal anchor
yChannel<TDatum, ChartValue?>Numeric datumVertical anchor
textChannel<TDatum, string | number?>String form of datumLabel content
zChannel<TDatum, ChartKey?>No groupInteraction group and default fill
keyChannel<TDatum, ChartKey>Unique id, then indexStable identity
fillVisualChannel<TDatum, string>Theme foreground without z; resolved color with zLabel fill
fontSizenumberInherited SVG font sizeFont size
fontWeightnumberInherited weightNumeric font weight
anchorVisualChannel<TDatum, TextAnchor>'middle''start', 'middle', or 'end'
rotateVisualChannel<TDatum, number>No transformRotation in degrees around the final label origin
dxVisualChannel<TDatum, number>0Horizontal pixel offset
dyVisualChannel<TDatum, number>0Vertical pixel offset

Labels use a middle baseline. Null or undefined text skips the row; the default for a null datum is an empty string. Invalid x/y values also skip the row.

The interaction point is at the offset label origin. Its semantic values remain the original x/y channels. Use a stable key when labels enter, exit, or reorder.

frame

frame draws a background, border, or both around the final inner chart bounds:

ts
frame({
  fill: 'color-mix(in srgb, currentColor 3%, transparent)',
  strokeOpacity: 0.25,
  radius: 8,
})
ts
function frame(options?: FrameOptions): ChartMark<never, never, never>
OptionTypeDefaultMeaning
idstringLayer-derivedStable mark ID
fillstring'none'Constant fill
fillOpacitynumberSVG defaultFill opacity
strokestringTheme foregroundConstant stroke
strokeOpacitynumber0.35Stroke opacity
strokeWidthnumber1Stroke width
insetnumber0Pixels removed from all chart-bound edges; clamped to at least zero
radiusnumberNoneCorner radius

frame materializes no scale channels and emits no interaction points. Put it before data marks when it should paint behind them.

facet

facet groups source rows by a key and renders one complete child chart spec per group inside the parent chart bounds.

ts
facet(rows, {
  by: 'region',
  columns: 3,
  minWidth: 220,
  gap: 16,
  label: (region) => `Region: ${region}`,
  chart(groupRows) {
    return {
      marks: [lineY(groupRows, { x: 'date', y: 'value' })],
      x: { scale: makeXScale(groupRows) },
      y: { scale: makeYScale(groupRows), grid: true },
    }
  },
})
ts
function facet<TDatum>(
  source: Iterable<TDatum>,
  options: FacetOptions<TDatum>,
): ChartMark<TDatum>

Options

OptionTypeDefaultMeaning
idstringLayer-derivedStable outer mark ID
byChannel<TDatum, ChartKey>RequiredString or number grouping key
chart(data: readonly TDatum[], key: ChartKey) => ChartSpecRequiredBuilds one static child spec per group
columnsnumberAutomaticRequested column count, floored and clamped to 1..groupCount
minWidthnumber220Target minimum cell width used for automatic columns
gapnumber16Gap between rows and columns, clamped to at least zero
labelboolean | ((key) => string)trueShows default key labels, formats them, or disables labels
axes'outer' | 'cell''outer' where shareableShared outside axes or independent axes in every cell

Facet preserves first-seen group order and original row order within each group. Non-string and non-number by results are skipped.

Automatic columns are:

plaintext
floor((availableWidth + gap) / (minWidth + gap))

with a minimum of one. Rows are then derived from group count. Visible facet labels reserve 22 pixels above every child plot.

Outer axes

With more than one child and guides enabled, the default axes: 'outer' renders y ticks on the first column, x ticks on the final occupied row, and one shared title per dimension.

Outer axes require compatible cells:

  • x and y resolved scale types, domains, ticks, labels, and direction match
  • axis label, label offset, and tick rotation match
  • foreground and muted theme tokens match
  • no child provides its own margin
  • no child provides a color legend
  • no child sets guides: false

An incompatible facet throws with guidance to use axes: 'cell'. This fail-fast behavior prevents a shared axis from implying a comparison the child scales do not support.

When every child sets guides: false, or there is only one child, facet uses the cell rendering path even if axes is omitted.

Cell axes

Set axes: 'cell' when groups need independent scales, guide options, margins, or legends:

ts
facet(rows, {
  by: 'region',
  axes: 'cell',
  chart: (groupRows) => buildIndependentSpec(groupRows),
})

Every child compiles at its own cell dimensions with the parent's text measurement. Compilation errors are wrapped with the facet and cell key.

Interaction

Child ChartPoint coordinates are offset into the parent scene. Point keys are prefixed with facet and group identity, so equal child keys remain unique across cells. Datum, semantic x/y values, group, group label, and paint are preserved.

The facet mark itself has intentionally opaque positional types because each child may return a different mark composition. Its child scale domains do not participate in the parent x/y scales.

facetChart

facetChart wraps a facet mark in a complete static definition:

ts
const definition = facetChart(rows, {
  by: 'region',
  chart: (groupRows) => buildSpec(groupRows),
})
ts
function facetChart<TDatum>(
  source: Iterable<TDatum>,
  options: FacetOptions<TDatum>,
): StaticChartDefinition<TDatum>

The wrapper sets:

ts
const specification = {
  marks: [facet(source, options)],
  guides: false,
  margin: 0,
  x: null,
  y: null,
}

Use facet directly when the small multiples must be layered with other parent-scene marks. Use facetChart for the usual standalone chart.