Command Palette

Search for a command...

Chart

Beautiful charts. Built using Recharts. Copy and paste into your apps.

Bar Chart - Interactive
Showing total visitors for the last 3 months
"use client";

import * as React from "react";
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";
import { css } from "styled-system/css";
import { styled } from "styled-system/jsx";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import {
  ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart";

export const description = "An interactive bar chart";

const chartData = [
  { date: "2024-04-01", desktop: 222, mobile: 150 },
  { date: "2024-04-02", desktop: 97, mobile: 180 },
  { date: "2024-04-03", desktop: 167, mobile: 120 },
  { date: "2024-04-04", desktop: 242, mobile: 260 },
  { date: "2024-04-05", desktop: 373, mobile: 290 },
  { date: "2024-04-06", desktop: 301, mobile: 340 },
  { date: "2024-04-07", desktop: 245, mobile: 180 },
  { date: "2024-04-08", desktop: 409, mobile: 320 },
  { date: "2024-04-09", desktop: 59, mobile: 110 },
  { date: "2024-04-10", desktop: 261, mobile: 190 },
  { date: "2024-04-11", desktop: 327, mobile: 350 },
  { date: "2024-04-12", desktop: 292, mobile: 210 },
  { date: "2024-04-13", desktop: 342, mobile: 380 },
  { date: "2024-04-14", desktop: 137, mobile: 220 },
  { date: "2024-04-15", desktop: 120, mobile: 170 },
  { date: "2024-04-16", desktop: 138, mobile: 190 },
  { date: "2024-04-17", desktop: 446, mobile: 360 },
  { date: "2024-04-18", desktop: 364, mobile: 410 },
  { date: "2024-04-19", desktop: 243, mobile: 180 },
  { date: "2024-04-20", desktop: 89, mobile: 150 },
  { date: "2024-04-21", desktop: 137, mobile: 200 },
  { date: "2024-04-22", desktop: 224, mobile: 170 },
  { date: "2024-04-23", desktop: 138, mobile: 230 },
  { date: "2024-04-24", desktop: 387, mobile: 290 },
  { date: "2024-04-25", desktop: 215, mobile: 250 },
  { date: "2024-04-26", desktop: 75, mobile: 130 },
  { date: "2024-04-27", desktop: 383, mobile: 420 },
  { date: "2024-04-28", desktop: 122, mobile: 180 },
  { date: "2024-04-29", desktop: 315, mobile: 240 },
  { date: "2024-04-30", desktop: 454, mobile: 380 },
  { date: "2024-05-01", desktop: 165, mobile: 220 },
  { date: "2024-05-02", desktop: 293, mobile: 310 },
  { date: "2024-05-03", desktop: 247, mobile: 190 },
  { date: "2024-05-04", desktop: 385, mobile: 420 },
  { date: "2024-05-05", desktop: 481, mobile: 390 },
  { date: "2024-05-06", desktop: 498, mobile: 520 },
  { date: "2024-05-07", desktop: 388, mobile: 300 },
  { date: "2024-05-08", desktop: 149, mobile: 210 },
  { date: "2024-05-09", desktop: 227, mobile: 180 },
  { date: "2024-05-10", desktop: 293, mobile: 330 },
  { date: "2024-05-11", desktop: 335, mobile: 270 },
  { date: "2024-05-12", desktop: 197, mobile: 240 },
  { date: "2024-05-13", desktop: 197, mobile: 160 },
  { date: "2024-05-14", desktop: 448, mobile: 490 },
  { date: "2024-05-15", desktop: 473, mobile: 380 },
  { date: "2024-05-16", desktop: 338, mobile: 400 },
  { date: "2024-05-17", desktop: 499, mobile: 420 },
  { date: "2024-05-18", desktop: 315, mobile: 350 },
  { date: "2024-05-19", desktop: 235, mobile: 180 },
  { date: "2024-05-20", desktop: 177, mobile: 230 },
  { date: "2024-05-21", desktop: 82, mobile: 140 },
  { date: "2024-05-22", desktop: 81, mobile: 120 },
  { date: "2024-05-23", desktop: 252, mobile: 290 },
  { date: "2024-05-24", desktop: 294, mobile: 220 },
  { date: "2024-05-25", desktop: 201, mobile: 250 },
  { date: "2024-05-26", desktop: 213, mobile: 170 },
  { date: "2024-05-27", desktop: 420, mobile: 460 },
  { date: "2024-05-28", desktop: 233, mobile: 190 },
  { date: "2024-05-29", desktop: 78, mobile: 130 },
  { date: "2024-05-30", desktop: 340, mobile: 280 },
  { date: "2024-05-31", desktop: 178, mobile: 230 },
  { date: "2024-06-01", desktop: 178, mobile: 200 },
  { date: "2024-06-02", desktop: 470, mobile: 410 },
  { date: "2024-06-03", desktop: 103, mobile: 160 },
  { date: "2024-06-04", desktop: 439, mobile: 380 },
  { date: "2024-06-05", desktop: 88, mobile: 140 },
  { date: "2024-06-06", desktop: 294, mobile: 250 },
  { date: "2024-06-07", desktop: 323, mobile: 370 },
  { date: "2024-06-08", desktop: 385, mobile: 320 },
  { date: "2024-06-09", desktop: 438, mobile: 480 },
  { date: "2024-06-10", desktop: 155, mobile: 200 },
  { date: "2024-06-11", desktop: 92, mobile: 150 },
  { date: "2024-06-12", desktop: 492, mobile: 420 },
  { date: "2024-06-13", desktop: 81, mobile: 130 },
  { date: "2024-06-14", desktop: 426, mobile: 380 },
  { date: "2024-06-15", desktop: 307, mobile: 350 },
  { date: "2024-06-16", desktop: 371, mobile: 310 },
  { date: "2024-06-17", desktop: 475, mobile: 520 },
  { date: "2024-06-18", desktop: 107, mobile: 170 },
  { date: "2024-06-19", desktop: 341, mobile: 290 },
  { date: "2024-06-20", desktop: 408, mobile: 450 },
  { date: "2024-06-21", desktop: 169, mobile: 210 },
  { date: "2024-06-22", desktop: 317, mobile: 270 },
  { date: "2024-06-23", desktop: 480, mobile: 530 },
  { date: "2024-06-24", desktop: 132, mobile: 180 },
  { date: "2024-06-25", desktop: 141, mobile: 190 },
  { date: "2024-06-26", desktop: 434, mobile: 380 },
  { date: "2024-06-27", desktop: 448, mobile: 490 },
  { date: "2024-06-28", desktop: 149, mobile: 200 },
  { date: "2024-06-29", desktop: 103, mobile: 160 },
  { date: "2024-06-30", desktop: 446, mobile: 400 },
];

const chartConfig = {
  views: {
    label: "Page Views",
  },
  desktop: {
    label: "Desktop",
    color: "var(--colors-chart-1)",
  },
  mobile: {
    label: "Mobile",
    color: "var(--colors-chart-2)",
  },
} satisfies ChartConfig;

export default function ChartDemo() {
  const [activeChart, setActiveChart] = React.useState<keyof typeof chartConfig>("desktop");

  const total = React.useMemo(
    () => ({
      desktop: chartData.reduce((acc, curr) => acc + curr.desktop, 0),
      mobile: chartData.reduce((acc, curr) => acc + curr.mobile, 0),
    }),
    []
  );

  return (
    <Card css={{ w: "full", py: "0" }}>
      <CardHeader
        css={{
          display: "flex",
          flexDir: "column",
          alignItems: "stretch",
          spaceY: "0",
          borderBottomWidth: "1px",
          p: "0!",
          sm: { flexDir: "row" },
        }}
      >
        <styled.div
          css={{
            display: "flex",
            flex: "1",
            flexDir: "column",
            justifyContent: "center",
            gap: "1",
            px: "6",
            pt: "4",
            pb: "3",
            sm: { py: "0!" },
          }}
        >
          <CardTitle>Bar Chart - Interactive</CardTitle>
          <CardDescription>Showing total visitors for the last 3 months</CardDescription>
        </styled.div>
        <styled.div css={{ display: "flex" }}>
          {["desktop", "mobile"].map((key) => {
            const chart = key as keyof typeof chartConfig;
            return (
              <styled.button
                key={chart}
                data-active={activeChart === chart}
                css={{
                  pos: "relative",
                  zIndex: "30",
                  display: "flex",
                  flex: "1",
                  flexDir: "column",
                  justifyContent: "center",
                  gap: "1",
                  borderTopWidth: "1px",
                  px: "6",
                  py: "4",
                  textAlign: "left",
                  _even: { borderLeftWidth: "1px" },
                  sm: { borderLeftWidth: "1px", borderTopWidth: "0", px: "8", py: "6" },
                  "&[data-active=true]": {
                    bg: "muted/50",
                  },
                }}
                onClick={() => setActiveChart(chart)}
              >
                <styled.span css={{ textStyle: "xs", color: "muted.fg" }}>
                  {chartConfig[chart].label}
                </styled.span>
                <styled.span
                  css={{
                    textStyle: "lg",
                    fontWeight: "bold",
                    lineHeight: "none",
                    sm: { textStyle: "3xl" },
                  }}
                >
                  {total[key as keyof typeof total].toLocaleString()}
                </styled.span>
              </styled.button>
            );
          })}
        </styled.div>
      </CardHeader>
      <CardContent css={{ px: "2", sm: { p: "6" } }}>
        <ChartContainer config={chartConfig} css={{ aspectRatio: "auto", w: "full", h: "250px" }}>
          <BarChart
            accessibilityLayer
            data={chartData}
            margin={{
              left: 12,
              right: 12,
            }}
          >
            <CartesianGrid vertical={false} />
            <XAxis
              dataKey="date"
              tickLine={false}
              axisLine={false}
              tickMargin={8}
              minTickGap={32}
              tickFormatter={(value) => {
                const date = new Date(value);
                return date.toLocaleDateString("en-US", {
                  month: "short",
                  day: "numeric",
                });
              }}
            />
            <ChartTooltip
              content={
                <ChartTooltipContent
                  className={css({ w: "150px!" })}
                  nameKey="views"
                  labelFormatter={(value) => {
                    return new Date(value).toLocaleDateString("en-US", {
                      month: "short",
                      day: "numeric",
                      year: "numeric",
                    });
                  }}
                />
              }
            />
            <Bar dataKey={activeChart} fill={`var(--color-${activeChart})`} />
          </BarChart>
        </ChartContainer>
      </CardContent>
    </Card>
  );
}

Introducing Charts. A collection of chart components that you can copy and paste into your apps.

Charts are designed to look great out of the box. They work well with the other components and are fully customizable to fit your project.

Component

We use Recharts under the hood.

We designed the chart component with composition in mind. You build your charts using Recharts components and only bring in custom components, such as ChartTooltip, when and where you need it.

import { Bar, BarChart } from "recharts";
import { ChartContainer, ChartTooltipContent } from "@/components/ui/charts";

export function MyChart() {
  return (
    <ChartContainer>
      <BarChart data={data}>
        <Bar dataKey="value" />
        <ChartTooltip content={<ChartTooltipContent />} />
      </BarChart>
    </ChartContainer>
  );
}

We do not wrap Recharts. This means you're not locked into an abstraction. When a new Recharts version is released, you can follow the official upgrade path to upgrade your charts.

The components are yours.

Installation

Run the following command to install chart.tsx

npx nore-ui-cli@latest add chart

Add the following colors to your semantic tokens.

export const semanticTokens = defineSemanticTokens({
  colors: {
    // ... other colors
    chart: {
      1: { value: { base: "hsl(12 76% 61%)", _dark: "hsl(220 70% 50%)" } },
      2: { value: { base: "hsl(173 58% 39%)", _dark: "hsl(160 60% 45%)" } },
      3: { value: { base: "hsl(197 37% 24%)", _dark: "hsl(30 80% 55%)" } },
      4: { value: { base: "hsl(43 74% 66%)", _dark: "hsl(280 65% 60%)" } },
      5: { value: { base: "hsl(27 87% 67%)", _dark: "hsl(340 75% 55%)" } },
    },
  },
});

Your First Chart

Let's build your first chart. We'll build a bar chart, add a grid, axis, tooltip and legend.

Start by defining your data.

The following data represents the number of desktop and mobile users for each month.

Note: Your data can be in any shape. You are not limited to the shape of the data below. Use the dataKey prop to map your data to the chart.

const chartData = [
  { month: "January", desktop: 186, mobile: 80 },
  { month: "February", desktop: 305, mobile: 200 },
  { month: "March", desktop: 237, mobile: 120 },
  { month: "April", desktop: 73, mobile: 190 },
  { month: "May", desktop: 209, mobile: 130 },
  { month: "June", desktop: 214, mobile: 140 },
];

Define your chart config

The chart config holds configuration for the chart. This is where you place human-readable strings, such as labels, icons and color tokens for theming.

import { type ChartConfig } from "@/components/ui/chart";

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "#2563eb",
  },
  mobile: {
    label: "Mobile",
    color: "#60a5fa",
  },
} satisfies ChartConfig;

