Skip to content

Repository files navigation

React Candlesticks

license npm version bundle size types included react 18%2B Coverage total docs

Canvas-powered, JSX-composable candlestick charts for React, for trading UIs.

Build responsive trading charts in React with candlesticks, indicators, crosshairs, zoom, and theming — rendered on Canvas for smooth performance on large datasets.

react-candlesticks is in an early public release / soft launch phase. Feedback, bug reports, and edge-case reproductions are especially helpful.

demo

WebsiteDocumentationnpmLive Demo

For bugs and feature requests, open an issue. For usage questions, start a discussion.

Installation

npm install react-candlesticks

Quick Start

Paste this into your React app for a basic candlestick chart with volume:

import 'react-candlesticks/style.css';
import { Chart, Panel, Candlesticks, VolumeBars, exampleData }
  from 'react-candlesticks';

export default function App() {
  return (
    <Chart
      width={800}
      height={500}
      granularity="d1"
      data={exampleData} // demo data
    >
      <Panel heightRatio={3}>
        <Candlesticks />
      </Panel>
      <Panel>
        <VolumeBars />
      </Panel>
    </Chart>
  );
}

granularity is optional if your dataset uses a consistent interval and you want the library to infer it automatically. For empty/loading states, pass granularity explicitly.

For a fuller walkthrough, see the Getting Started docs.

Why React Candlesticks?

  • Composable like React — build charts using JSX (React components)
  • Canvas rendering — predictable performance on large datasets
  • Trading-focused — indicators, crosshairs, multi-panel layouts
  • Nice feeling pan and zoom — mouse, mouse wheel, trackpad, touch
  • Fully themeable — light and dark mode, or provide a custom theme
  • Lightweight & dependency-free — no core dependencies (other than React and ReactDOM peer dependencies)

Usage Patterns

Declarative (JSX) ✅ recommended

The recommended approach for most use cases. Compose panels and layers declaratively using JSX.

import { Chart, Panel, Candlesticks, VolumeBars, BollingerBands, EMA, MACD, RSI }
  from 'react-candlesticks';

<Chart granularity="d1" data={data}>
  <Panel heightRatio={3}>
    <Candlesticks />
    <EMA period={50} />
    <BollingerBands />
  </Panel>
  <Panel>
    <VolumeBars />
  </Panel>
  <Panel>
    <MACD />
  </Panel>
  <Panel>
    <RSI />
  </Panel>
</Chart>

Config-driven (panels prop)

import { Chart } from 'react-candlesticks';

<Chart
  granularity="d1"
  data={data}
  panels={[
    {
      heightRatio: 3,
      layers: [
        { type: 'price:candlesticks' },
        { type: 'ema', period: 20 },
        { type: 'bollinger-bands' },
      ]
    },
    {
      layers: [{ type: 'volume:bars' }]
    },
    {
      layers: [{ type: 'macd' }]
    },
    {
      layers: [{ type: 'rsi' }]
    }
  ]}
/>

Custom indicator layers

Use defineLayer to add an indicator implementation to a specific chart. A definition controls config parsing, calculation, and Canvas drawing, and provides a JSX component through its Component property.

import {
  Candlesticks,
  Chart,
  Panel,
  defineLayer,
  drawLineSeries,
  parseLineConfig,
} from 'react-candlesticks';

