AdminKitDocs
Charts

Next.js Doughnut Chart

Build a Next.js doughnut chart with Chart.js: a client component with a 65% cutout, rendered from a server component page in the App Router.

A doughnut chart shows a total split into a few parts as a ring: a pie chart with an empty center. In the free and PRO versions of AdminKit for Next.js, the ChartJs component draws it with Chart.js. In AdminKit PRO, ApexChart draws the ApexCharts version.

ChartJs is a client component, since Chart.js draws in the browser. You describe the chart with a function of the theme colors. Pages in the App Router are server components, and server components cannot pass functions to client components. So the chart lives in its own "use client" file that your page renders. More in Chart setup.

Example

Traffic sources with a cutout of 65% of the radius.

src/components/traffic-doughnut-chart.tsx
"use client";

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

export function TrafficDoughnutChart() {
  return (
    <div className="chart">
      <ChartJs
        aria-label="Visits per traffic source"
        config={(theme) => ({
          type: "doughnut",
          data: {
            labels: ["Social", "Search Engines", "Direct", "Other"],
            datasets: [
              {
                data: [260, 125, 54, 146],
                backgroundColor: [theme.primary, theme.success, theme.warning, "#dee2e6"],
                borderColor: "transparent",
              },
            ],
          },
          options: {
            maintainAspectRatio: false,
            cutout: "65%",
            plugins: { legend: { display: false } },
          },
        })}
      />
    </div>
  );
}

The server component page renders it:

src/app/(dashboard)/traffic/page.tsx
import { TrafficDoughnutChart } from "@/components/traffic-doughnut-chart";
import { pageMetadata } from "@/lib/site";

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

export default function TrafficPage() {
  return (
    <>
      <h1 className="h3 mb-3">Traffic</h1>
      <div className="card">
        <div className="card-header">
          <h2 className="card-title">Traffic sources</h2>
        </div>
        <div className="card-body">
          <TrafficDoughnutChart />
        </div>
      </div>
    </>
  );
}

The exported HTML shows a skeleton where the chart goes. The ring draws once the browser has taken over that HTML (hydration).

Props

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

Other props, such as id and aria-label, go to the <canvas>. The demo's doughnut chart sits in .chart.chart-sm, 250px high. Chart setup has the other heights.

Cutout and colors

cutout is the size of the hole, as a percentage of the radius or in pixels. Raise it for a thinner ring:

options: {
  maintainAspectRatio: false,
  cutout: "80%",
},

The "Other" slice uses the fixed #dee2e6 from the HTML example. theme["gray-300"] is that gray in light mode and follows the gray scale of dark mode.

ApexCharts example

Pro

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

ApexCharts calls this chart type "donut". Its series is a plain array of numbers, one per slice. The file starts with "use client", like the Chart.js one.

src/components/sources-donut-chart.tsx
"use client";

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

export function SourcesDonutChart() {
  return (
    <div className="position-relative" style={{ minHeight: 365 }}>
      <ApexChart
        options={() => ({
          chart: { height: 350, type: "donut" },
          dataLabels: { enabled: false },
          series: [44, 55, 13, 33],
        })}
      />
    </div>
  );
}

The slices take the primary, success, warning and danger colors and the borders the surface color (theme.white). On a color mode change, ApexChart draws the donut again without animation, with the new colors.

Accessibility

  • The <canvas> of ChartJs has role="img". Screen readers do not read the slices, so add an aria-label and write the shares in text.
  • The Chart.js example hides the legend, so only color tells the sources apart. Show the legend or name them next to the chart.
  • Under prefers-reduced-motion, the charts draw without the entrance animation.

Last updated on

On this page