DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
ChartArea ChartBar ChartLine ChartLive Line ChartComposed ChartPie ChartFunnel ChartRadar ChartRadial ChartSankey ChartScatter ChartCandlestick ChartSunburst ChartChoropleth ChartActivity Heatmap
AI agentsContributing
  1. Components
  2. Candlestick Chart

Candlestick Chart

Token-driven candlestick (OHLC) chart built on Recharts v3 — one candle per period, hollow when it closed up and filled when it closed down, with optional volume bars, a range selector and a readout hook.

$ pnpm add @tessinaui/ui

When to use

  • To show how a price moved within each period. A candle carries four numbers a line cannot: where the period opened, its high, its low and where it closed.
  • To show the size of each day's move as well as the trend. Long bodies are decisive days, long wicks are days that tried and came back.
  • Beside volume. Set volume on the rows and the bottom of the plot shows how much traded each period (Volume).

When not to use

  • For the price trend alone. If only the close matters, use LineChart; it reads faster and fits a smaller space (In a card switches between the two).
  • For anything that is not a price. Open, high, low and close only mean something for a value that trades through a period.
  • For more than about a hundred candles on screen. Bodies shrink to their wicks. Show a shorter period, or add the range selector (Range selector).
  • For a trading terminal. Drawing tools, indicator panes, depth and order books are a different product; this chart draws prices.

Installation

Terminal
pnpm add @tessinaui/ui recharts

Usage

example.tsx
import { CandlestickChart } from "@tessinaui/ui";

// One row per period, oldest first.
const data = [
  { date: "2026-03-02", open: 184.26, high: 187.48, low: 184.24, close: 186.37 },
  { date: "2026-03-03", open: 186.6, high: 187.03, low: 185.74, close: 185.97 },
  { date: "2026-03-04", open: 186.72, high: 187.43, low: 184.1, close: 185.11 },
];

<CandlestickChart
  data={data}
  valueFormatter={(value) => usd.format(value)}
  label="Northwind shares slipped for three days"
/>

Each row is one period. date can be a date string, a Date or epoch milliseconds; the four prices are numbers. A row missing any price is a period with no trading. There is no config: up and down take the status inks.

Examples

Default

Hover a candle, or Tab to the chart and use the arrow keys: the candle is read in the tooltip and the others step back.

Volume

Add volume to the rows and the bottom fifth of the plot shows it, one bar per candle in its candle's direction. showVolume={false} hides it.

Range selector

showBrush draws a range selector under the chart. The price axis fits the candles in range, so a quarter fills the plot. defaultBrushRange sets the first range and onBrushChange reports a new one, both as row indices.

Readout

onActiveIndexChange reports the row under the pointer or the keyboard, so the day's prices can sit above the chart instead of in a tooltip. With showTooltip={false} the readout is the live region, so give it role="status".

Last price

A dashed ReferenceLine child at the last close.

Moving average

Compute the average into the rows and compose a Line child that reads it by key. The chart draws the line it is given.

In a card

The price, the change, the range and the chart type belong to the card. The chart draws the rows it is given, and switches to a line of the close.

Formatting

valueFormatter formats prices on the axis and in the tooltip, volumeFormatter the volume, and dateFormatter the tooltip heading and the ticks. Five-minute candles take a time; give the formatter a fixed time zone so the server and the browser agree.

Sizes

Three aspect ratios: sm (short and wide), md, and lg (tall). A short plot holds fewer price ticks.

Accessible name and data table

label states the insight, description carries the numbers, and a table holds the same rows for anyone who cannot read the candles.

Choosing a day

With selectionMode="single" a click or a tap over a day, or Enter or Space on the day the arrow keys are on, chooses it; choosing it again clears it. A day's key is its date. The chosen period keeps the hover band, and its tick becomes the inverse chip, so the choice reads without colour. Here the readout above the chart follows the chosen day, and shows the latest day when none is chosen.

On a coloured plate

Inside a coloured Surface the candles take the plate's direction inks and the chrome follows the plate, with no prop. On plates that cannot carry a hue, up and down share the plate's ink, and hollow and filled bodies still tell them apart. The tooltip is a page panel, so it keeps the page's look.

Right-to-left

The chart reads the direction around it. In a right-to-left page time runs from right to left and the price axis moves to the left-hand side, the end of the line. Set dir="rtl" and the tooltip's words switch to Arabic as well; strings overrides them for another language, and yAxisPosition keeps the price axis on one side.

States

Loading skeleton, empty state, error, and a week with a doji and a market holiday.

API Reference

