Skip to main content
Scrubbing is on by default. Drag across the chart to reveal a vertical crosshair, the content ahead of it faded back, and a tooltip pill with the value and time under your finger.
Live example: app/demo/scrubbing.tsx in the example app.

Scrub a crosshair across history

Disable it with scrub={false}, or configure the crosshair and tooltip:

Keeping scrubs inside the plot

Set clampToPlot to keep the crosshair out of the Y-axis gutter and other horizontal padding:
A plain-scrub gesture that starts left or right of the plot is rejected before activation, remains available to competing or parent gestures, and does not fire onGestureStart. A gesture that starts within its horizontal bounds stays active when the pointer moves beyond either edge; its crosshair remains clamped to the nearest plot edge. Scrub-action gestures retain their existing behavior. The option works on LiveChart and LiveChartSeries and defaults to false for backwards compatibility.

Customizing the tooltip

The default pill shows the value on the first row and the time on the second, offset to the side of the crosshair. Reshape it with ScrubConfig knobs — no custom rendering required:
  • tooltipPlacement"side" (default) offsets the pill to the right of the scrub line and flips it left near the right edge. "top" / "bottom" center it horizontally over the line, clamped into the plot and pinned to the plot’s top or bottom. "point" centers it over the line and floats it just above the scrub dot, flipping below when there isn’t room above (single-series line mode; candle mode keeps its top-pinned OHLC stack).
  • tooltipMargin — the gap (px) between the pill and the plot edge it’s pinned to (the top for "side" / "top", the bottom for "bottom") — or, for "point", the gap between the scrub dot and the pill. Default 8. Applies to a custom renderTooltip too.
  • tooltipShowValue / tooltipShowTime — both default true. Drop a row to show just the date (tooltipShowValue: false) or just the value. Turning both off keeps the time row.
  • tooltipBorderRadius, tooltipBackground, tooltipColor, tooltipBorderColor — the pill’s shape and colors.

Custom tooltip (renderTooltip)

For full control — your own layout, blur, or any non-Skia view — pass renderTooltip. It’s the same idea as renderMarker: return a React Native element and the chart floats it over the canvas, positioning it on the UI thread (honoring tooltipPlacement). When set, it replaces the built-in pill entirely. The catch the JS-thread onScrub can’t solve: the value/date change every frame while scrubbing, so rebuilding the tooltip in React lags. renderTooltip receives the live fields as SharedValues — bind them to an animated TextInput (useAnimatedProps) and both the position and the text update on the UI thread, with no re-renders.
Supplying renderTooltip replaces the built-in pill entirely while scrubbing. Returning null/undefined from a frame renders nothing for that frame (handy to hide the tooltip in certain states) — it does not restore the built-in pill. The tooltip* style props above are ignored while a custom tooltip is active, since you own the chrome.

Custom tooltip in candle mode

renderTooltip works in candle mode too — it replaces the built-in OHLC stack. The context’s candle field is a SharedValue<CandlePoint | null> holding the scrubbed bucket, so you can render your own OHLC readout. Bind it the same way — read it inside useAnimatedProps so the values update on the UI thread:
candle is always present (a SharedValue); it’s null in line mode or when scrubbing is inactive. value / valueStr are the candle’s close, and time / timeStr the bucket time — so a tooltip that only shows the close + date needs no candle-specific code at all. This is handy when the active candle is shown elsewhere in your UI and you want a minimal (or empty) in-chart tooltip.

Trailing fade (dimOpacity)

While scrubbing, the chart content to the right of the crosshair (the “future”) is faded so the part you’re inspecting stands out. This is done by erasing the trailing content’s alpha — it reveals the real background, so it works on any background color (no need to match a mask color). dimOpacity sets the opacity of that trailing region: 1 disables the fade, 0 fully hides it. Default 0.3.
crosshairDimColor is the legacy alternative: a solid (usually semi-transparent) color painted over the trailing region. Because it’s a mask on top, it only looks right when it matches the background — prefer dimOpacity. When crosshairDimColor is set, it overrides the fade.
The same dimOpacity option works on LiveChartSeries.

Hide overlays while scrubbing (hideOverlaysOnScrub)

