Tooltips work the same way on every TanStack chart, through the kit's Chart components. The options below go into the chart's TanStack definition, written the way TanStack documents it.
Install
npx antuikit@latest init
npx antuikit@latest add tanstack-chart
The first command installs AntuikitProvider and the theme tokens. The second adds the chart components and TanStack Charts itself.
Which component
All three take the same definition and the same palette props.
Chart components
Add a tooltip
Import tooltip and pass it in the second argument of defineChart. focus decides what the tooltip describes, and seriesTooltip from the kit fills it: the x value as the title, then one row per series with its color and value.
import { barY, defineChart, stack } from "@tanstack/charts"
import { scaleBand } from "@tanstack/charts/scales/band"
import { scaleLinear } from "@tanstack/charts/scales/linear"
import { tooltip } from "@tanstack/charts/tooltip"
import { Chart, seriesTooltip } from "@/components/ui/tanstack-chart"
<Chart definition={definition} ariaLabel="Calories burned by activity and day" height={200} />
Without content, the tooltip builds its own rows from the focused point's channels and the axis labels. The kit paints the tooltip card with the theme's elevated background, shadow, radius and font.
Focus modes
focus sets what one hover or one arrow key press lands on. The tooltip, the focus ring and the states on your marks all follow it.
Left out: the nearest point in both directions. Good for scatter plots.
nearest-x or nearest-y: one point, nearest along that axis. Good for a single line.
group-x or group-y: every series at the nearest x or y value. Good for grouped bars and several lines.
The pointer focuses a point only within maxFocusDistance, 48 pixels by default, so empty space clears the tooltip. On a continuous line, pass Number.POSITIVE_INFINITY to snap from anywhere. For a dense scatter, add a voronoi mark from @tanstack/charts/spatial/voronoi so every point owns the area nearest to it.
Title and rows
content receives the focused points and returns a title and rows. Each row has a label, a string value and an optional color for its dot. Read your own fields from point.datum.
tooltip: {
use: tooltip,
sort: "color-domain",
content: (points) => ({
title: formatDate(points[0]?.datum.date),
rows: [
...points.map((point) => ({
label: point.datum.activity,
value: `${point.datum.value} kcal`,
color: point.color,
})),
{
label: "Total",
value: `${points.reduce((total, point) => total + point.datum.value, 0)} kcal`,
},
],
}),
}
Leave out title to show only the rows.
Leave out a row's color to drop its dot.
sort: "color-domain" lists the rows in the color scale's order, the same as the legend. By default, the rows of an x group follow the marks from top to bottom.
The row of the focused point is bold on a shaded background. Set active: false on a row to turn that off.
One point, chosen rows
For a single focused point, items lists the rows in order. An item names a channel or a datum field, or derives its own text. A text that returns null drops the row.
anchor sets the point the tooltip attaches to, and placement the side it opens on. A list of placements is tried in order, and the first one that fits wins.
point, the default: the focused point. Steady, and the same for pointer and keyboard.
pointer: the pointer itself, for large marks. Keyboard focus falls back to the point.
group-center: the center of a focused group, so the tooltip does not jump between series.
{ x: "value", y: "plot-top" }: follow the x value and stay at the top of the plot.
Your own tooltip body
renderTooltipBody on the chart replaces the tooltip's markup while TanStack keeps focus, order, placement and dismissal. The kit draws its card around what you return, so you write only the rows.
Return defaultBody to fall back to the built-in rows, or wrap it to add something beside them.
Pinned tooltips
A click, Enter or Space pins the tooltip, and a second click or Escape unpins it. While pinned, the reader can select its text and use controls inside it. pinned reaches content, items and renderTooltipBody, so the pinned tooltip can show more.
Render buttons and links only when pinned is true. Before that, the body cannot take focus or clicks.
dismiss() closes the tooltip and returns focus to the chart.
sticky: false turns pinning off. visibility: "pinned" shows the tooltip only once it is pinned.
Inside clipping containers
A card with overflow: hidden or a scrolling panel cuts a tooltip off at its edge. Add portal and the tooltip opens in the browser's top layer. It stays inside the chart in the DOM, so the kit's tooltip styles still apply. A browser without the Popover API gets the tooltip moved under body instead, where those styles do not reach it.
import { portal } from "@tanstack/charts/tooltip/portal"
tooltip: {
use: tooltip,
portal,
anchor: "group-center",
placement: ["top", "right", "left", "bottom"],
}
Open it from code
onRender gives you the chart's interaction controller. Pass a point to setControlledFocus to show its tooltip without a hover, and null to clear it.
const opened = useRef(false)
<Chart
definition={definition}
ariaLabel="Calories burned by activity and day"
onRender={({ scene, interaction }) => {
if (opened.current) return
const point = scene.points.find((item) => item.datum.date === "2024-07-16")
On a RendererChart with motion(), the tooltip moves with the renderer's transition. When the data animates slowly, keep the tooltip quick with motion: false or a short tween in the tooltip options, such as { type: "tween", duration: 120 }.
API
The focus and tooltip options, and what a custom body receives. TanStack Charts' documentation has the full reference.