Build your chart

You can now build your chart using Recharts components.

Remember to set a min-height on the ChartContainer component. This is required for the chart be responsive.
"use client";

import { Bar, BarChart } from "recharts";
import { ChartConfig, ChartContainer } from "@/components/ui/chart";

const chartData = [
  { month: "January", desktop: 186, mobile: 80 },
  { month: "February", desktop: 305, mobile: 200 },
  { month: "March", desktop: 237, mobile: 120 },
  { month: "April", desktop: 73, mobile: 190 },
  { month: "May", desktop: 209, mobile: 130 },
  { month: "June", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "#2563eb",
  },
  mobile: {
    label: "Mobile",
    color: "#60a5fa",
  },
} satisfies ChartConfig;

export function Component() {
  return (
    <ChartContainer config={chartConfig} w="full" minH="200px">
      <BarChart accessibilityLayer data={chartData}>
        <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
        <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
      </BarChart>
    </ChartContainer>
  );
}
"use client";

import { Bar, BarChart } from "recharts";
import { ChartConfig, ChartContainer } from "@/components/ui/chart";

const chartData = [
  { month: "January", desktop: 186, mobile: 80 },
  { month: "February", desktop: 305, mobile: 200 },
  { month: "March", desktop: 237, mobile: 120 },
  { month: "April", desktop: 73, mobile: 190 },
  { month: "May", desktop: 209, mobile: 130 },
  { month: "June", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "#2563eb",
  },
  mobile: {
    label: "Mobile",
    color: "#60a5fa",
  },
} satisfies ChartConfig;