const doubleClose = defineLayer({
  type: 'custom:double-close',
  parseConfig: (config, _theme, panelId) => ({
    ...config,
    id: config.id ?? `double-close_${panelId}`,
    type: 'custom:double-close',
    indicator: true,
    // Share the candlestick price scale so doubled values use price coordinates.
    defaultScale: { key: 'price_auto', domain: 'price', range: { type: 'auto' } },
    scale: config.scale ?? null,
    scalePolicy: 'derived',
    requiredInputKeys: ['input'],
    inputs: config.inputs ?? [
      { key: 'input', source: { type: 'price', field: 'close' } },
    ],
    outputs: ['value'],
    period: 1,
    offset: 0,
    lookback: 0,
    calculate: true,
    includeInAutoScale: true,
    valueToY: (min, max, top, height) =>
      value => top + ((max - value) / (max - min)) * height,
    legend: null,
    yAxis: null,
    valueLabelFormatter: String,
  }),
  calculate: (_config, inputs, outputs, start, end) => {
    for (let index = start; index < end; index++) {
      outputs.value[index] = inputs.input.values[index] * 2;
    }
  },
  draw: (
    context, _axes, _chart, _panel, config, _layout, viewport,
    _chartMetrics, _panelMetrics, layerMetrics,
  ) => {
    const values =
      viewport.layersData.layerDataInstances[config.id].outputValues.value;
    const { intervalSize, scrollOffset } = viewport.timeScale.metadata;

    drawLineSeries({
      context,
      values,
      lineConfig: parseLineConfig({ color: '#8b5cf6', width: 2 })!,
      valueToY: layerMetrics.valueToY,
      startBarIndex: viewport.timeScale.startBarIndex,
      endBarIndex: viewport.timeScale.endBarIndex,
      intervalSize,
      scrollOffset,
    });
  },
});

const DoubleClose = doubleClose.Component;

<Chart data={data} customLayers={[doubleClose]}>
  <Panel>
    <Candlesticks />
    <DoubleClose />
  </Panel>
</Chart>

Custom type names must not collide with built-in or other custom layer types. Definitions are chart-scoped, so separate charts can use independent custom layer sets without global registration.

Minimal Mode for List/Thumbnail Charts

Use renderMode="minimal" when rendering many small charts per screen (for example, watchlist rows or card grids).

This mode defaults to a lighter configuration by disabling interaction and non-essential chart chrome unless you explicitly opt back in.

<Chart
  renderMode="minimal"
  pixelRatio={1}
  width={120}
  height={60}
  data={data}
>
  <Panel>
    <Candlesticks yAxis={false} legend={false} />
  </Panel>
</Chart>
  • renderMode="minimal" defaults crosshairs, grid, xAxis, borders, enableScroll, and enableZoom to false.
  • You can still override any of these explicitly.
  • pixelRatio defaults to 'device'; set pixelRatio={1} for cheaper rendering in dense lists.

Scale smoothing

Enable y-scale smoothing to reduce abrupt vertical rescaling while panning.

<Chart data={data} scaleSmoothing />

<Chart
  data={data}
  scaleSmoothing={{ durationMs: 240, expandImmediate: true }}
/>

Imperative chart controls (experimental)

Use a ChartHandle ref when external UI needs to drive the viewport or crosshair.

import { useRef, useState } from 'react';
import type { ChartHandle, ChartViewport } from 'react-candlesticks';
import { Candlesticks, Chart, Panel, VolumeBars } from 'react-candlesticks';

function ControlledChart({ data }) {
  const chartRef = useRef<ChartHandle>(null);
  const [viewport, setViewport] = useState<ChartViewport | null>(null);
  const highlighted = data[Math.floor(data.length * 0.7)];

  return (
    <>
      <button
        type="button"
        onClick={() => chartRef.current?.setVisibleRange({
          from: data.length - 120,
          to: data.length - 40,
          marginBars: 6,
        })}
      >
        Show range
      </button>
      <button type="button" onClick={() => chartRef.current?.fitContent({ marginBars: 8 })}>
        Fit
      </button>
      <button type="button" onClick={() => chartRef.current?.scrollToLatest({ marginBars: 8 })}>
        Latest
      </button>
      <button
        type="button"
        onClick={() => chartRef.current?.setCrosshairPosition({
          timestamp: highlighted.time,
          value: highlighted.close,
          panelId: 'price',
        })}
      >
        Crosshair
      </button>

      <Chart
        ref={chartRef}
        data={data}
        onViewportChange={setViewport}
        userViewportCallbackMode="debounce"
        userViewportCallbackDebounceMs={120}
      >
        <Panel id="price" heightRatio={3}>
          <Candlesticks />
        </Panel>
        <Panel id="volume">
          <VolumeBars />
        </Panel>
      </Chart>

      {viewport && (
        <output>
          {viewport.startBarIndex}-{viewport.endBarIndex}
        </output>
      )}
    </>
  );
}