dimOpacity fades the line/candles, but annotation overlays — buy/sell markers and reference lines — stay at full strength and can clutter the crosshair read-out. Set hideOverlaysOnScrub to fade them out for the duration of the scrub and back in on release.
It covers both the built-in Skia tags/lines and any custom renderMarker / renderReferenceLine views. The fade is driven by the scrub-active state (not the crosshair’s edge-proximity fade, so the overlays don’t resurface as the crosshair nears the live dot) and eased on the UI thread.
Cheap by construction: only a group opacity animates — the marker atlas and reference-line geometry are untouched (still one batched draw each). It does not rebuild or empty the overlay data per scrub, so there’s no per-scrub layout/recompute cost.
The leading live dot is governed separately — see Selection dot. Works the same on LiveChartSeries.

Dashed crosshair (crosshairDash)

The vertical crosshair line is solid by default. Set crosshairDash to draw it dashed — true applies a default [4, 4] dash, or pass explicit Skia dash intervals [on, off, …] in pixels for finer control. Omitting it (or false) keeps the solid line.
Pairs with crosshairLineColor for the stroke color, and works on LiveChartSeries too.

Crosshair width, line cap, overshoot, and edge fade

Use crosshairStrokeWidth to change the line width and crosshairOvershoot to extend it past the plot’s top and bottom edges. Both values are pixels; their defaults are 1 and 0. Negative overshoot values are clamped to 0. A custom top tooltip’s measured line stop is preserved so overshoot never draws the crosshair through the tooltip. Set crosshairLineCap to "butt", "round", or "square" to control the ends of the vertical line. Omitting it preserves the existing Skia default. Caps compose with solid, thick, and dashed crosshairs. The crosshair fades over the final 4 px as it approaches the live edge by default. crosshairFadeDistance changes that distance for the visible line, selection dot, and tooltip; negative values clamp to 0. Set crosshairFade: false to keep that visible group fully opaque while scrubbing. Neither option changes the trailing-content dim’s existing edge fade.
These options also work on LiveChartSeries.

Press-and-hold to scrub (panGestureDelay)

By default scrubbing starts the moment the user drags. When the chart sits inside a horizontally swipeable container — most commonly a stack navigator with swipe-back-to-previous-route — that can steal the back gesture. panGestureDelay makes scrubbing a press and hold: the pan only activates after the finger is held for that many milliseconds, so a quick horizontal flick falls through to the parent gesture, while a deliberate hold begins scrubbing.
0 (the default) scrubs immediately. Works the same on LiveChartSeries.

Selection dot

A dot is drawn at the scrub intersection — where the crosshair meets the line. On LiveChart it’s on by default, so you get it for free with scrub. On LiveChartSeries it’s off by default — with multiple lines the dot can only track the leading series, so the crosshair + the opt-in per-series tooltip dots mark the scrub point instead; pass selectionDot explicitly to opt in. Turn it off, or tweak its look, with the boolean | SelectionDotConfig selectionDot prop:
For full control, pass a component — a Skia dot that receives the scrub position as SharedValues (SelectionDotProps), so it animates on the UI thread without re-renders. When set, the size / color / ring knobs are ignored.
The same selectionDot prop works on LiveChartSeries — there it defaults to hidden, so pass true (or a config) to show the dot anchored to the leading series.

Reading the scrub position (single series)

onScrub fires with a ScrubPoint while scrubbing, and null when it ends. It runs on the JS thread, so you can call React setState directly:

Gesture lifecycle (onGestureStart / onGestureEnd)

onGestureStart and onGestureEnd fire once at the edges of a scrub — on both LiveChart and LiveChartSeries. Unlike onScrub, they’re plain JS-thread functions (not worklets), so a regular useState is fine:
onGestureStart fires when the pan activates — so it respects scrub.panGestureDelay and only fires once the press-and-hold threshold is met. onGestureEnd fires only if a scrub actually started; a quick tap that never activates emits neither callback. They pair well with side effects you want to bracket around an interaction — fire a haptic on start, or pause a live feed while the user inspects history and resume it on end.

Multi-series scrub (worklet)

For LiveChartSeries, onScrub is a worklet that runs on the UI thread each frame with a ScrubPointMulti (including a per-series value array). Write straight to a shared value with no bridge overhead:
point.seriesValues is an array of { id, label, value } for each visible series at the scrub time. See the types reference for the full payload shape.

Order ticket (scrubAction)

scrubAction turns the ephemeral crosshair into a placed interaction — tap to drop a locked price reticle, drag to fine-tune, then press to fire an order callback. It has its own guide: Order ticket.