export default function Chart1() {
  return (
    <ChartContainer config={chartConfig} css={{ w: "full", minH: "200px" }}>
      <BarChart accessibilityLayer data={chartData}>
        <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
        <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
      </BarChart>
    </ChartContainer>
  );
}

Add a Grid

Let's add a grid to the chart.

Import the CartesianGrid component.

import { Bar, BarChart, CartesianGrid } from "recharts";

Add the CartesianGrid component to your chart.

<ChartContainer config={chartConfig} w="full" minH="200px">
  <BarChart accessibilityLayer data={chartData}>
    <CartesianGrid vertical={false} />
    <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
    <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
  </BarChart>
</ChartContainer>
"use client";

import { Bar, BarChart, CartesianGrid } from "recharts";
import { ChartConfig, ChartContainer } from "@/components/ui/chart";

const chartData = [
  { month: "January", desktop: 186, mobile: 80 },
  { month: "February", desktop: 305, mobile: 200 },
  { month: "March", desktop: 237, mobile: 120 },
  { month: "April", desktop: 73, mobile: 190 },
  { month: "May", desktop: 209, mobile: 130 },
  { month: "June", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "#2563eb",
  },
  mobile: {
    label: "Mobile",
    color: "#60a5fa",
  },
} satisfies ChartConfig;

