TanStack
API Reference

Focus and Interaction

The DOM host and framework adapters provide point-level interaction from each mark's emitted ChartPoint values. The defaults cover nearest-point pointer focus, linear keyboard navigation, activation, and an optional native tooltip. Definitions own these policies; adapters only mount them and report events.

Default behavior

With no custom focus strategy:

  • pointer movement finds the nearest point within maxFocusDistance
  • pointer leave or cancellation clears unpinned focus
  • the SVG uses tabIndex (0 by default) when keyboard is enabled
  • focusing the SVG selects the first point in the keyboard task order
  • arrow keys move through points sorted by pixel x, then pixel y
  • Home and End move to the first and last point
  • Enter and Space toggle an enabled sticky tooltip and call onSelect for the focused point
  • a click focuses and selects the nearest point, or selects null
  • the renderer's focus ring follows the primary point

maxFocusDistance defaults to 48 scene pixels. Set tabIndex to control normal tab-order participation while keeping keyboard handling enabled. Set keyboard: false to remove keyboard navigation and force tab index -1.

Focus modes

Use a preset for built-in focus behavior:

ts
const groupedDownloads = defineChart(definition, {
  focus: 'group-x',
  tooltip: true,
})
PresetPointer resolutionGroup returned to callbacks and tooltipKeyboard navigation
nearestNearest point in two dimensionsPrimary point onlyEvery point
nearest-xNearest x coordinate, then nearest yPrimary point onlyEvery point
nearest-yNearest y coordinate, then nearest xPrimary point onlyEvery point
group-xNearest x coordinate, then nearest y within that xOne point per group sharing the semantic x value; nearest point firstOne representative per semantic x value
group-yNearest y coordinate, then nearest x within that yOne point per group sharing the semantic y value; nearest point firstOne representative per semantic y value

Grouping compares semantic values, including dates by timestamp. Duplicate points with the same group value are reduced to one member in grouped focus.

The equivalent focusX, focusY, focusNearestX, and focusNearestY strategy objects remain available from @tanstack/charts/focus for composition or direct strategy use.

Disabling chart-owned focus

ts
import { focusDisabled } from '@tanstack/charts/focus/disabled'

focusDisabled resolves, groups, and navigates to no points. Use it when an application owns gestures, selection paint, accessibility, and task semantics outside the native focus layer. It does not remove the rendered focus node or other DOM listeners; set definition keyboard: false and omit its tooltip as appropriate for the application-owned interaction.

Custom focus strategies

ts
interface ChartFocusStrategy<
  TDatum = unknown,
  TXValue extends ChartValue = ChartValue,
  TYValue extends ChartValue = ChartValue,
> {
  resolve(
    points: readonly ChartPoint<TDatum, TXValue, TYValue>[],
    x: number,
    y: number,
    maxDistance: number,
  ): readonly ChartPoint<TDatum, TXValue, TYValue>[]

  group(
    points: readonly ChartPoint<TDatum, TXValue, TYValue>[],
    point: ChartPoint<TDatum, TXValue, TYValue>,
  ): readonly ChartPoint<TDatum, TXValue, TYValue>[]

  navigation(
    points: readonly ChartPoint<TDatum, TXValue, TYValue>[],
  ): readonly ChartPoint<TDatum, TXValue, TYValue>[]
}

resolve receives scene-pixel pointer coordinates and returns primary point first. group is called when an existing point is restored or reached through keyboard navigation. navigation returns the ordered keyboard task set.

ChartFocusMode accepts a ChartFocusPreset string or a ChartFocusStrategy.

Tooltips

Set tooltip: true for the native accessible tooltip. The default is a structured label-value table. Grouped focus adds a shared-axis heading and one color swatch and value row per series. Visible axis labels are reused. Numbers use browser locale formatting; dates use stable UTC ISO formatting.

ts
interface ChartTooltipOptions<
  TDatum = unknown,
  TXValue extends ChartValue = ChartValue,
  TYValue extends ChartValue = ChartValue,
> {
  className?: string
  portal?: boolean
  items?: readonly ChartTooltipItem<TDatum, TXValue, TYValue>[]
  sort?: ChartTooltipSort<TDatum, TXValue, TYValue>
  anchor?: ChartTooltipAnchor<TDatum, TXValue, TYValue>
  placement?: 'auto' | ChartTooltipPlacement | readonly ChartTooltipPlacement[]
  offset?: number
  content?: (
    points: readonly ChartPoint<TDatum, TXValue, TYValue>[],
    context: ChartTooltipContentContext,
  ) => ChartTooltipContent
  format?: (point: ChartPoint<TDatum, TXValue, TYValue>) => string
  formatGroup?: (
    points: readonly ChartPoint<TDatum, TXValue, TYValue>[],
  ) => string
  sticky?: boolean
}
OptionDefaultMeaning
classNameNoneClass appended after ts-chart-tooltip
portalfalseEscapes clipping through top-layer or fixed positioning
itemsAutomatic x/yOrdered rows for a single focused point
sortcolor-domainGrouped row order
anchorpointPoint, pointer, group center, or coordinate resolver
placementautoFixed or ordered fallback box placements
offset10Scene-pixel gap between anchor and box
contentAutomatic rowsReturns a safe title and structured rows
formatNoneReplaces content with primary-point text
formatGroupNoneReplaces content with focused-group text
stickytrueEnables activation-to-pin and text selection

