Interaction should help the reader inspect, navigate, select, or edit semantic data. It should not turn a chart into a second application state system.
TanStack Charts owns nearest-point focus, grouped focus, keyboard point navigation, point selection callbacks, and native structured tooltips. The application owns interactions that change a domain, viewport, persistent selection, or product record.
| Reader task | Start with |
|---|---|
| Inspect one point or a same-x group | Native chart focus and tooltip |
| Follow focus with one rule or crosshair | Data-less crosshair mark |
| Paint existing geometry for the active datum/group | whenFocused around an ordinary mark |
| Synchronize focus or free coordinates between charts | Shared createChartCursor controller |
| Resize, recolor, or fade existing marks during focus | Inline mark states |
| Keep rich framework detail open, including another chart | Pinned composed tooltip body |
| Navigate a wide schedule without changing its semantic scale | Native horizontal scrolling |
| Crop and pan a continuous domain | Controlled zoom and viewport state |
| Edit an interval or record | Controlled direct manipulation plus form input |
Interactions and Selections defines the controlled gesture loop. Tooltips and Focus defines the native inspection path.
A focused dot can resize and restyle the existing pointer target:
dot(rows, {
x: 'Date',
y: 'Close',
r: 3,
fill: '#2563eb',
states: [
{
when: { focus: 'primary' },
style: { r: 7, stroke: 'Canvas', strokeWidth: 2 },
transition: { type: 'tween', duration: 140, easing: 'ease-out' },
},
],
})A focused band emphasizes the shared x value for every series. Its position before the lines places it underneath them:
marks: [
whenFocused(
bandX(dates, {
x: 'date',
fill: '#64748b',
fillOpacity: 0.14,
inset: 3,
}),
{ match: 'x' },
),
lineY(rows, { x: 'date', y: 'unemployed', color: 'industry' }),
]Open the grouped focus example to inspect its live chart and complete source.
A crosshair is one dynamic guide driven by the existing focus state. It is not one hidden rule per datum:
marks: [
crosshair({
x: {
band: {
inset: 0,
radius: 3,
fill: '#64748b',
fillOpacity: 0.16,
},
label: true,
},
y: false,
}),
barY(rows, { x: 'period', y: 'value', color: 'series', inset: 4 }),
crosshair({
x: false,
y: { strokeDasharray: '4 4', label: true },
}),
]It follows pointer and keyboard focus, stays out of hit testing, and renders through SVG, Canvas, motion, and native focus presentation. The first guide uses categorical bandwidth to paint below the bars; with bar inset 4 and band inset 0, it extends 4 pixels past each bar edge. Its x label shows the focused period. The second guide paints the dotted y rule above the bars and labels the focused stack endpoint. Set maxFocusDistance to Number.POSITIVE_INFINITY only when the guides should remain snapped across the complete plot.
Open the stacked cursor-band example to inspect the live chart and complete source.
For synchronized charts or a free two-dimensional cursor, create one controller from @tanstack/charts/cursor and bind it through definition cursor. Focus mode shares semantic x/y values and local charts map them to their own pixels. Free mode shares controlled coordinates without selecting a datum. The complete state and inversion examples are in Interactions and Selections.
This energy tooltip stays compact on hover or keyboard focus. Click, Enter, or Space pins the same surface, adds solar coverage to its native rows, and smoothly reveals the detailed consumption and generation breakdown.
The definition's content callback receives pinned, so it can keep the transient summary short and add structured rows only after activation. The React renderTooltipBody callback receives that updated defaultBody and the same pinned state:
<TooltipChart
definition={definition}
renderTooltipBody={({ points, defaultBody, pinned, dismiss }) => (
<EnergyTooltip
month={points[0].datum}
summary={defaultBody}
expanded={pinned}
onClose={dismiss}
/>
)}
/>The detail wrapper stays mounted and transitions from grid-template-rows: 0fr to 1fr; its direct child uses min-height: 0 and overflow: hidden. This animates intrinsic height without measuring content. The transient body remains inert, controls render only while pinned, and the nested consumption chart has its own accessible label and lifecycle.
Add the portal extension to escape clipped ancestors and use viewport collision handling. Wire the close button to dismiss; the shared host also owns Escape, focus return, and non-modal dialog semantics.
Native horizontal scrolling is often better than zoom for resource lanes. It preserves a stable time scale and gives the browser proven wheel, touch, and keyboard behavior.
Keep lane labels in a fixed rail and place the timeline in the scroll region. Preserve lane order, task keys, scroll position, and viewport-relative geometry across data updates. Do not capture vertical page scrolling when the timeline only needs horizontal movement.
Use Layout, Axes, and Coordinates to align labels and the plotted region.
Zooming changes an explicit semantic domain. Wheel, drag, touch, keyboard, and reset controls should all update the same start and end values.
Define:
Store the resulting domain in application state and pass it into the chart's configured scale. Preserve that domain when data values update unless product policy explicitly follows the latest point.
An editable timeline combines direct manipulation with native semantic controls. The chart renders the current record; application validation decides which edit can commit.
A complete editor should:
Do not mutate a rectangle and treat that painted geometry as the saved record. Update application state, validate it, and let the next definition produce the scene.
Application-owned interaction state should be semantic:
Pixel geometry is derived from scene.chart and copied configured scales on each render. This keeps state valid after responsive layout, font changes, and server hydration.
Controllers and overlays may install pointer capture, event listeners, observers, nested hosts, and animation frames. Tear down every resource when the chart unmounts or ownership changes.
Use Testing and Debugging for behavior scenarios and Accessibility for equivalent input paths.