PropTypeDefaultDescription
data{ date, open, high, low, close, volume? }[]—One row per period, oldest first. Other keys are kept for composed children.
showVolumebooleanany row has a volumeDraw volume bars in the bottom fifth of the plot.
showBrushbooleanfalseDraw a range selector under the chart.
defaultBrushRange[number, number]every rowThe selector's first range, as row indices.
onBrushChange(range: [number, number]) => void—Called with the new range, as row indices.
onActiveIndexChange(index: number | null) => void—Called with the row under the pointer or the keyboard, and null when none is.
valueFormatter(value: number) => string2–4 decimalsFormat a price: axis ticks and tooltip.
volumeFormatter(value: number) => stringcompact (1.2M)Format a volume in the tooltip.
dateFormatter(date, index) => stringISO dateFormat a row's date: the tooltip heading, and the x ticks unless xTickFormatter is set.
xTickFormatter / yTickFormatter(value, index) => stringdateFormatter / valueFormatterFormat tick text alone.
yDomain[number | "auto", number | "auto"]fit the visible candlesPin either end of the price axis.
yAxisPosition"left" | "right"the inline endWhich side the price axis sits on.
size"sm" | "md" | "lg""md"Aspect ratio.
showGrid / showTooltip / showXAxis / showYAxisbooleantrueToggle chart chrome.
animatebooleantrueAnimate the candles when they enter. Reduced motion turns it off regardless.
stringsPartial<CandlestickChartStrings>follows dirOverride the tooltip's words, and selection's: selected, cleared, items, period.
selectionMode"none" | "single""none""single" lets a reader choose one period by click, tap, Enter or Space; choosing it again clears it.
selectedstring | null—The date (a Date or a timestamp as its ISO instant) of the chosen period, controlled. null chooses nothing.
defaultSelectedstring | nullnullThe date (a Date or a timestamp as its ISO instant) of the period chosen at first render, uncontrolled.
onSelectedChange(key, { datum, index, source }) => void—Called on every choice and clear; source is "pointer" or "keyboard".
activestring | null—The date (a Date or a timestamp as its ISO instant) of the lit period, controlled: opens the tooltip on it.
onActiveChange(key: string | null) => void—Called when the chart's own pointer or arrow keys move to another period.
childrenReactNode—Extra Recharts children (ReferenceLine, Line, ReferenceArea, …).
dir"ltr" | "rtl"inheritedReading direction. Unset, read off the chart's position in the page once it mounts.
labelstring—Accessible name; renders the chart as a role="figure".
descriptionstring—Visually hidden summary for assistive tech.
loadingbooleanfalseRender the skeleton.
errorReactNode—Render an error message instead of the chart.
emptyStateReactNode"No data to display"Shown when no row has prices.

Up, down and colour

Colour is never the only thing that tells up from down. A candle that closed above its open is hollow; one that closed below is filled; one that closed where it opened is a hairline across its body. The chart reads the same in greyscale, in print and on a plate that cannot carry a hue.

  • Up and down take the status inks: the success ink for up, the error ink for down, and the muted ink for a candle that did not move. They are the same inks a profit-and-loss line uses.
  • Fill and colour follow the same rule — the close against the open — so they never disagree.
  • In the tooltip the change carries its sign, so the direction is in the text too.
  • Volume bars take their candle's ink: a light fill under a full-ink top edge, so the bar's height reads at a mark's contrast and the candles stay the figure.

Missing and sparse data

A row with no prices is a period with no trading. It keeps its slot, draws no candle, and the tooltip says so. It is never drawn at zero.

  • Rows sit one per slot, so time does not stretch over weekends or holidays. Leave a row out to close a gap, or keep it without prices to show one.
  • A row whose high and low are given the wrong way round is drawn around every price it gives.
  • When no row has prices, the chart shows emptyState.

Accessibility

  • Give every chart a label. It names the figure to assistive tech. State the insight, not the anatomy: "Shares fell $3.86 on April 7", not "Candlestick chart of daily prices".
  • Put the numbers in description. A static read should carry the message without anyone stepping through the days.
  • Offer the rows as a table when the exact prices matter (Accessible name and data table).
  • Keyboard. Tab moves focus to the chart, which draws the focus ring. The Left and Right arrow keys step through every period from the start of the axis to its end, and each is read through the tooltip: the date, then open, high, low, close, the change and the volume. Escape dismisses the tooltip without leaving the chart. The range selector's handles are sliders: Tab to one, then the arrow keys move it.
  • Pointer. Anywhere in a candle's column reads that candle, and the range selector's handles take a 44px target under a finger.
  • Motion. Candles grow in once. Under prefers-reduced-motion they do not, and the dimming of the other candles is instant.
  • Screen readers. Not yet verified with VoiceOver, NVDA or JAWS, which is why the component is in beta.
  • With selectionMode="single" the periods become a list to choose from. The chart's one tab stop is a named listbox ("ACME: Periods"), each period an option with aria-selected and a name that carries its readings, and the period the arrow keys are on is its aria-activedescendant. Enter or Space chooses that period, so Enter no longer toggles the tooltip; Escape still hides it. A polite status region says "Mar 3 selected" or "Selection cleared" once per change. The screen reader pass of this mode is still to be run.

Composition

children are rendered inside the Recharts chart and share its axes, so an annotation is Recharts' own API instead of a prop:

example.tsx
import { Line, ReferenceLine } from "recharts";

<CandlestickChart data={rows}>
  <ReferenceLine y={last} strokeDasharray="4 4" />
  <Line dataKey="average" stroke="var(--chart-1)" dot={false} tooltipType="none" />
</CandlestickChart>

A reference line left at its default ink is painted as a hairline to match the grid; a stroke you pass is kept. A label is text, so give it a text ink (hsl(var(--muted-foreground))).

Boundaries

  • No indicators of its own. The chart computes nothing: an average, a band or a signal is your data, drawn as a child.
  • No crosshair price tags, drawing tools or indicator panes. Those belong to a trading terminal.
  • No colour by previous close. Fill and colour both compare a candle's close with its own open.
  • No second price axis. Draw two charts.

Theming

Up and down come from the status tokens, so both themes are handled and a plate re-lights them. Axis text, grid lines, the range selector and the tooltip use the page's text, border and surface tokens, and follow a coloured Surface without a prop.

Scatter ChartSunburst Chart

On this page

When to useWhen not to useInstallationUsageExamplesDefaultVolumeRange selectorReadoutLast priceMoving averageIn a cardFormattingSizesAccessible name and data tableChoosing a dayOn a coloured plateRight-to-leftStatesAPI ReferenceUp, down and colourMissing and sparse dataAccessibilityCompositionBoundariesTheming