AdminKitDocs
Charts

Next.js Candlestick Chart

A Next.js candlestick chart for the App Router: OHLC prices in an ApexCharts client component of AdminKit PRO, the chart of the crypto dashboard.

Pro

This component is only available in AdminKit PRO. Learn more.

Use a candlestick chart for prices that move. One candle stands for one period and shows where the price opened, how high and low it went, and where it closed: open, high, low and close, or OHLC for short. In AdminKit for Next.js, ApexChart draws it with ApexCharts, as on the crypto dashboard. See Chart setup for the component.

ApexChart draws in the browser, so it is a client component. Your page is a server component, which renders on the server and cannot pass the options function to it. Write the chart as a client component file, with the data and the options function, and render it from the page.

Example

src/components/price-chart.tsx
"use client";

import { ApexChart } from "@/components/plugins/apexcharts";

// One candle per month: y is [open, high, low, close]
const prices = [
  { x: new Date(2025, 1, 1), y: [51.98, 56.29, 51.59, 53.85] },
  { x: new Date(2025, 2, 1), y: [53.66, 54.99, 51.35, 52.95] },
  { x: new Date(2025, 3, 1), y: [52.96, 53.78, 51.54, 52.48] },
  { x: new Date(2025, 4, 1), y: [52.54, 52.79, 47.88, 49.24] },
  { x: new Date(2025, 5, 1), y: [49.1, 52.86, 47.7, 52.78] },
  { x: new Date(2025, 6, 1), y: [52.83, 53.48, 50.32, 52.29] },
  { x: new Date(2025, 7, 1), y: [52.2, 54.48, 51.64, 52.58] },
  { x: new Date(2025, 8, 1), y: [52.76, 57.35, 52.15, 57.03] },
  { x: new Date(2025, 9, 1), y: [57.04, 58.15, 48.88, 56.19] },
  { x: new Date(2025, 10, 1), y: [56.09, 58.85, 55.48, 58.79] },
  { x: new Date(2025, 11, 1), y: [58.78, 59.65, 58.23, 59.05] },
  { x: new Date(2026, 0, 1), y: [59.37, 61.11, 59.35, 60.34] },
  { x: new Date(2026, 1, 1), y: [60.4, 60.52, 56.71, 56.93] },
  { x: new Date(2026, 2, 1), y: [57.02, 59.71, 56.04, 56.82] },
  { x: new Date(2026, 3, 1), y: [56.97, 59.62, 54.77, 59.3] },
  { x: new Date(2026, 4, 1), y: [59.11, 62.29, 59.1, 59.85] },
  { x: new Date(2026, 5, 1), y: [59.97, 60.11, 55.66, 58.42] },
  { x: new Date(2026, 6, 1), y: [58.34, 60.93, 56.75, 57.42] },
  { x: new Date(2026, 7, 1), y: [57.76, 58.08, 51.18, 54.71] },
  { x: new Date(2026, 8, 1), y: [54.8, 61.42, 53.18, 57.35] },
  { x: new Date(2026, 9, 1), y: [57.56, 63.09, 57.0, 62.99] },
  { x: new Date(2026, 10, 1), y: [62.89, 63.42, 59.72, 61.76] },
  { x: new Date(2026, 11, 1), y: [61.71, 64.15, 61.29, 63.04] },
];

export function PriceChart() {
  return (
    <div className="position-relative" style={{ minHeight: 365 }}>
      <ApexChart
        options={() => ({
          chart: { height: 350, type: "candlestick" },
          series: [{ data: prices }],
          stroke: { width: 1 },
          xaxis: { type: "datetime" },
        })}
      />
    </div>
  );
}
src/app/(dashboard)/markets/page.tsx
import { PriceChart } from "@/components/price-chart";
import { pageMetadata } from "@/lib/site";

export const metadata = pageMetadata("/markets");

export default function MarketsPage() {
  return (
    <div className="card">
      <div className="card-header">
        <h2 className="card-title mb-0">LTC/BTC</h2>
      </div>
      <div className="card-body">
        <PriceChart />
      </div>
    </div>
  );
}

List /markets in src/data/pages.json, where pageMetadata() reads its title and description. Mind the dates in the example: JavaScript counts months from 0, so new Date(2025, 1, 1) is 1 February 2025.

Props

PropTypeDefaultDescription
options(theme: Theme) => ApexOptionsThe ApexCharts options, with series and chart.type. Called again with the new colors when the color mode changes

Data

Each point is { x, y }. x is the date of the period, and y holds the prices in the order open, high, low, close.

Prices change, but AdminKit for Next.js is a static export (output: "export"). A server component page runs at build time, so prices passed from it are those of the last build. For current prices, fetch them in the client component, for example in a useEffect. Keep the chart in its skeleton state until they arrive:

src/components/price-chart.tsx
"use client";

import { useEffect, useState } from "react";
import { ApexChart } from "@/components/plugins/apexcharts";
import { Skeleton } from "@/components/ui/skeleton";

type Candle = { x: number; y: [number, number, number, number] };

export function PriceChart() {
  const [prices, setPrices] = useState<Candle[] | null>(null);

  useEffect(() => {
    fetch("https://api.example.com/ltc-btc/monthly")
      .then((response) => response.json())
      .then(setPrices);
  }, []);

  return (
    <div className="position-relative" style={{ minHeight: 365 }}>
      {prices ? (
        <ApexChart
          options={() => ({
            chart: { height: 350, type: "candlestick" },
            series: [{ data: prices }],
            xaxis: { type: "datetime" },
          })}
        />
      ) : (
        <Skeleton />
      )}
    </div>
  );
}

The AI and CRM dashboards render a Skeleton the same way while their data is not ready.

Candle colors

ApexCharts draws candlesticks in its own green and red, not in the series colors of the wrapper. plotOptions.candlestick.colors takes the theme colors instead, so the candles follow the color mode:

plotOptions: {
  candlestick: {
    colors: { upward: theme.success, downward: theme.danger },
  },
},

Accessibility

  • A rising and a falling candle have the same shape. Only the color differs. Put the latest price and its change in text, as the stat cards of the crypto dashboard do.
  • The OHLC values of a candle show in the tooltip on hover. Add a table where exact prices matter.
  • Other props of ApexChart go to its <div>: role="figure" with an aria-label names the chart.

Last updated on

On this page