export default function ChartGrid() {
  return (
    <ChartContainer config={chartConfig} css={{ w: "full", minH: "200px" }}>
      <BarChart accessibilityLayer data={chartData}>
        <CartesianGrid vertical={false} />
        <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
        <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
      </BarChart>
    </ChartContainer>
  );
}

Add an Axis

To add an x-axis to the chart, we'll use the XAxis component.

Import the XAxis component.

import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";

Add the XAxis component to your chart.

<ChartContainer config={chartConfig} w="full" minH="200px">
  <BarChart accessibilityLayer data={chartData}>
    <CartesianGrid vertical={false} />
    <XAxis
      dataKey="month"
      tickLine={false}
      tickMargin={10}
      axisLine={false}
      tickFormatter={(value) => value.slice(0, 3)}
    />
    <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
    <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
  </BarChart>
</ChartContainer>
"use client";

import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";
import { ChartConfig, ChartContainer } from "@/components/ui/chart";

const chartData = [
  { month: "January", desktop: 186, mobile: 80 },
  { month: "February", desktop: 305, mobile: 200 },
  { month: "March", desktop: 237, mobile: 120 },
  { month: "April", desktop: 73, mobile: 190 },
  { month: "May", desktop: 209, mobile: 130 },
  { month: "June", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "#2563eb",
  },
  mobile: {
    label: "Mobile",
    color: "#60a5fa",
  },
} satisfies ChartConfig;

export default function ChartXAxis() {
  return (
    <ChartContainer config={chartConfig} css={{ w: "fuull", minH: "200px" }}>
      <BarChart accessibilityLayer data={chartData}>
        <CartesianGrid vertical={false} />
        <XAxis
          dataKey="month"
          tickLine={false}
          tickMargin={10}
          axisLine={false}
          tickFormatter={(value) => value.slice(0, 3)}
        />
        <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
        <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
      </BarChart>
    </ChartContainer>
  );
}

Add a Tooltip

So far we've only used components from Recharts. They look great out of the box thanks to some customization in the chart component.

To add a tooltip, we'll use the custom ChartTooltip and ChartTooltipContent components from chart.

Import the ChartTooltip and ChartTooltipContent components.

import { ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";

Add the components to you chart.

<ChartContainer config={chartConfig} w="full" minH="200px">
  <BarChart accessibilityLayer data={chartData}>
    <CartesianGrid vertical={false} />
    <XAxis
      dataKey="month"
      tickLine={false}
      tickMargin={10}
      axisLine={false}
      tickFormatter={(value) => value.slice(0, 3)}
    />
    <ChartTooltip content={<ChartTooltipContent />} />
    <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
    <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
  </BarChart>
</ChartContainer>
"use client";

import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";
import {
  ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart";

const chartData = [
  { month: "January", desktop: 186, mobile: 80 },
  { month: "February", desktop: 305, mobile: 200 },
  { month: "March", desktop: 237, mobile: 120 },
  { month: "April", desktop: 73, mobile: 190 },
  { month: "May", desktop: 209, mobile: 130 },
  { month: "June", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "#2563eb",
  },
  mobile: {
    label: "Mobile",
    color: "#60a5fa",
  },
} satisfies ChartConfig;

export default function ChartWithTooltip() {
  return (
    <ChartContainer config={chartConfig} css={{ w: "full", minH: "200px" }}>
      <BarChart accessibilityLayer data={chartData}>
        <CartesianGrid vertical={false} />
        <XAxis
          dataKey="month"
          tickLine={false}
          tickMargin={10}
          axisLine={false}
          tickFormatter={(value) => value.slice(0, 3)}
        />
        <ChartTooltip content={<ChartTooltipContent />} />
        <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
        <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
      </BarChart>
    </ChartContainer>
  );
}

Add a Legend

We'll do the same for the legend. We'll use the ChartLegend and ChartLegendContent components from chart.

Import the ChartLegend and ChartLegendContent components.

import { ChartLegend, ChartLegendContent } from "@/components/ui/chart";

Add the components to your chart.

<ChartContainer config={chartConfig} w="full" minH="200px">
  <BarChart accessibilityLayer data={chartData}>
    <CartesianGrid vertical={false} />
    <XAxis
      dataKey="month"
      tickLine={false}
      tickMargin={10}
      axisLine={false}
      tickFormatter={(value) => value.slice(0, 3)}
    />
    <ChartTooltip content={<ChartTooltipContent />} />
    <ChartLegend content={<ChartLegendContent />} />
    <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
    <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
  </BarChart>
</ChartContainer>
"use client";

import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";
import {
  ChartConfig,
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart";

const chartData = [
  { month: "January", desktop: 186, mobile: 80 },
  { month: "February", desktop: 305, mobile: 200 },
  { month: "March", desktop: 237, mobile: 120 },
  { month: "April", desktop: 73, mobile: 190 },
  { month: "May", desktop: 209, mobile: 130 },
  { month: "June", desktop: 214, mobile: 140 },
];

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "#2563eb",
  },
  mobile: {
    label: "Mobile",
    color: "#60a5fa",
  },
} satisfies ChartConfig;

export default function ChartWithLegend() {
  return (
    <ChartContainer config={chartConfig} css={{ w: "full", minH: "200px" }}>
      <BarChart accessibilityLayer data={chartData}>
        <CartesianGrid vertical={false} />
        <XAxis
          dataKey="month"
          tickLine={false}
          tickMargin={10}
          axisLine={false}
          tickFormatter={(value) => value.slice(0, 3)}
        />
        <ChartTooltip content={<ChartTooltipContent />} />
        <ChartLegend content={<ChartLegendContent />} />
        <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
        <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
      </BarChart>
    </ChartContainer>
  );
}

Done. You've built your first chart! What's next?

Chart Config

The chart config is where you define the labels, icons and colors for a chart.

It is intentionally decoupled from chart data.

This allows you to share config and color tokens between charts. It can also works independently for cases where your data or color tokens live remotely or in a different format.

import { LuMonitor } from "react-icons/lu";
import { type ChartConfig } from "@/components/ui/chart";

const chartConfig = {
  desktop: {
    label: "Desktop",
    icon: LuMonitor,
    // A color like 'hsl(220, 98%, 61%)' or 'var(--color-name)'
    color: "#2563eb",
    // OR a theme object with 'light' and 'dark' keys
    theme: {
      light: "#2563eb",
      dark: "#dc2626",
    },
  },
} satisfies ChartConfig;

Theming

Charts has built-in support for theming. You can use Panda CSS semantic tokens (recommended) or color values in any color format, such as hex, hsl or oklch.

Semantic Tokens

Define your semantic tokens.

export const semanticTokens = defineSemanticTokens({
  colors: {
    chart: {
      1: { value: { base: "hsl(12 76% 61%)", _dark: "hsl(220 70% 50%)" } },
      2: { value: { base: "hsl(173 58% 39%)", _dark: "hsl(160 60% 45%)" } },
      3: { value: { base: "hsl(197 37% 24%)", _dark: "hsl(30 80% 55%)" } },
      4: { value: { base: "hsl(43 74% 66%)", _dark: "hsl(280 65% 60%)" } },
      5: { value: { base: "hsl(27 87% 67%)", _dark: "hsl(340 75% 55%)" } },
    },
  },
});

Add the color to your chartConfig.

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "var(--colors-chart-1)",
  },
  mobile: {
    label: "Mobile",
    color: "var(--colors-chart-2)",
  },
} satisfies ChartConfig;
--colors-[value] is semantic tokens prefix used by Panda CSS.

