Skip to main content

Price lines

Price lines are horizontal status lines you place at specific prices — resting limit orders, take-profits, stop-losses, liquidation levels, alerts. Each one draws a styled rule across the price pane, a label group at its right end, and a price badge in the y-axis strip. They can be dragged to a new price and closed with a button, which is what makes them usable as live order controls rather than static annotations.

They work on both web and React Native (the renderer lives in the C++ core).

A minimal line

import { VroomChart, type PriceLine } from "@vroomchart/react";

const lines: PriceLine[] = [
{ id: "tp", price: 68420.5, text: "Take Profit", color: "#26a69a" },
];

<VroomChart candles={candles} priceLines={lines} />;

That renders a dotted teal line with a Take Profit pill and a 68420.50 badge on the axis. Everything beyond id and price is optional.

Anatomy

extendLeft label group axis
|<-------------- line ----------->| | |
..........[ text ][ quantity ][ × ]----[ badge ]
  • text — the body pill: a translucent label with your caption.
  • quantity — a second, solid-filled pill for size or scope ('0.75', 'Full position'). Omit it and the segment disappears.
  • The close button — a red-on-translucent ×. See Closing a line.
  • The axis badge — the price, formatted, painted over any axis label it overlaps. Turn it off per line with axisLabel: false.

A line whose price scrolls outside the visible price range is hidden, not clamped to the pane edge, so an off-screen order can't be mistaken for a nearby one.

Dragging

Dragging is opt in per line — set draggable: true. On the web the cursor becomes ns-resize over a draggable line; on touch, press and drag it directly.

priceLines is a controlled prop, and that shapes how dragging works: the chart paints a live preview at the pointer and reports the price, but never mutates the line. It moves for real only when you pass a new array. Which means a move your backend rejects needs no rollback — leave your state alone and the line snaps back on its own.

function Chart({ candles }) {
const [lines, setLines] = useState<PriceLine[]>([
{ id: "buy-1", price: 67000, text: "Limit Buy", draggable: true },
]);

return (
<VroomChart
candles={candles}
priceLines={lines}
// Fires continuously during the drag — good for a live order ticket.
onPriceLineDrag={(id, price) => setTicketPrice(price)}
// Fires once on release. Submit here, then commit on success.
onPriceLineDragEnd={async (id, price) => {
const ok = await api.repriceOrder(id, price);
if (ok) {
setLines((prev) =>
prev.map((l) => (l.id === id ? { ...l, price } : l)),
);
}
// On failure: do nothing. The line reverts to its committed price.
}}
/>
);
}

While a drag is in progress a faint ghost marks the starting price, so the user can see how far they've moved. On the web, Escape cancels the drag: onPriceLineDragEnd never fires and the line returns to where it started.

Prices are not snapped to anything — you get the exact price under the pointer. Round it yourself if your venue has a tick size.

Closing a line

The close button is callback-gated: it renders only if you pass onPriceLineClose. If there's nothing for the button to do, there's no button.

<VroomChart
candles={candles}
priceLines={lines}
onPriceLineClose={async (id) => {
await api.cancelOrder(id);
setLines((prev) => prev.filter((l) => l.id !== id));
}}
/>

To keep the handler but exclude one line — a liquidation level the user shouldn't be able to dismiss — set closable: false on it.

Like dragging, closing is a request, not an action: the line disappears when you remove it from the array, so a failed cancellation leaves it on the chart.

Styling

Per-line styling covers color (the line, its border, the body text and the close icon), width, and lineStyle ('solid', 'dotted' — the default — or 'dashed'). Layout is shared across all lines via priceLinesStyle:

<VroomChart
candles={candles}
priceLines={lines}
priceLinesStyle={{
// Where the label group sits. 'right' (default) parks it near the axis.
align: "right",
// Push it further left, as a fraction of pane width. 0.25 = a quarter in.
inset: 0.25,
// The translucent fill behind the body and close-button pills.
bodyBackground: "rgba(28, 33, 40, 0.85)",
fontSize: 11,
}}
/>

extendLeft (default true) controls whether the line continues past the label to the left edge of the pane. Set it to false for a short line that only spans from the label to the axis.

Platform differences

The only difference is hover: on the web, moving the pointer over a line or its close button brightens that segment (tune it with priceLinesStyle.hoverBoost, or set it to 1 to disable). Touch has no hover state, so on React Native everything renders at full strength all the time.

Escape-to-cancel is likewise web-only. On touch, drag the line back or reject the move in onPriceLineDragEnd.

Overlapping lines

Two lines at the same price stack on top of each other; vroom doesn't offset them automatically. When several are within grab range of a press, the nearest one in y wins, and a close button always wins over a line underneath it — so a mis-grab can't cancel an order you weren't aiming at.