
Skill
build-chart-interactions
build chart interactions with TanStack Charts
Description
Compose TanStack Charts focus, tooltips, controlled selections, cursors, brushes, zoom, keyboard behavior, and coordinated views. Load for hover, focus, pinning, crosshairs, clipping, gesture state, or chart-table coordination.
SKILL.md
Build Chart Interactions
Use trigger → inspect → decide → build → verify. Charts owns renderer-neutral interaction mechanics; the application owns accepted semantic state, persistence, product policy, and equivalent non-pointer controls.
Setup
Start with native focus and tooltip behavior before adding controlled state:
import { defineChart, lineY } from '@tanstack/charts'
import { crosshair } from '@tanstack/charts/crosshair'
import { scaleLinear } from '@tanstack/charts/scales/linear'
import { scalePoint } from '@tanstack/charts/scales/point'
import { tooltip } from '@tanstack/charts/tooltip'
const rows = [
{ week: 'May 4', value: 820 },
{ week: 'May 11', value: 960 },
{ week: 'May 18', value: 1_140 },
]
export const chart = defineChart({
marks: [
lineY(rows, { x: 'week', y: 'value', points: true }),
crosshair({ x: { label: true }, y: false }),
],
x: { scale: scalePoint },
y: { scale: scaleLinear },
focus: 'nearest-x',
maxFocusDistance: Number.POSITIVE_INFINITY,
tooltip,
})
Keep the finite default focus distance when empty space should clear inspection.
Core Patterns
Choose interaction ownership
| Need | Owner |
|---|---|
| Nearest datum, grouped tooltip, snapped crosshair, keyboard point navigation | chart focus |
| Semantic selection, free cursor, handle, brush, zoom, interactive legend | first-party control plus controlled signal |
| Shared accepted range, persistence, playback, editing, rich pinned details | application state/UI |
Read the interaction state matrix before combining controls.
Coordinate chart and application selection by key
import { defineChart, dot } from '@tanstack/charts'
import { controlledSignal } from '@tanstack/charts/interaction/signal'
import { scaleLinear } from '@tanstack/charts/scales/linear'
import { keyedSelection, whenSelected } from '@tanstack/charts/selection'
const observations = [
{ id: 'a', speed: 12, efficiency: 32 },
{ id: 'b', speed: 18, efficiency: 27 },
]
let selectedId: string | null = null
const selection = keyedSelection<
(typeof observations)[number],
string,
number,
number
>({
selected: controlledSignal(selectedId, (next) => {
selectedId = next
}),
key: (datum) => datum.id,
})
export const chart = defineChart({
marks: [
dot(observations, {
id: 'observations',
x: 'speed',
y: 'efficiency',
key: 'id',
}),
whenSelected(
dot(observations, {
id: 'selected-observation',
x: 'speed',
y: 'efficiency',
key: 'id',
r: 7,
}),
selection,
),
],
x: { scale: scaleLinear },
y: { scale: scaleLinear },
selection,
})
Rebuild the definition with the accepted controlled value. A signal is a snapshot and callback, not a hidden store.
Synchronize semantic values, not pixels
Use one createChartCursor controller across definitions when charts should resolve the same x/y value through their own scales. Keep crosshair presentation in each definition. Never copy DOM coordinates or mutate another chart's SVG.
Add portaling only for containment boundaries
Use native tooltip content first. Add portal when overflow, transforms, or stacking contexts clip the surface. Use framework adapter tooltip bodies only when the product requires rich interactive content.
Common Mistakes
CRITICAL Mutating SVG for focus presentation
Wrong:
onRender={({ svg }) => svg.insertBefore(activeBand, svg.firstChild)}
Correct:
marks: [
whenFocused(bandX(rows, { x: 'date' }), { match: 'x' }),
lineY(rows, channels),
]
DOM mutation bypasses scene identity, SSR, Canvas, React Native, motion, and cleanup. Current focus marks replace an older workaround that still appears in issue examples.
Source: GitHub issue 9; API-FRICTION.md F-178
HIGH Focusing a point-less rule
Wrong:
whenFocused(ruleX(dates), { match: 'x' })
Correct:
focusGuideX(rows, { x: 'date', y: 'value', xRule: {} })
Rules emit no interaction points, so a focus filter has no candidate identity. Current focus-guide primitives replace this legacy pattern.
Source: GitHub issue 32; API-FRICTION.md F-237
CRITICAL Treating callbacks as complete behavior
Wrong: attach only an onRangeChange or key callback to an overlay.
Correct: use the matching controlled control (keyedSelection, continuousCursor, handleX, brushX, or zoomX) and store its accepted semantic value in application state.
A callback alone does not define capture, clamping, cancellation, keyboard operations, or ownership.
Source: API-FRICTION.md F-075; docs/guides/interactions-and-selections.md
HIGH Keeping tooltips inside clipped ancestors
Wrong:
tooltip: {
use: tooltip
}
Correct:
tooltip: {
use: (tooltip, portal)
}
Overflow, transforms, and stacking contexts can trap a correctly positioned tooltip.
Source: API-FRICTION.md F-133; docs/guides/tooltips-and-focus.md
HIGH Letting decorative layers own duplicate points
Wrong: make every area, line, dot, label, and highlight layer over one observation independently focusable.
Correct: choose one semantic interaction owner and use decorative, whenFocused, or whenSelected for supporting presentation.
Duplicate points create repeated keyboard stops, focus candidates, activations, and tooltip rows.
Source: API-FRICTION.md F-218; docs/guides/tooltips-and-focus.md
HIGH Tension: rich interaction versus portable rendering
Prefer marks, controls, semantic state, and host extensions over DOM-only overlays. Verify pointer and keyboard paths, static fallback, teardown, and any native equivalent.
See also: ship-accessible-charts/SKILL.md and extend-tanstack-charts/SKILL.md
References
See also: coordinate-charts-with-tanstack/SKILL.md, ship-accessible-charts/SKILL.md, and update-and-animate-charts/SKILL.md — interaction requires explicit cross-surface ownership, keyboard parity, and stable identity across updates.
More skills from the charts repository
View all 12 skillscompose-marks-and-views
compose marks and views in TanStack Charts
Aug 15ChartsFrontendTanStackUI Componentsconfigure-scales-guides-color
configure scales and guides for TanStack Charts
Aug 15ChartsData VisualizationFrontendTanStackcoordinate-charts-with-tanstack
coordinate TanStack Charts with data sources
Aug 15ChartsData AnalysisData EngineeringFrontend +1debug-and-verify-charts
debug and verify TanStack Charts
Aug 15ChartsDebuggingFrontendTanStackdesign-a-chart
design charts with TanStack Charts
Aug 15ChartsData VisualizationFrontendTanStackdesign-responsive-charts
design responsive TanStack Charts
Aug 15ChartsData VisualizationFrontendSSR +1
More from TanStack
View publisheraggregation
perform data aggregation in TanStack Table
table
Aug 11Data AnalysisFrontendTanStackapi-not-found
diagnose TanStack Table API errors
table
Aug 11DebuggingFrontendTanStackcell-selection
select rectangular cell ranges in tables
table
Aug 11Data AnalysisTanStackUI Componentscell-spanning
configure cell spanning in TanStack Table
table
Aug 11Data VisualizationFrontendTanStackUI Componentsclient-vs-server
manage TanStack Table data pipelines
table
Aug 11Data PipelineFrontendPerformanceTanStackcolumn-faceting
build faceted filter UIs
table
Aug 11Data VisualizationFrontendTanStack