setCrosshairPosition() gives pointer movement control again as soon as the user moves over the chart. Pass { lock: true } as the second argument to keep the programmatic crosshair fixed until clearCrosshairPosition() is called.

User-driven viewport callbacks default to one notification per animation frame. Use userViewportCallbackMode="debounce" for heavier React state updates, "sync" for every internal viewport update, or "none" when you only need imperative reads through getViewport().

Theming

Use the built-in 'light' or 'dark' theme, or provide a partial Theme object.

import type { Theme } from 'react-candlesticks';

// Named theme
<Chart theme="dark" ... />

const myCustomTheme: Theme = {
  base: 'dark',
  chart: {
    backgroundColor: '#101820',
  },
  indicators: {
    line: { width: 2 },
    linePalette: ['#ffd166', '#5dd9c1', '#c77dff'],
  },
  layers: {
    sma: {
      series: {
        value: { color: '#ffd166', width: 3 },
      },
    },
  },
};

// Custom partial theme
<Chart theme={myCustomTheme} ... />

See the Theme documentation for the full theme shape.


Compatibility and Limitations

  • Supports React 18+ and React 19.
  • Ships ESM only; CommonJS/UMD consumers are not supported.
  • Intended for modern browser-based React apps.
  • Chart rendering and interaction are client-side. In SSR frameworks, render the chart from a client component or client-only boundary.
  • Runtime behavior depends on standard browser APIs including Canvas, ResizeObserver, matchMedia, devicePixelRatio, and Pointer Events for touch and pinch interactions.
  • If you target older browsers or restricted webviews, you may need compatibility checks or polyfills for APIs such as ResizeObserver.
  • Current chart time display defaults to UTC. A public chart-level timezone configuration API is not exposed yet, so if your UI requires exchange-local or user-local time labels, treat that as a current limitation.
  • Mouse, trackpad, touch, and pinch interactions are covered in modern evergreen browsers. If you rely on embedded webviews, older Safari/WebKit builds, or custom kiosk environments, verify pointer and wheel behavior in your exact target runtime before rollout.
  • Data should use a consistent interval if you want automatic granularity inference. For irregular datasets, pass granularity explicitly.

Layers

Layer Type string Description
<Candlesticks> 'price:candlesticks' OHLC candlestick chart
<PriceLine> 'price:line' Line chart of a single price field
<VolumeBars> 'volume:bars' Volume bar chart
<ADX> 'adx' Average Directional Index
<ATR> 'atr' Average True Range
<BollingerBands> 'bollinger-bands' Bollinger Bands overlay
<CCI> 'cci' Commodity Channel Index
<EMA> 'ema' Exponential Moving Average
<MACD> 'macd' MACD oscillator
<OBV> 'obv' On-Balance Volume
<ParabolicSAR> 'parabolic-sar' Parabolic Stop and Reverse overlay
<RSI> 'rsi' Relative Strength Index
<SMA> 'sma' Simple Moving Average
<Stochastic> 'stochastic' Stochastic Oscillator
<WilliamsR> 'williams-r' Williams %R oscillator

API Reference


Latest Articles

30 July 2026 · 4 min read · Intro

An open-source React/TypeScript library for composable financial charts with candlesticks, volume, indicators, theming, custom layers, and drawings.

Jul 2, 2026 · 6 min read · Guides

Build an interactive React candlestick chart in Next.js with volume bars, SMA overlays, a Stochastic indicator, and light and dark themes.

Jun 29, 2026 · 9 min read · Guides

Fetch free crypto OHLCV data and build a React crypto chart with granularity controls, chart type switching, volume, and removable indicators.

Jun 7, 2026 · 7 min read · Guides

Build an interactive OHLC chart with volume bars, technical indicators, crosshairs, zoom, and dark mode.

View all articles →


Project Notes


Prop Types

If you want runtime prop validation in a JavaScript app, install the optional peer dependency too:

npm install react-candlesticks prop-types

Then import Chart from the opt-in propTypes entrypoint:

import { Chart } from 'react-candlesticks/propTypes';

License

MIT