A composable React Native tooltip for chart data points with customizable indicators, labels, and value formatters.
Cartesian charts only.
ChartTooltipis driven by victory-native's Cartesian press state (useChartPressState→state.x.position,state.y[yKey].position,state.matchedIndex,state.isActive) and clamps againstchartBoundsfromonChartBoundsChange. It works withLineChart,BarChart,AreaChart, andComposedChart. It does not support polar charts (PieChart/PolarChart), which expose no press positions,matchedIndex, orchartBounds— there is no anchor coordinate for the card to follow. For pie/donut selection labels, render your own label inside thePieChart.Pierender callback instead.
import { ChartTooltip, useChartTooltipAnchor } from 'heroui-native-pro';<ChartTooltip.Anchor
chartBounds={...}
isActive={state.isActive}
matchedIndex={state.matchedIndex}
x={state.x.position}
y={state.y[yKey].position}
>
<SomeHeroUINative.Chart>...</SomeHeroUINative.Chart>
<ChartTooltip>
<ChartTooltip.Header />
<ChartTooltip.Item>
<ChartTooltip.Indicator color="..." />
<ChartTooltip.Label />
<ChartTooltip.Value />
</ChartTooltip.Item>
</ChartTooltip>
</ChartTooltip.Anchor>(x, y) anchor, clamps inside chartBounds on both axes, and fades with press activity when isVisible="auto". Must be a descendant of ChartTooltip.Anchor.View that wraps the chart and sibling tooltip. Supplies press coordinates, activeIndex, and plot bounds via context.flex-row with indicator, label, and value).When wrapping a chart with
ChartTooltip.Anchor, the chart'swrapperClassNamemust not contain padding (e.g.p-*,px-*,py-*). The anchor readschartBoundsin the same coordinate space as the Skia canvas, so any padding on the wrapper offsets the chart relative to the anchor and breaks positioning / clamping ofChartTooltip. Apply spacing on a parent container instead.
Pass state.isActive, state.matchedIndex, and press positions to ChartTooltip.Anchor. Read the bridged activeIndex from useChartTooltipAnchor() inside a descendant to map tooltip rows from your data array. Visibility fades automatically with press activity (isVisible="auto" is the default).
const { state, isActive } = useChartPressState({
x: '' as string,
y: { revenue: 0, orders: 0 },
});
const [chartBounds, setChartBounds] = useState<ChartBounds | null>(null);
function OrdersTooltip() {
const { activeIndex } = useChartTooltipAnchor();
const activeRow = activeIndex != null ? DATA[activeIndex] : null;
return (
<ChartTooltip>
<ChartTooltip.Header>{activeRow?.month ?? ''}</ChartTooltip.Header>
<ChartTooltip.Item>
<ChartTooltip.Indicator color="#6366f1" />
<ChartTooltip.Label>Revenue</ChartTooltip.Label>
<ChartTooltip.Value>
${activeRow?.revenue.toLocaleString() ?? ''}
</ChartTooltip.Value>
</ChartTooltip.Item>
<ChartTooltip.Item>
<ChartTooltip.Indicator color="#f97316" variant="line" />
<ChartTooltip.Label>Orders</ChartTooltip.Label>
<ChartTooltip.Value>
{activeRow?.orders.toLocaleString() ?? ''}
</ChartTooltip.Value>
</ChartTooltip.Item>
</ChartTooltip>
);
}
<ChartTooltip.Anchor
chartBounds={chartBounds}
isActive={state.isActive}
matchedIndex={state.matchedIndex}
x={state.x.position}
y={state.y.orders.position}
>
<ComposedChart
data={DATA}
xKey="month"
yKeys={['revenue', 'orders']}
chartPressState={state}
onChartBoundsChange={setChartBounds}
wrapperClassName="h-[220px]"
>
{({ points, chartBounds: bounds }) => (
<>
<ComposedChart.Bar points={points.revenue} chartBounds={bounds} />
<ComposedChart.Line points={points.orders} />
{isActive ? (
<>
<ChartCrosshair
x={state.x.position}
top={bounds.top}
bottom={bounds.bottom}
/>
<ChartIndicator x={state.x.position} y={state.y.orders.position} />
</>
) : null}
</>
)}
</ComposedChart>
<OrdersTooltip />
</ChartTooltip.Anchor>;Control tooltip visibility with isVisible on ChartTooltip:
'auto' (default) — fades with press activity from the anchor.true — always visible.false — not rendered.<ChartTooltip isVisible="auto">...</ChartTooltip>
<ChartTooltip isVisible={false}>...</ChartTooltip>Set the swatch shape on each ChartTooltip.Indicator with variant. 'dot' renders a circular marker; 'line' renders a narrow vertical pill suited to line-series tooltips.
<ChartTooltip.Item>
<ChartTooltip.Indicator color="#6366f1" variant="dot" />
<ChartTooltip.Label>Revenue</ChartTooltip.Label>
<ChartTooltip.Value>$4,200</ChartTooltip.Value>
</ChartTooltip.Item>Position the tooltip above (top) or below (bottom) the (x, y) anchor with the placement prop on ChartTooltip.
<ChartTooltip placement="top">...</ChartTooltip>
<ChartTooltip placement="bottom">...</ChartTooltip>Nudge the overlay without fighting the animated style. offset accepts CSS-like additive top / bottom / left / right pixels. When chartBounds is supplied on ChartTooltip.Anchor, ChartTooltip clamps its position so the card stays inside the plot box on both axes.
The animated style owns
transformandopacity, so do not override those viaclassName. Useoffsetinstead.
<ChartTooltip placement="top" gap={12} offset={{ top: 8, left: 4 }}>
...
</ChartTooltip>The card springs to follow the press indicator by default (withSpring, no config). Control the motion type and config with the animation prop on ChartTooltip. Pass false / "disabled" to snap instantly.
<ChartTooltip animation={{ type: 'spring', damping: 18, stiffness: 160 }}>...</ChartTooltip>
<ChartTooltip animation={{ type: 'timing', duration: 200 }}>...</ChartTooltip>
<ChartTooltip animation={false}>...</ChartTooltip>Disable all tooltip animations (including descendants) by cascading from the anchor:
<ChartTooltip.Anchor animation="disable-all" ...>
...
</ChartTooltip.Anchor>Sensible accessibility defaults are applied; every default is overridable via the matching prop.
accessibilityRole="summary", accessibilityLiveRegion="polite") so screen readers announce the active values as the press selection changes.accessible, accessibilityRole="text") so its label and value are read together as one node (e.g. "Revenue, $4,200").accessible={false}, aria-hidden, accessibilityElementsHidden, importantForAccessibility="no-hide-descendants").accessibilityRole="header"; ChartTooltip.Label / ChartTooltip.Value use accessibilityRole="text".<ChartTooltip accessibilityLabel="Revenue and orders for the selected month">
<ChartTooltip.Header>Mar</ChartTooltip.Header>
<ChartTooltip.Item accessibilityLabel="Revenue 4,200 dollars">
<ChartTooltip.Indicator color="#6366f1" />
<ChartTooltip.Label>Revenue</ChartTooltip.Label>
<ChartTooltip.Value>$4,200</ChartTooltip.Value>
</ChartTooltip.Item>
</ChartTooltip>import { Card } from 'heroui-native';
import {
ChartCrosshair,
ChartIndicator,
ChartTooltip,
ComposedChart,
useChartTooltipAnchor,
} from 'heroui-native-pro';
import { useState } from 'react';
import { View } from 'react-native';
import type { ChartBounds } from 'victory-native';
import { useChartPressState } from 'victory-native';
const DATA = [
{ month: 'Jan', revenue: 4200, orders: 320 },
{ month: 'Feb', revenue: 5800, orders: 450 },
];
function TooltipBody() {
const { activeIndex } = useChartTooltipAnchor();
const activeRow = activeIndex != null ? DATA[activeIndex] : null;
return (
<ChartTooltip>
<ChartTooltip.Header>{activeRow?.month ?? ''}</ChartTooltip.Header>
<ChartTooltip.Item>
<ChartTooltip.Indicator color="#6366f1" />
<ChartTooltip.Label>Revenue</ChartTooltip.Label>
<ChartTooltip.Value>
${activeRow?.revenue.toLocaleString() ?? ''}
</ChartTooltip.Value>
</ChartTooltip.Item>
<ChartTooltip.Item>
<ChartTooltip.Indicator color="#f97316" variant="line" />
<ChartTooltip.Label>Orders</ChartTooltip.Label>
<ChartTooltip.Value>
{activeRow?.orders.toLocaleString() ?? ''}
</ChartTooltip.Value>
</ChartTooltip.Item>
</ChartTooltip>
);
}
export default function TooltipChartExample() {
const [chartBounds, setChartBounds] = useState<ChartBounds | null>(null);
const { state, isActive } = useChartPressState({
x: '' as string,
y: { revenue: 0, orders: 0 },
});
return (
<View className="flex-1 px-5 justify-center">
<Card className="w-full">
<Card.Body>
<ChartTooltip.Anchor
chartBounds={chartBounds}
isActive={state.isActive}
matchedIndex={state.matchedIndex}
x={state.x.position}
y={state.y.orders.position}
>
<ComposedChart
data={DATA}
xKey="month"
yKeys={['revenue', 'orders']}
chartPressState={state}
wrapperClassName="h-[200px]"
onChartBoundsChange={setChartBounds}
>
{({ points, chartBounds: bounds }) => (
<>
<ComposedChart.Bar
points={points.revenue}
chartBounds={bounds}
/>
<ComposedChart.Line points={points.orders} />
{isActive ? (
<>
<ChartCrosshair
x={state.x.position}
top={bounds.top}
bottom={bounds.bottom}
/>
<ChartIndicator
x={state.x.position}
y={state.y.orders.position}
/>
</>
) : null}
</>
)}
</ComposedChart>
<TooltipBody />
</ChartTooltip.Anchor>
</Card.Body>
</Card>
</View>
);
}| prop | type | default | description |
|---|---|---|---|
children | ReactNode | - | Card content: Header and Item rows |
animation | ChartTooltipRootAnimation | spring | Motion animation while tracking the press indicator. withSpring (no config) by default |
isVisible | ChartTooltipVisibility | 'auto' | 'auto' fades with press activity; true always visible; false unmounts |
placement | ChartTooltipPlacement | 'top' | Whether the tooltip sits above or below the (x, y) anchor |
gap | number | 12 | Gap in logical pixels between the anchor and the tooltip edge |
offset | ChartTooltipOffset | - | Pixel offsets applied on top of auto-positioning. CSS-like additive top/bottom/left/right |
className | string | - | Additional classes merged onto the animated card container |
...ViewProps | Omit<ViewProps, 'children'> | - | All standard React Native View props are supported except children (typed above) |
| type | description |
|---|---|
'auto' | boolean | 'auto' fades with anchor press activity (default) |
| type | description |
|---|---|
'top' | 'bottom' | Vertical placement of the overlay relative to anchor |
Discriminated on type. Both branches accept the matching Reanimated config fields.
| type | description |
|---|---|
{ type: 'spring' } & WithSpringConfig | Physically-based motion (default; no config required) |
{ type: 'timing' } & WithTimingConfig | Duration-based slide |
| prop | type | default | description |
|---|---|---|---|
top | number | 0 | Vertical inset that pushes the overlay down (positive) |
bottom | number | 0 | Vertical inset that pushes the overlay up (positive) |
left | number | 0 | Horizontal pixel offset added to translateX. Positive values push right |
right | number | 0 | Horizontal pixel offset subtracted from translateX. Positive values push left |
| type | description |
|---|---|
'dot' | 'line' | Swatch shape. 'dot' is circular; 'line' is a narrow pill |
| prop | type | default | description |
|---|---|---|---|
children | ReactNode | - | The chart and sibling ChartTooltip overlay |
animation | ChartTooltipAnchorRootAnimation | - | Root-level cascade. Pass "disable-all" to disable animations on ChartTooltip and descendants |
chartBounds | ChartBounds | null | - | Plot bounds from onChartBoundsChange. Enables 2D clamping on ChartTooltip |
isActive | SharedValue<boolean> | - | Press activity shared value (state.isActive) |
matchedIndex | SharedValue<number> | - | Matched datum index (state.matchedIndex); bridged to JS as activeIndex in context |
x | SharedValue<number> | - | Horizontal press position in chart space (state.x.position) |
y | SharedValue<number> | - | Vertical press position in chart space (state.y[yKey].position) |
...ViewProps | ViewProps | - | All standard React Native View props are supported |
| prop | type | default | description |
|---|---|---|---|
children | ReactNode | - | Title text (e.g. X-axis category) |
className | string | - | Additional classes merged onto the header |
...TextProps | TextProps | - | All standard React Native Text props |
| prop | type | default | description |
|---|---|---|---|
children | ReactNode | - | Indicator, label, and value for one series |
className | string | - | Additional classes merged onto the item row |
...ViewProps | ViewProps | - | All standard React Native View props |
| prop | type | default | description |
|---|---|---|---|
color | string | - | Fill color for the swatch |
variant | ChartTooltipIndicatorVariant | 'dot' | Swatch shape ('dot' or 'line') |
className | string | - | Additional classes merged onto the indicator |
...ViewProps | ViewProps | - | All standard React Native View props are supported |
| prop | type | default | description |
|---|---|---|---|
children | ReactNode | - | Series name |
className | string | - | Additional classes merged onto the label |
...TextProps | TextProps | - | All standard React Native Text props |
| prop | type | default | description |
|---|---|---|---|
children | ReactNode | - | Formatted data value |
className | string | - | Additional classes merged onto the value |
...TextProps | TextProps | - | All standard React Native Text props |
Returns the anchor context from the nearest ChartTooltip.Anchor. Throws when used outside an anchor (strict context).
| field | type | description |
|---|---|---|
activeIndex | number | null | Active datum index when press is active; null when inactive |
chartBounds | ChartBounds | null | Latest plot bounds from the chart |
isActive | SharedValue<boolean> | Press activity shared value |
x | SharedValue<number> | Horizontal press position |
y | SharedValue<number> | Vertical press position |