Formatting precedence is content, formatGroup, format, then the default. The text formatters do not parse HTML, and newlines are preserved. className is appended to ts-chart-tooltip.

Ordered point items

items is an ordered single-point row list. Use x, y, and group shorthands, a configured channel, a scalar datum field, or derived text:

ts
const tooltip = {
  items: [
    {
      channel: 'y',
      label: 'Revenue',
      text: (point) => currency(point.yValue),
    },
    {
      field: 'volume',
      label: 'Volume',
      text: (point) => compact(point.datum.volume),
    },
    {
      id: 'change',
      label: 'Change',
      text: (point) =>
        point.datum.change == null ? null : percent(point.datum.change),
    },
    'x',
    'group',
  ],
}

Array order is row order. A nullish datum field or nullish text result omits the row. Adding group to items renders it as a row instead of the automatic single-point title. In grouped focus, the shared-axis item supplies the heading label and text, the opposite-axis item formats values, and the group item formats series names. sort orders those generated series rows. Additional grouped structure belongs in content.

sort accepts color-domain, focus, or a typed point comparator.

Anchor and placement

anchor controls the scene coordinate followed by the box:

  • point follows the primary focused point.
  • pointer follows the current pointer and falls back to the point for keyboard focus.
  • group-center uses the center of the focused points' bounding box.
  • A resolver receives the focused points plus pointer, chart bounds, width, and height, and returns scene coordinates. A nullish or non-finite result falls back to the primary point.

placement accepts auto, one placement, or an ordered fallback list. The placements are top, top-right, right, bottom-right, bottom, bottom-left, left, and top-left. A single placement is fixed and shifted inside the surface. A list uses the first placement that fits; if none fits, it uses the least-overflowing candidate and shifts it inside. auto uses top, bottom, right, then left.

ts
const tooltip = {
  anchor: 'group-center',
  placement: ['top', 'right', 'left', 'bottom'],
  offset: 12,
}

Set portal: true when an ancestor clips overflow or creates an incompatible stacking context. The host opens the tooltip as a manual Popover in the browser top layer where supported, while retaining its chart DOM ancestry. If Popover is unavailable or fails, it moves the tooltip directly under the chart's ownerDocument body with fixed high-stack positioning. Both paths map the scene anchor to viewport coordinates, reposition during scroll, resize, and content resize, and collide against the viewport instead of the chart box.

Clicking, Enter, or Space pins the tooltip. The next activation unpins it. Escape unpins and clears focus. Set sticky: false to disable pinning. A display-only tooltip has role="status" and aria-live="polite".

content supports display-only rows. Every framework adapter can compose native content around those rows while preserving the definition's ordering, anchor, placement, portal, and pinning behavior. A pinned custom body has non-modal dialog semantics.

Callbacks

ts
interface ChartInteractionCallbacks {
  onFocusChange?: (point: ChartPoint | null) => void
  onFocusGroupChange?: (points: readonly ChartPoint[]) => void
  onSelect?: (point: ChartPoint | null) => void
}

Definitions infer callback datum and semantic x/y value types without adapter generics. Focus callbacks run only when the primary focus key changes, except that a scene rebuild with an existing focused key reports the point with its new coordinates and datum.

onSelect reports mouse clicks and keyboard activation. A click with no point reports null; Enter and Space do nothing until a point is focused.

Spatial indexes

The default nearest-point lookup scans all interaction points. Dense charts can inject an index:

ts
type ChartSpatialIndexFactory<
  TDatum = unknown,
  TXValue extends ChartValue = ChartValue,
  TYValue extends ChartValue = ChartValue,
> = (
  points: readonly ChartPoint<TDatum, TXValue, TYValue>[],
) => ChartSpatialIndex<TDatum, TXValue, TYValue>

The host rebuilds the index when the scene or definition changes. The index owns its search algorithm and must apply maxDistance. Use the granular spatial primitive appropriate to the data; the boundary is described in Scales and D3.

A custom focus strategy takes precedence over spatialIndex for pointer resolution.

Application-owned gestures

Brushes, zooming, dragging, scrolling, crosshair overlays, and selections can listen on a wrapper or use onRender to attach application behavior to the live SVG. Keep semantic state outside the scene, update a dynamic definition by replacing its identity, and clean up listeners before the next attachment or unmount. For a completely independent renderer or interaction layer, use the scene and extension contracts in Custom extensions.