hex, hsl or oklch

You can also define your colors directly in the chart config. Use the color format you prefer.

const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "#2563eb",
  },
} satisfies ChartConfig;

Using Colors

To use the theme colors in your chart, reference the colors using the format var(--color-KEY).

Components

<Bar dataKey="desktop" fill="var(--color-desktop)" />

Chart Data

const chartData = [
  { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" },
  { browser: "safari", visitors: 200, fill: "var(--color-safari)" },
];

Tooltip

A chart tooltip contains a label, name, indicator and value. You can use a combination of these to customize your tooltip.

Label
Page Views
Desktop
186
Mobile
80
Name
Chrome
1,286
Firefox
1,000
Page Views
Desktop
12,486
Indicator
Chrome
1,286
"use client";

import * as React from "react";
import { styled } from "styled-system/jsx";

export default function ChartTooltip() {
  return (
    <styled.div
      css={{
        display: "grid",
        aspectRatio: "16 / 9",
        w: "full",
        maxW: "md",
        justifyContent: "center",
        color: "fg",
        md: {
          gridTemplateColumns: "repeat(2, 1fr)",
        },
        "& > div": {
          pos: "relative",
          display: "flex",
          w: "224px",
          h: "137px",
          alignItems: "center",
          justifyContent: "center",
          p: 4,
        },
      }}
    >
      <div>
        <styled.div
          css={{
            pos: "absolute",
            top: "45px",
            left: "-45px",
            zIndex: 10,
            textStyle: "sm",
            fontWeight: "medium",
          }}
        >
          Label
        </styled.div>
        <styled.svg
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 193 40"
          width="50"
          height="12"
          fill="none"
          css={{
            pos: "absolute",
            left: "-5px",
            top: "50px",
            zIndex: "10",
          }}
        >
          <g clipPath="url(#a)">
            <path
              fill="currentColor"
              d="M173.928 21.13C115.811 44.938 58.751 45.773 0 26.141c4.227-4.386 7.82-2.715 10.567-1.88 21.133 5.64 42.9 6.266 64.457 7.101 31.066 1.253 60.441-5.848 89.183-17.335 1.268-.418 2.325-1.253 4.861-2.924-14.582-2.924-29.165 2.089-41.845-3.76.212-.835.212-1.879.423-2.714 9.51-.627 19.231-1.253 28.742-2.089 9.51-.835 18.808-1.88 28.318-2.506 6.974-.418 9.933 2.924 7.397 9.19-3.17 8.145-7.608 15.664-11.623 23.391-.423.836-1.057 1.88-1.902 2.298-2.325.835-4.65 1.044-7.186 1.67-.422-2.088-1.479-4.386-1.268-6.265.423-2.506 1.902-4.595 3.804-9.19Z"
            />
          </g>
          <defs>
            <clipPath id="a">
              <path fill="currentColor" d="M0 0h193v40H0z" />
            </clipPath>
          </defs>
        </styled.svg>
        <TooltipDemo
          label="Page Views"
          payload={[
            { name: "Desktop", value: 186, fill: "var(--colors-chart-1)" },
            { name: "Mobile", value: 80, fill: "var(--colors-chart-2)" },
          ]}
          css={{ w: "8rem" }}
        />
      </div>
      <styled.div css={{ alignItems: "flex-end" }}>
        <styled.div
          css={{
            pos: "absolute",
            top: "0px",
            left: "122px",
            zIndex: 10,
            textStyle: "sm",
            fontWeight: "medium",
          }}
        >
          Name
        </styled.div>
        <styled.svg
          xmlns="http://www.w3.org/2000/svg"
          width="35"
          height="42"
          fill="none"
          viewBox="0 0 122 148"
          css={{
            pos: "absolute",
            top: "10px",
            left: "85px",
            zIndex: 10,
            transform: "scaleX(-1)",
          }}
        >
          <g clipPath="url(#ab)">
            <path
              fill="currentColor"
              d="M0 2.65c6.15-4.024 12.299-2.753 17.812-.847a115.56 115.56 0 0 1 21.84 10.59C70.4 32.727 88.849 61.744 96.483 97.54c1.908 9.108 2.544 18.639 3.817 29.017 8.481-4.871 12.934-14.402 21.416-19.909 1.061 4.236-1.06 6.989-2.756 9.319-6.998 9.531-14.207 19.062-21.63 28.382-3.604 4.448-6.36 4.871-10.177 1.059-8.058-7.837-12.935-17.368-14.42-28.382 0-.424.636-1.059 1.485-2.118 9.118 2.33 6.997 13.979 14.843 18.215 3.393-14.614.848-28.593-2.969-42.149-4.029-14.19-9.33-27.746-17.812-39.82-8.27-11.86-18.66-21.392-30.11-30.287C26.93 11.758 14.207 6.039 0 2.65Z"
            />
          </g>
          <defs>
            <clipPath id="ab">
              <path fill="currentColor" d="M0 0h122v148H0z" />
            </clipPath>
          </defs>
        </styled.svg>
        <TooltipDemo
          label="Browser"
          hideLabel
          payload={[
            { name: "Chrome", value: 1286, fill: "var(--colors-chart-3)" },
            { name: "Firefox", value: 1000, fill: "var(--colors-chart-4)" },
          ]}
          indicator="dashed"
          css={{ w: "8rem" }}
        />
      </styled.div>
      <styled.div css={{ display: "none", md: { display: "flex" } }}>
        <TooltipDemo
          label="Page Views"
          payload={[{ name: "Desktop", value: 12486, fill: "var(--colors-chart-3)" }]}
          css={{ w: "9rem" }}
          indicator="line"
        />
      </styled.div>
      <styled.div css={{ alignItems: "flex-start", justifyContent: "flex-start" }}>
        <styled.div
          css={{
            pos: "absolute",
            top: "100px",
            left: "80px",
            zIndex: 10,
            textStyle: "sm",
            fontWeight: "medium",
          }}
        >
          Indicator
        </styled.div>
        <TooltipDemo
          label="Browser"
          hideLabel
          payload={[{ name: "Chrome", value: 1286, fill: "var(--colors-chart-1)" }]}
          indicator="dot"
          css={{ w: "8rem" }}
        />
        <styled.svg
          xmlns="http://www.w3.org/2000/svg"
          width="15"
          height="34"
          fill="none"
          viewBox="0 0 75 175"
          css={{
            pos: "absolute",
            top: "80px",
            left: "62px",
            zIndex: "10",
            transform: "rotate(-40deg)",
          }}
        >
          <g clipPath="url(#abc)">
            <path
              fill="currentColor"
              d="M20.187 175c-4.439-2.109-7.186-2.531-8.032-4.008-3.17-5.484-6.763-10.968-8.454-17.084-5.073-16.242-4.439-32.694-1.057-49.146 5.707-28.053 18.388-52.942 34.24-76.565 1.692-2.531 3.171-5.063 4.862-7.805 0-.21-.211-.632-.634-1.265-4.65 1.265-9.511 2.53-14.161 3.585-2.537.422-5.496.422-8.032-.421-1.48-.422-3.593-2.742-3.593-4.219 0-1.898 1.48-4.218 2.747-5.906 1.057-1.054 2.96-1.265 4.65-1.687C35.406 7.315 48.088 3.729 60.98.776c10.99-2.53 14.584 1.055 13.95 11.812-.634 11.18-.846 22.358-1.268 33.326-.212 3.375-.846 6.96-1.268 10.757-8.878-4.007-8.878-4.007-12.048-38.177C47.03 33.259 38.153 49.289 29.91 65.741 21.667 82.193 16.17 99.49 13.212 117.84c-2.959 18.984.634 36.912 6.975 57.161Z"
            />
          </g>
          <defs>
            <clipPath id="abc">
              <path fill="currentColor" d="M0 0h75v175H0z" />
            </clipPath>
          </defs>
        </styled.svg>
      </styled.div>
    </styled.div>
  );
}

function TooltipDemo({
  indicator = "dot",
  label,
  payload,
  hideLabel,
  hideIndicator,
  css,
}: {
  label: string;
  hideLabel?: boolean;
  hideIndicator?: boolean;
  indicator?: "line" | "dot" | "dashed";
  payload: {
    name: string;
    value: number;
    fill: string;
  }[];
  nameKey?: string;
  labelKey?: string;
} & React.ComponentProps<typeof styled.div>) {
  const tooltipLabel = hideLabel ? null : (
    <styled.div css={{ fontWeight: "medium" }}>{label}</styled.div>
  );

  if (!payload?.length) {
    return null;
  }

  const nestLabel = payload.length === 1 && indicator !== "dot";

  return (
    <styled.div
      css={{
        display: "grid",
        minW: "8rem",
        alignItems: "flex-start",
        gap: 1.5,
        rounded: "lg",
        borderWidth: "1px",
        borderColor: "border/50",
        bg: "bg",
        px: "2.5",
        py: "1.5",
        textStyle: "xs",
        shadow: "xl",
        transition: "all",
        transitionTimingFunction: "ease-in-out",
        _hover: {
          transform: "translateY(-0.25rem)",
        },
        ...css,
      }}
    >
      {!nestLabel ? tooltipLabel : null}
      <styled.div css={{ display: "grid", gap: "1.5" }}>
        {payload.map((item, index) => {
          const indicatorColor = item.fill;

          return (
            <styled.div
              key={index}
              css={{
                display: "flex",
                w: "full",
                alignItems: indicator === "dot" ? "center" : "stretch",
                gap: "2",
                "& > svg": {
                  w: "2.5",
                  h: "2.5",
                  color: "muted.fg",
                },
              }}
            >
              <>
                {!hideIndicator && (
                  <styled.div
                    data-indicator={indicator}
                    data-nest-label={nestLabel}
                    css={{
                      flexShrink: "0",
                      rounded: "2px",
                      borderColor: "var(--color-border)",
                      bg: "var(--color-bg)",
                      "&[data-indicator=dot]": { w: "2.5", h: "2.5" },
                      "&[data-indicator=line]": { w: "1" },
                      "&[data-indicator=dashed]": {
                        w: "0",
                        borderWidth: "1.5px",
                        borderStyle: "dashed",
                        bg: "transparent",
                        "&[data-nest-label=true]": { my: "0.5" },
                      },
                    }}
                    style={
                      {
                        "--color-bg": indicatorColor,
                        "--color-border": indicatorColor,
                      } as React.CSSProperties
                    }
                  />
                )}
                <styled.div
                  css={{
                    display: "flex",
                    flex: "1",
                    alignItems: nestLabel ? "flex-end" : "center",
                    justifyContent: "space-between",
                    lineHeight: "none",
                  }}
                >
                  <styled.div css={{ display: "grid", gap: "1.5" }}>
                    {nestLabel ? tooltipLabel : null}
                    <styled.span css={{ color: "muted.fg" }}>{item.name}</styled.span>
                  </styled.div>
                  <styled.span
                    css={{
                      color: "fg",
                      fontFamily: "mono",
                      fontWeight: "medium",
                      fontVariantNumeric: "tabular-nums",
                    }}
                  >
                    {item.value.toLocaleString()}
                  </styled.span>
                </styled.div>
              </>
            </styled.div>
          );
        })}
      </styled.div>
    </styled.div>
  );
}

You can turn on/off any of these using the hideLabel, hideIndicator props and customize the indicator style using the indicator prop.

Use labelKey and nameKey to use a custom key for the tooltip label and name.

Chart comes with the <ChartTooltip> and <ChartTooltipContent> components. You can use these two components to add custom tooltips to your chart.

import { ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
<ChartTooltip content={<ChartTooltipContent />} />

Props

Use the following props to customize the tooltip.

PropTypeDescription
labelKeystringThe config or data key to use for the label.
nameKeystringThe config or data key to use for the name.
indicatordot line or dashedThe indicator style for the tooltip.
hideLabelbooleanWhether to hide the label.
hideIndicatorbooleanWhether to hide the indicator.

Colors

Colors are automatically referenced from the chart config.

Custom

To use a custom key for tooltip label and names, use the labelKey and nameKey props.

const chartData = [
  { browser: "chrome", visitors: 187, fill: "var(--color-chrome)" },
  { browser: "safari", visitors: 200, fill: "var(--color-safari)" },
];

const chartConfig = {
  visitors: {
    label: "Total Visitors",
  },
  chrome: {
    label: "Chrome",
    color: "var(--chart-1)",
  },
  safari: {
    label: "Safari",
    color: "var(--chart-2)",
  },
} satisfies ChartConfig;
<ChartTooltip content={<ChartTooltipContent labelKey="visitors" nameKey="browser" />} />

This will use Total Visitors for label and Chrome and Safari for the tooltip names.

Legend

You can use the custom <ChartLegend> and <ChartLegendContent> components to add a legend to your chart.

import { ChartLegend, ChartLegendContent } from "@/components/ui/chart";
<ChartLegend content={<ChartLegendContent />} />

Colors

Colors are automatically referenced from the chart config.

Custom

To use a custom key for legend names, use the nameKey prop.

const chartData = [
  { browser: "chrome", visitors: 187, fill: "var(--color-chrome)" },
  { browser: "safari", visitors: 200, fill: "var(--color-safari)" },
];

const chartConfig = {
  chrome: {
    label: "Chrome",
    color: "var(--chart-1)",
  },
  safari: {
    label: "Safari",
    color: "var(--chart-2)",
  },
} satisfies ChartConfig;
<ChartLegend content={<ChartLegendContent nameKey="browser" />} />

This will use Chrome and Safari for the legend names.

Accessibility

You can turn on the accessibilityLayer prop to add an accessible layer to your chart.

This prop adds keyboard access and screen reader support to your charts.

<LineChart accessibilityLayer />