AdminKitDocs
Charts

Next.js Bar Chart

A Next.js bar chart for the App Router: a Chart.js client component rendered from a server component page, in theme colors for light and dark mode.

Use a bar chart to compare values side by side, such as this year's sales against last year's, month by month. In the free and PRO versions of AdminKit for Next.js, the ChartJs component draws it with Chart.js. AdminKit PRO also has ApexChart for ApexCharts.

Chart.js draws on a canvas in the browser, so ChartJs is a client component, marked "use client". That decides where the chart goes. The chart's configuration is a function of the theme colors, and pages in the App Router are server components, which cannot pass a function to a client component. So write the chart in its own client component and render that from your page. The page stays a server component with its metadata export. Chart setup explains the components.

Example

Each month has two bars, last year and this year. The rounded top corners (borderRadius: 10) come from AdminKit's Chart.js defaults.

src/components/sales-bar-chart.tsx
"use client";

import { ChartJs } from "@/components/plugins/chartjs";

export function SalesBarChart() {
  return (
    <div className="chart">
      <ChartJs
        aria-label="Sales per month, last year and this year"
        config={(theme) => ({
          type: "bar",
          data: {
            labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
            datasets: [
              {
                label: "Last year",
                backgroundColor: theme.primary,
                borderColor: theme.primary,
                hoverBackgroundColor: theme.primary,
                hoverBorderColor: theme.primary,
                data: [54, 67, 41, 55, 62, 45, 55, 73, 60, 76, 48, 79],
                barPercentage: 0.75,
                categoryPercentage: 0.5,
              },
              {
                label: "This year",
                backgroundColor: "#dee2e6",
                borderColor: "#dee2e6",
                hoverBackgroundColor: "#dee2e6",
                hoverBorderColor: "#dee2e6",
                data: [69, 66, 24, 48, 52, 51, 44, 53, 62, 79, 51, 68],
                barPercentage: 0.75,
                categoryPercentage: 0.5,
              },
            ],
          },
          options: {
            maintainAspectRatio: false,
            plugins: { legend: { display: false } },
            scales: {
              y: {
                grid: { display: false },
                border: { display: false },
                stacked: false,
                ticks: { stepSize: 20 },
              },
              x: {
                stacked: false,
                grid: { color: "transparent" },
                border: { display: false },
              },
            },
          },
        })}
      />
    </div>
  );
}

The page imports it like any other component. pageMetadata() reads the title and description from src/data/pages.json, so a new page needs an entry there (see Add a page).

src/app/(dashboard)/reports/page.tsx
import { SalesBarChart } from "@/components/sales-bar-chart";
import { pageMetadata } from "@/lib/site";

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

export default function ReportsPage() {
  return (
    <>
      <h1 className="h3 mb-3">Reports</h1>
      <div className="card">
        <div className="card-header">
          <h2 className="card-title">Sales</h2>
        </div>
        <div className="card-body">
          <SalesBarChart />
        </div>
      </div>
    </>
  );
}

The theme colors are only known in the browser. So the exported HTML has an empty <canvas> and a skeleton in the .chart box, and the chart draws once the page has hydrated. The server and browser output match, and the layout does not move.

Props

PropTypeDefaultDescription
config(theme: Theme) => ChartConfigurationThe Chart.js configuration: type, data and options. Called again with the new colors when the color mode changes

Other props, such as id and aria-label, go to the <canvas>. The container heights and AdminKit's Chart.js defaults are on Chart setup.

Colors in dark mode

To make a color follow the mode, read it from theme. "This year" keeps the fixed #dee2e6 of the HTML example, so it stays light gray in dark mode. theme["gray-300"] gives the same gray in light mode and follows the gray scale of dark mode:

backgroundColor: theme["gray-300"],

Horizontal and stacked bars

Horizontal and stacked bars are a matter of Chart.js options. indexAxis: "y" turns the bars sideways, and stacked: true on both scales stacks the series:

options: {
  indexAxis: "y",
  scales: {
    x: { stacked: true },
    y: { stacked: true },
  },
},

ApexCharts example

Pro

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

This example draws a horizontal stacked bar chart with ApexChart. plotOptions.bar.horizontal lays the bars on their side, and chart.stacked stacks the series. ApexChart is a client component too, so this file also starts with "use client". It loads ApexCharts in the browser only, so you don't need next/dynamic.

src/components/products-bar-chart.tsx
"use client";

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

export function ProductsBarChart() {
  return (
    <div className="position-relative" style={{ minHeight: 365 }}>
      <ApexChart
        options={() => ({
          chart: { height: 350, type: "bar", stacked: true },
          plotOptions: { bar: { horizontal: true } },
          stroke: { width: 1, colors: ["#fff"] },
          series: [
            { name: "Marine Sprite", data: [44, 55, 41, 37, 22, 43, 21] },
            { name: "Striking Calf", data: [53, 32, 33, 52, 13, 43, 32] },
            { name: "Tank Picture", data: [12, 17, 11, 9, 15, 11, 20] },
            { name: "Bucket Slope", data: [9, 7, 5, 8, 6, 9, 4] },
            { name: "Reborn Kid", data: [25, 12, 19, 32, 25, 24, 10] },
          ],
          xaxis: {
            categories: [2020, 2021, 2022, 2023, 2024, 2025, 2026],
            labels: { formatter: (val: string) => val + "K" },
          },
          yaxis: { title: { text: undefined } },
          tooltip: { y: { formatter: (val: number) => val + "K" } },
          fill: { opacity: 1 },
          legend: { position: "top", horizontalAlign: "left", offsetX: 40 },
        })}
      />
    </div>
  );
}

Without colors in the options, the series take the primary, success, warning, danger and info colors of the current mode. The 1px #fff stroke between the stacked parts stays white in dark mode. theme.white, the surface color, follows the mode.

Accessibility

  • The <canvas> that ChartJs renders has role="img". Screen readers do not read the bars, so give it an aria-label that says what it shows, and the figures that matter in text.
  • The legend is hidden in the Chart.js example, so the two series differ only by color. Name them in the card text or turn the legend on.
  • Under prefers-reduced-motion, both components draw the chart without the entrance animation.

Last updated on

On this page