# Chart

> Recharts charts with themed colors, a tooltip and legend that read labels from one config, and keyboard navigation with a visible focus ring.

Docs: https://hextaui.com/docs/chart
Markdown: https://hextaui.com/docs/chart.md

```tsx title="components/examples/chart/demo.tsx"
"use client"

import * as React from "react"
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"

import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

const chartData = Array.from({ length: 90 }, (_, index) => {
  const date = new Date(Date.UTC(2026, 3, 1 + index))
  const wave = Math.sin(index / 6) * 60 + Math.sin(index / 2.3) * 25
  return {
    date: date.toISOString().slice(0, 10),
    desktop: Math.round(260 + wave + index * 1.6),
    mobile: Math.round(180 - wave * 0.6 + index * 1.1),
  }
})

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

const ranges = [
  { value: "90", label: "90d", name: "Last 90 days" },
  { value: "30", label: "30d", name: "Last 30 days" },
  { value: "7", label: "7d", name: "Last 7 days" },
]

const dateFormat = new Intl.DateTimeFormat("en-US", {
  month: "short",
  day: "numeric",
  timeZone: "UTC",
})

function formatDate(value: unknown) {
  return dateFormat.format(new Date(`${value}T00:00:00Z`))
}

export function ChartDemo() {
  const [range, setRange] = React.useState("90")
  const data = chartData.slice(-Number(range))

  return (
    <Card className="w-full">
      <CardHeader>
        <CardTitle>Visitors</CardTitle>
        <CardDescription>Desktop and mobile visits per day</CardDescription>
        <CardAction>
          <ToggleGroup
            variant="outline"
            size="sm"
            aria-label="Time range"
            value={[range]}
            onValueChange={(value) => {
              if (value.length > 0) {
                setRange(value[0])
              }
            }}
          >
            {ranges.map((item) => (
              <ToggleGroupItem
                key={item.value}
                value={item.value}
                aria-label={item.name}
              >
                {item.label}
              </ToggleGroupItem>
            ))}
          </ToggleGroup>
        </CardAction>
      </CardHeader>
      <CardContent>
        <ChartContainer
          aria-label="Area chart of desktop and mobile visitors per day"
          config={chartConfig}
          className="h-64 w-full"
        >
          <AreaChart data={data} margin={{ left: 12, right: 12 }}>
            <defs>
              <linearGradient id="fill-desktop" x1="0" y1="0" x2="0" y2="1">
                <stop
                  offset="5%"
                  stopColor="var(--color-desktop)"
                  stopOpacity={0.3}
                />
                <stop
                  offset="95%"
                  stopColor="var(--color-desktop)"
                  stopOpacity={0.02}
                />
              </linearGradient>
              <linearGradient id="fill-mobile" x1="0" y1="0" x2="0" y2="1">
                <stop
                  offset="5%"
                  stopColor="var(--color-mobile)"
                  stopOpacity={0.3}
                />
                <stop
                  offset="95%"
                  stopColor="var(--color-mobile)"
                  stopOpacity={0.02}
                />
              </linearGradient>
            </defs>
            <CartesianGrid vertical={false} />
            <XAxis
              dataKey="date"
              tickLine={false}
              axisLine={false}
              tickMargin={8}
              minTickGap={32}
              tickFormatter={formatDate}
            />
            <ChartTooltip
              cursor={false}
              content={
                <ChartTooltipContent
                  indicator="dot"
                  labelFormatter={formatDate}
                />
              }
            />
            <Area
              dataKey="mobile"
              type="natural"
              fill="url(#fill-mobile)"
              stroke="var(--color-mobile)"
              strokeWidth={2}
              stackId="a"
            />
            <Area
              dataKey="desktop"
              type="natural"
              fill="url(#fill-desktop)"
              stroke="var(--color-desktop)"
              strokeWidth={2}
              stackId="a"
            />
            <ChartLegend content={<ChartLegendContent />} />
          </AreaChart>
        </ChartContainer>
      </CardContent>
    </Card>
  )
}
```

## Installation

### CLI

```bash
npx shadcn@latest add https://hextaui.com/r/chart.json
```

This adds the component, the HextaUI theme tokens and any HextaUI components it depends on.

### Manual

Add the HextaUI theme tokens (https://hextaui.com/docs/installation#theme) to your global CSS if you haven't yet, then install the dependencies.

```bash
pnpm add recharts cn
```

Copy and paste the following code into your project.

```tsx title="components/ui/chart.tsx"
"use client"

import * as React from "react"
import { cn } from "cn"
import * as RechartsPrimitive from "recharts"
import type {
  DefaultLegendContentProps,
  TooltipContentProps,
  TooltipProps,
  TooltipValueType,
} from "recharts"

const THEMES = { light: "", dark: ".dark" } as const

const INITIAL_DIMENSION = { width: 320, height: 200 } as const

type TooltipNameType = number | string

type ChartConfig = Record<
  string,
  {
    label?: React.ReactNode
    icon?: React.ComponentType
  } & (
    | { color?: string; theme?: never }
    | { color?: never; theme: Record<keyof typeof THEMES, string> }
  )
>

type ChartContextProps = {
  config: ChartConfig
}

const ChartContext = React.createContext<ChartContextProps | null>(null)

function useChart() {
  const context = React.useContext(ChartContext)

  if (!context) {
    throw new Error("useChart must be used within <ChartContainer>.")
  }

  return context
}

function toCssIdent(value: string) {
  return value.replace(/[^\w-]/g, "-")
}

function toCssValue(value: string) {
  return value.replace(/[;{}<>]/g, "").trim()
}

function ChartContainer({
  id,
  className,
  children,
  config,
  initialDimension = INITIAL_DIMENSION,
  ...props
}: React.ComponentProps<"div"> & {
  config: ChartConfig
  children: React.ComponentProps<
    typeof RechartsPrimitive.ResponsiveContainer
  >["children"]
  initialDimension?: {
    width: number
    height: number
  }
}) {
  const uniqueId = React.useId()
  const chartId = `chart-${toCssIdent(id ?? uniqueId)}`
  const context = React.useMemo(() => ({ config }), [config])

  return (
    <ChartContext.Provider value={context}>
      <div
        role="figure"
        data-slot="chart"
        data-chart={chartId}
        className={cn(
          "flex aspect-video min-w-0 justify-center text-xs tabular-nums forced-color-adjust-none [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-angle-axis-tick_text]:fill-muted-foreground [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-foreground/5 [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:rounded-md [&_.recharts-surface]:outline-hidden [&_.recharts-surface]:transition-shadow [&_.recharts-surface]:duration-150 [&_.recharts-surface]:[direction:ltr] [&_.recharts-surface_*]:outline-hidden [&_.recharts-surface:focus-visible]:ring-3 [&_.recharts-surface:focus-visible]:ring-focus-ring [&_.recharts-tooltip-wrapper[data-chart-snap]]:transition-none! [&_.recharts-wrapper]:touch-pan-y",
          className
        )}
        {...props}
      >
        <ChartStyle id={chartId} config={config} />
        <RechartsPrimitive.ResponsiveContainer
          initialDimension={initialDimension}
        >
          {children}
        </RechartsPrimitive.ResponsiveContainer>
      </div>
    </ChartContext.Provider>
  )
}

function ChartStyle({ id, config }: { id: string; config: ChartConfig }) {
  const colorConfig = Object.entries(config).filter(
    ([, itemConfig]) => itemConfig.theme ?? itemConfig.color
  )

  if (!colorConfig.length) {
    return null
  }

  const css = Object.entries(THEMES)
    .map(([theme, prefix]) => {
      const declarations = colorConfig
        .map(([key, itemConfig]) => {
          const color =
            itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??
            itemConfig.color
          const value = color ? toCssValue(color) : ""
          return value ? `  --color-${toCssIdent(key)}: ${value};` : null
        })
        .filter(Boolean)
        .join("\n")
      return `${prefix} [data-chart="${toCssIdent(id)}"] {\n${declarations}\n}`
    })
    .join("\n")

  return <style dangerouslySetInnerHTML={{ __html: css }} />
}

function ChartTooltip({
  animationDuration = 200,
  animationEasing = "ease-out",
  ...props
}: TooltipProps<TooltipValueType, TooltipNameType>) {
  return (
    <RechartsPrimitive.Tooltip
      animationDuration={animationDuration}
      animationEasing={animationEasing}
      {...props}
    />
  )
}

function useNumberFormat(locale: Intl.LocalesArgument) {
  return React.useMemo(() => {
    try {
      return new Intl.NumberFormat(locale)
    } catch {
      return new Intl.NumberFormat("en-US")
    }
  }, [locale])
}

function formatValue(value: unknown, format: Intl.NumberFormat): string {
  if (typeof value === "number") {
    return Number.isFinite(value) ? format.format(value) : String(value)
  }
  if (Array.isArray(value)) {
    return value.map((item) => formatValue(item, format)).join(" – ")
  }
  return value == null ? "" : String(value)
}

function snapTooltipWrapper(node: HTMLDivElement | null) {
  const wrapper = node?.parentElement
  if (
    !wrapper?.classList.contains("recharts-tooltip-wrapper") ||
    typeof MutationObserver === "undefined"
  ) {
    return undefined
  }

  let hidden = wrapper.style.visibility === "hidden"
  let from: string | null = null
  let frame = 0
  let timeout: ReturnType<typeof setTimeout> | undefined

  const release = () => {
    from = null
    clearTimeout(timeout)
    cancelAnimationFrame(frame)
    frame = requestAnimationFrame(() => {
      frame = requestAnimationFrame(() => {
        wrapper.removeAttribute("data-chart-snap")
      })
    })
  }

  const snap = () => {
    cancelAnimationFrame(frame)
    clearTimeout(timeout)
    wrapper.setAttribute("data-chart-snap", "")
    from = wrapper.style.transform
    timeout = setTimeout(release, 100)
  }

  if (!hidden) {
    snap()
  }

  const observer = new MutationObserver(() => {
    const nowHidden = wrapper.style.visibility === "hidden"
    if (hidden && !nowHidden) {
      snap()
    } else if (from !== null && wrapper.style.transform !== from) {
      release()
    }
    hidden = nowHidden
  })
  observer.observe(wrapper, { attributes: true, attributeFilter: ["style"] })

  return () => {
    observer.disconnect()
    cancelAnimationFrame(frame)
    clearTimeout(timeout)
    wrapper.removeAttribute("data-chart-snap")
  }
}

type ChartTooltipPayload = TooltipContentProps<
  TooltipValueType,
  TooltipNameType
>["payload"]

type ChartTooltipContentProps = {
  active?: boolean
  payload?: ChartTooltipPayload
  label?: React.ReactNode
  labelFormatter?: TooltipProps<
    TooltipValueType,
    TooltipNameType
  >["labelFormatter"]
  formatter?: TooltipProps<TooltipValueType, TooltipNameType>["formatter"]
  valueFormatter?: (
    value: TooltipValueType | undefined,
    name: TooltipNameType | undefined,
    item: ChartTooltipPayload[number]
  ) => React.ReactNode
  className?: string
  labelClassName?: string
  color?: string
  indicator?: "line" | "dot" | "dashed"
  hideLabel?: boolean
  hideIndicator?: boolean
  nameKey?: string
  labelKey?: string
  locale?: Intl.LocalesArgument
}

function ChartTooltipContent({
  active,
  payload,
  className,
  indicator = "dot",
  hideLabel = false,
  hideIndicator = false,
  label,
  labelFormatter,
  labelClassName,
  formatter,
  valueFormatter,
  color,
  nameKey,
  labelKey,
  locale = "en-US",
}: ChartTooltipContentProps) {
  const { config } = useChart()
  const format = useNumberFormat(locale)

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

  const items = payload.filter((item) => item.type !== "none")

  const tooltipLabel = (() => {
    if (hideLabel) {
      return null
    }
    const [item] = payload
    const key = `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}`
    const itemConfig = getPayloadConfigFromPayload(config, item, key)
    const value =
      !labelKey && (typeof label === "string" || typeof label === "number")
        ? ((Object.hasOwn(config, label) ? config[label].label : undefined) ??
          label)
        : itemConfig?.label

    if (labelFormatter) {
      const formatted = labelFormatter(value, payload)
      return formatted == null || formatted === "" ? null : (
        <div
          data-slot="chart-tooltip-label"
          className={cn("font-medium text-pretty", labelClassName)}
        >
          {formatted}
        </div>
      )
    }

    if (value == null || value === "") {
      return null
    }

    return (
      <div
        data-slot="chart-tooltip-label"
        className={cn("font-medium text-pretty", labelClassName)}
      >
        {value}
      </div>
    )
  })()

  const nestLabel = items.length === 1 && indicator !== "dot"

  return (
    <div
      ref={snapTooltipWrapper}
      data-slot="chart-tooltip"
      className={cn(
        "grid max-w-[min(18rem,calc(100vw-2rem))] min-w-32 animate-in items-start gap-1.5 rounded-lg bg-popover px-2.5 py-1.5 text-xs wrap-anywhere text-popover-foreground shadow-md ring-(length:--hairline) ring-foreground/10 ease-out-quint animation-duration-150 fade-in-0 motion-safe:zoom-in-95 forced-colors:border",
        className
      )}
    >
      {!nestLabel ? tooltipLabel : null}
      <div className="grid gap-1.5">
        {items.map((item, index) => {
          const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`
          const itemConfig = getPayloadConfigFromPayload(config, item, key)
          const indicatorColor = color ?? item.payload?.fill ?? item.color
          const value = valueFormatter
            ? valueFormatter(item.value, item.name, item)
            : formatValue(item.value, format)

          return (
            <div
              key={`${item.graphicalItemId ?? ""}-${String(item.dataKey ?? index)}-${index}`}
              data-slot="chart-tooltip-item"
              className={cn(
                "flex w-full flex-wrap items-stretch gap-2 [&>svg]:size-2.5 [&>svg]:shrink-0 [&>svg]:text-muted-foreground",
                indicator === "dot" && "items-center"
              )}
            >
              {formatter && item.value !== undefined && item.name ? (
                formatter(item.value, item.name, item, index, payload)
              ) : (
                <>
                  {itemConfig?.icon ? (
                    <itemConfig.icon />
                  ) : (
                    !hideIndicator && (
                      <div
                        aria-hidden
                        data-slot="chart-tooltip-indicator"
                        data-indicator={indicator}
                        style={
                          {
                            "--chart-indicator": indicatorColor,
                          } as React.CSSProperties
                        }
                        className={cn(
                          "shrink-0 rounded-[calc(var(--radius-sm)*0.4)] border-(--chart-indicator) bg-(--chart-indicator)",
                          indicator === "dot" && "size-2.5",
                          indicator === "line" && "w-1",
                          indicator === "dashed" &&
                            "w-0 border-[1.5px] border-dashed bg-transparent",
                          nestLabel && indicator === "dashed" && "my-0.5"
                        )}
                      />
                    )
                  )}
                  <div
                    className={cn(
                      "flex min-w-0 flex-1 justify-between gap-4 leading-none",
                      nestLabel ? "items-end" : "items-center"
                    )}
                  >
                    <div className="grid min-w-0 gap-1.5">
                      {nestLabel ? tooltipLabel : null}
                      <span className="text-muted-foreground">
                        {itemConfig?.label ?? item.name}
                      </span>
                    </div>
                    {value != null && value !== "" && value !== false && (
                      <span
                        dir="ltr"
                        className="shrink-0 font-medium text-foreground tabular-nums"
                      >
                        {value}
                      </span>
                    )}
                  </div>
                </>
              )}
            </div>
          )
        })}
      </div>
    </div>
  )
}

function ChartLegend({
  itemSorter = null,
  ...props
}: React.ComponentProps<typeof RechartsPrimitive.Legend>) {
  return <RechartsPrimitive.Legend itemSorter={itemSorter} {...props} />
}

function ChartLegendContent({
  className,
  hideIcon = false,
  payload,
  verticalAlign = "bottom",
  nameKey,
}: Pick<DefaultLegendContentProps, "payload" | "verticalAlign"> & {
  className?: string
  hideIcon?: boolean
  nameKey?: string
}) {
  const { config } = useChart()

  const items = payload?.filter((item) => item.type !== "none") ?? []

  if (!items.length) {
    return null
  }

  return (
    <div
      data-slot="chart-legend"
      className={cn(
        "flex flex-wrap items-center justify-center gap-x-4 gap-y-1.5",
        verticalAlign === "top" ? "pb-3" : "pt-3",
        className
      )}
    >
      {items.map((item, index) => {
        const key = `${nameKey ?? item.dataKey ?? "value"}`
        const itemConfig = getPayloadConfigFromPayload(config, item, key)

        return (
          <div
            key={`${String(item.dataKey ?? item.value ?? "")}-${index}`}
            data-slot="chart-legend-item"
            className="flex min-w-0 items-center gap-1.5 [&>svg]:size-3 [&>svg]:shrink-0 [&>svg]:text-muted-foreground"
          >
            {itemConfig?.icon && !hideIcon ? (
              <itemConfig.icon />
            ) : (
              <div
                aria-hidden
                data-slot="chart-legend-indicator"
                style={
                  { "--chart-indicator": item.color } as React.CSSProperties
                }
                className="size-2 shrink-0 rounded-[calc(var(--radius-sm)*0.4)] bg-(--chart-indicator)"
              />
            )}
            <span className="min-w-0 wrap-anywhere">
              {itemConfig?.label ?? item.value}
            </span>
          </div>
        )
      })}
    </div>
  )
}

function getPayloadConfigFromPayload(
  config: ChartConfig,
  payload: unknown,
  key: string
) {
  if (typeof payload !== "object" || payload === null) {
    return undefined
  }

  const payloadPayload =
    "payload" in payload &&
    typeof payload.payload === "object" &&
    payload.payload !== null
      ? payload.payload
      : undefined

  let configLabelKey: string = key

  if (
    key in payload &&
    typeof payload[key as keyof typeof payload] === "string"
  ) {
    configLabelKey = payload[key as keyof typeof payload] as string
  } else if (
    payloadPayload &&
    key in payloadPayload &&
    typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
  ) {
    configLabelKey = payloadPayload[
      key as keyof typeof payloadPayload
    ] as string
  }

  return Object.hasOwn(config, configLabelKey)
    ? config[configLabelKey]
    : Object.hasOwn(config, key)
      ? config[key]
      : undefined
}

export {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartStyle,
  ChartTooltip,
  ChartTooltipContent,
  useChart,
}
export type { ChartConfig, ChartTooltipContentProps }
```

Update the import paths to match your project setup.

## Usage

You build charts with Recharts components and add the parts from `chart` only where you need them. Recharts is not wrapped, so its own docs and upgrade guides apply as they are.

```tsx
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"

import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"
```

A `ChartConfig` maps each data key to a label, an optional icon and a color. It stays separate from the data, so one config can serve several charts.

```tsx
const chartConfig = {
  desktop: {
    label: "Desktop",
    color: "var(--chart-1)",
  },
  mobile: {
    label: "Mobile",
    color: "var(--chart-2)",
  },
} satisfies ChartConfig
```

```tsx
<ChartContainer
  aria-label="Bar chart of desktop and mobile visitors by month"
  config={chartConfig}
  className="min-h-52 w-full"
>
  <BarChart data={chartData}>
    <CartesianGrid vertical={false} />
    <XAxis dataKey="month" tickLine={false} axisLine={false} />
    <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>
```

Give `<ChartContainer />` a height, a `min-h-*` or an `aspect-*` so the chart can measure itself on the first render. Without one it falls back to `aspect-video`.

## Composition

```text
ChartContainer
└── BarChart / LineChart / AreaChart / …
    ├── ChartTooltip
    │   └── ChartTooltipContent
    └── ChartLegend
        └── ChartLegendContent
```

## Theming

Each config key becomes a `--color-KEY` variable scoped to its chart. Point it at a theme token, at any CSS color, or give it separate `light` and `dark` values.

```tsx
const chartConfig = {
  desktop: {
    label: "Desktop",
    icon: IconDeviceDesktop,
    color: "var(--chart-1)",
  },
  mobile: {
    label: "Mobile",
    theme: {
      light: "oklch(0.5 0.17 285)",
      dark: "oklch(0.75 0.14 190)",
    },
  },
} satisfies ChartConfig
```

Use the variable wherever Recharts takes a color: on components, in your data, or in Tailwind classes.

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

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

<LabelList className="fill-(--color-desktop)" />
```

The theme ships five chart tokens, `--chart-1` to `--chart-5`: blue, teal, amber, violet and rose. In this order, neighbouring colors stay distinct with the common forms of color blindness, and each one has at least 3:1 contrast against the light and dark backgrounds. Use them in order and keep the legend or labels on, so color is never the only cue.

Keys that are not valid in a CSS name, such as `Mobile users`, become `--color-Mobile-users`.

## Examples

### Bar chart

A grid, an axis, `<ChartTooltip />` and `<ChartLegend />`. The tooltip and legend read their labels and colors from the config.

```tsx title="components/examples/chart/bar.tsx"
"use client"

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

import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} 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: "var(--chart-1)",
  },
  mobile: {
    label: "Mobile",
    color: "var(--chart-2)",
  },
} satisfies ChartConfig

export function ChartBar() {
  return (
    <ChartContainer
      aria-label="Bar chart of desktop and mobile visitors by month"
      config={chartConfig}
      className="min-h-52 w-full"
    >
      <BarChart 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>
  )
}
```

### Line chart

`indicator="line"` matches the tooltip keys to the lines they describe.

```tsx title="components/examples/chart/line.tsx"
"use client"

import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"

import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

const chartData = [
  { week: "W1", signups: 412, activated: 238 },
  { week: "W2", signups: 468, activated: 301 },
  { week: "W3", signups: 395, activated: 254 },
  { week: "W4", signups: 530, activated: 362 },
  { week: "W5", signups: 588, activated: 417 },
  { week: "W6", signups: 561, activated: 431 },
  { week: "W7", signups: 642, activated: 489 },
]

const chartConfig = {
  signups: {
    label: "Sign-ups",
    color: "var(--chart-1)",
  },
  activated: {
    label: "Activated",
    color: "var(--chart-4)",
  },
} satisfies ChartConfig

export function ChartLine() {
  return (
    <ChartContainer
      aria-label="Line chart of sign-ups and activated accounts"
      config={chartConfig}
      className="h-60 w-full"
    >
      <LineChart data={chartData} margin={{ left: 4, right: 12, top: 8 }}>
        <CartesianGrid vertical={false} />
        <XAxis
          dataKey="week"
          tickLine={false}
          axisLine={false}
          tickMargin={8}
        />
        <YAxis tickLine={false} axisLine={false} tickMargin={8} width={36} />
        <ChartTooltip content={<ChartTooltipContent indicator="line" />} />
        <Line
          dataKey="signups"
          type="monotone"
          stroke="var(--color-signups)"
          strokeWidth={2}
          dot={false}
          activeDot={{ r: 4 }}
        />
        <Line
          dataKey="activated"
          type="monotone"
          stroke="var(--color-activated)"
          strokeWidth={2}
          dot={false}
          activeDot={{ r: 4 }}
        />
        <ChartLegend content={<ChartLegendContent />} />
      </LineChart>
    </ChartContainer>
  )
}
```

### Stacked bars

Bars with the same `stackId` stack. Round only the outer ends, so the stack reads as one bar.

```tsx title="components/examples/chart/stacked.tsx"
"use client"

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

import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

const chartData = [
  { quarter: "Q1", starter: 120, pro: 86, team: 34 },
  { quarter: "Q2", starter: 138, pro: 104, team: 51 },
  { quarter: "Q3", starter: 131, pro: 122, team: 69 },
  { quarter: "Q4", starter: 149, pro: 141, team: 88 },
]

const chartConfig = {
  starter: {
    label: "Starter",
    color: "var(--chart-1)",
  },
  pro: {
    label: "Pro",
    color: "var(--chart-2)",
  },
  team: {
    label: "Team",
    color: "var(--chart-3)",
  },
} satisfies ChartConfig

export function ChartStacked() {
  return (
    <ChartContainer
      aria-label="Stacked bar chart of Starter, Pro and Team plans"
      config={chartConfig}
      className="h-60 w-full max-w-md"
    >
      <BarChart data={chartData}>
        <CartesianGrid vertical={false} />
        <XAxis
          dataKey="quarter"
          tickLine={false}
          axisLine={false}
          tickMargin={10}
        />
        <ChartTooltip content={<ChartTooltipContent />} />
        <ChartLegend content={<ChartLegendContent />} />
        <Bar
          dataKey="starter"
          stackId="plans"
          fill="var(--color-starter)"
          radius={[0, 0, 4, 4]}
        />
        <Bar dataKey="pro" stackId="plans" fill="var(--color-pro)" />
        <Bar
          dataKey="team"
          stackId="plans"
          fill="var(--color-team)"
          radius={[4, 4, 0, 0]}
        />
      </BarChart>
    </ChartContainer>
  )
}
```

### Horizontal bars

Set `layout="vertical"` on the chart and swap the axis types. `<LabelList />` prints each value at the end of its bar.

```tsx title="components/examples/chart/horizontal.tsx"
"use client"

import { Bar, BarChart, LabelList, XAxis, YAxis } from "recharts"

import {
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

const chartData = [
  { page: "Pricing", views: 4821 },
  { page: "Docs", views: 3902 },
  { page: "Changelog", views: 2214 },
  { page: "Blog", views: 1736 },
  { page: "Careers", views: 640 },
]

const chartConfig = {
  views: {
    label: "Views",
    color: "var(--chart-1)",
  },
} satisfies ChartConfig

export function ChartHorizontal() {
  return (
    <ChartContainer
      aria-label="Horizontal bar chart of page views"
      config={chartConfig}
      className="h-56 w-full max-w-md"
    >
      <BarChart data={chartData} layout="vertical" margin={{ right: 48 }}>
        <XAxis type="number" dataKey="views" hide />
        <YAxis
          type="category"
          dataKey="page"
          tickLine={false}
          axisLine={false}
          tickMargin={8}
          width={76}
        />
        <ChartTooltip
          cursor={false}
          content={<ChartTooltipContent hideLabel />}
        />
        <Bar dataKey="views" fill="var(--color-views)" radius={4} barSize={20}>
          <LabelList
            dataKey="views"
            position="right"
            offset={8}
            className="fill-foreground"
            formatter={(value) => Number(value).toLocaleString("en-US")}
          />
        </Bar>
      </BarChart>
    </ChartContainer>
  )
}
```

### Donut

Each slice takes its color from `fill` in the data. `nameKey` on the tooltip and legend looks up the label for each slice in the config.

```tsx title="components/examples/chart/pie.tsx"
"use client"

import { Label, Pie, PieChart } from "recharts"

import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

const chartData = [
  { browser: "chrome", visitors: 275, fill: "var(--color-chrome)" },
  { browser: "safari", visitors: 200, fill: "var(--color-safari)" },
  { browser: "firefox", visitors: 187, fill: "var(--color-firefox)" },
  { browser: "edge", visitors: 173, fill: "var(--color-edge)" },
  { browser: "other", visitors: 90, fill: "var(--color-other)" },
]

const chartConfig = {
  visitors: {
    label: "Visitors",
  },
  chrome: {
    label: "Chrome",
    color: "var(--chart-1)",
  },
  safari: {
    label: "Safari",
    color: "var(--chart-2)",
  },
  firefox: {
    label: "Firefox",
    color: "var(--chart-3)",
  },
  edge: {
    label: "Edge",
    color: "var(--chart-4)",
  },
  other: {
    label: "Other",
    color: "var(--chart-5)",
  },
} satisfies ChartConfig

const total = chartData.reduce((sum, item) => sum + item.visitors, 0)

export function ChartPie() {
  return (
    <ChartContainer
      aria-label="Pie chart of visitors by browser"
      config={chartConfig}
      className="aspect-square h-72"
    >
      <PieChart>
        <ChartTooltip
          cursor={false}
          content={<ChartTooltipContent nameKey="browser" hideLabel />}
        />
        <Pie
          data={chartData}
          dataKey="visitors"
          nameKey="browser"
          innerRadius={58}
          paddingAngle={2}
          cornerRadius={4}
        >
          <Label
            content={({ viewBox }) => {
              if (!viewBox || !("cx" in viewBox)) {
                return null
              }
              return (
                <text
                  x={viewBox.cx}
                  y={viewBox.cy}
                  textAnchor="middle"
                  dominantBaseline="middle"
                >
                  <tspan
                    x={viewBox.cx}
                    y={viewBox.cy}
                    className="fill-foreground text-2xl font-semibold"
                  >
                    {total.toLocaleString("en-US")}
                  </tspan>
                  <tspan
                    x={viewBox.cx}
                    y={(viewBox.cy ?? 0) + 22}
                    className="fill-muted-foreground"
                  >
                    Visitors
                  </tspan>
                </text>
              )
            }}
          />
        </Pie>
        <ChartLegend content={<ChartLegendContent nameKey="browser" />} />
      </PieChart>
    </ChartContainer>
  )
}
```

### Radar

Polar grids and axes pick up the same muted strokes and labels as cartesian ones.

```tsx title="components/examples/chart/radar.tsx"
"use client"

import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from "recharts"

import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

const chartData = [
  { skill: "Speed", current: 82, target: 90 },
  { skill: "Quality", current: 74, target: 85 },
  { skill: "Reach", current: 61, target: 70 },
  { skill: "Retention", current: 88, target: 85 },
  { skill: "Support", current: 69, target: 80 },
  { skill: "Pricing", current: 77, target: 75 },
]

const chartConfig = {
  current: {
    label: "Current",
    color: "var(--chart-1)",
  },
  target: {
    label: "Target",
    color: "var(--chart-3)",
  },
} satisfies ChartConfig

export function ChartRadar() {
  return (
    <ChartContainer
      aria-label="Radar chart of current and target scores"
      config={chartConfig}
      className="aspect-square h-80"
    >
      <RadarChart data={chartData}>
        <ChartTooltip content={<ChartTooltipContent indicator="line" />} />
        <PolarGrid />
        <PolarAngleAxis dataKey="skill" />
        <Radar
          dataKey="current"
          stroke="var(--color-current)"
          strokeWidth={2}
          fill="var(--color-current)"
          fillOpacity={0.15}
        />
        <Radar
          dataKey="target"
          stroke="var(--color-target)"
          strokeWidth={2}
          strokeDasharray="4 4"
          fill="none"
        />
        <ChartLegend content={<ChartLegendContent />} />
      </RadarChart>
    </ChartContainer>
  )
}
```

### Tooltip

Switch between `dot`, `line` and `dashed` indicators, and hide the label with `hideLabel`. `defaultIndex` on `<ChartTooltip />` shows a point on the first render.

```tsx title="components/examples/chart/tooltip.tsx"
"use client"

import * as React from "react"
import { Bar, BarChart, XAxis } from "recharts"

import {
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  type ChartTooltipContentProps,
  type ChartConfig,
} from "@/components/ui/chart"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

const chartData = [
  { day: "Mon", running: 450, swimming: 300 },
  { day: "Tue", running: 380, swimming: 420 },
  { day: "Wed", running: 520, swimming: 120 },
  { day: "Thu", running: 140, swimming: 550 },
  { day: "Fri", running: 600, swimming: 350 },
  { day: "Sat", running: 480, swimming: 400 },
]

const chartConfig = {
  running: {
    label: "Running",
    color: "var(--chart-1)",
  },
  swimming: {
    label: "Swimming",
    color: "var(--chart-2)",
  },
} satisfies ChartConfig

type Indicator = NonNullable<ChartTooltipContentProps["indicator"]>

const indicators: Indicator[] = ["dot", "line", "dashed"]

export function ChartTooltipIndicators() {
  const [indicator, setIndicator] = React.useState<Indicator>("dot")
  const [hideLabel, setHideLabel] = React.useState(false)

  return (
    <div className="flex w-full max-w-md flex-col items-center gap-4">
      <div className="flex flex-wrap justify-center gap-2">
        <ToggleGroup
          variant="outline"
          size="sm"
          aria-label="Indicator"
          value={[indicator]}
          onValueChange={(value) => {
            if (value.length > 0) {
              setIndicator(value[0] as Indicator)
            }
          }}
        >
          {indicators.map((item) => (
            <ToggleGroupItem key={item} value={item}>
              {item}
            </ToggleGroupItem>
          ))}
        </ToggleGroup>
        <ToggleGroup
          variant="outline"
          size="sm"
          aria-label="Label"
          value={hideLabel ? ["hidden"] : []}
          onValueChange={(value) => setHideLabel(value.length > 0)}
        >
          <ToggleGroupItem value="hidden">hideLabel</ToggleGroupItem>
        </ToggleGroup>
      </div>
      <ChartContainer
        aria-label="Bar chart of running and swimming time"
        config={chartConfig}
        className="h-52 w-full"
      >
        <BarChart data={chartData}>
          <XAxis
            dataKey="day"
            tickLine={false}
            axisLine={false}
            tickMargin={10}
          />
          <ChartTooltip
            defaultIndex={2}
            content={
              <ChartTooltipContent
                indicator={indicator}
                hideLabel={hideLabel}
              />
            }
          />
          <Bar
            dataKey="running"
            stackId="a"
            fill="var(--color-running)"
            radius={[0, 0, 4, 4]}
          />
          <Bar
            dataKey="swimming"
            stackId="a"
            fill="var(--color-swimming)"
            radius={[4, 4, 0, 0]}
          />
        </BarChart>
      </ChartContainer>
    </div>
  )
}
```

### Formatting values

`valueFormatter` formats the number and keeps the indicator and name. `labelFormatter` does the same for the label. To replace a whole row instead, use `formatter`.

```tsx title="components/examples/chart/value-format.tsx"
"use client"

import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"

import {
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

const chartData = [
  { month: "2026-01", revenue: 18420.5 },
  { month: "2026-02", revenue: 21310 },
  { month: "2026-03", revenue: 19875.25 },
  { month: "2026-04", revenue: 24960 },
  { month: "2026-05", revenue: 28140.75 },
  { month: "2026-06", revenue: 31295 },
]

const chartConfig = {
  revenue: {
    label: "Revenue",
    color: "var(--chart-1)",
  },
} satisfies ChartConfig

const currency = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
})

const compactCurrency = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  notation: "compact",
})

const monthFormat = new Intl.DateTimeFormat("en-US", {
  month: "long",
  year: "numeric",
  timeZone: "UTC",
})

function formatMonth(value: unknown) {
  return monthFormat.format(new Date(`${value}-01T00:00:00Z`))
}

export function ChartValueFormat() {
  return (
    <ChartContainer
      aria-label="Area chart of revenue"
      config={chartConfig}
      className="h-56 w-full max-w-md"
    >
      <AreaChart data={chartData} margin={{ left: 4, right: 12, top: 8 }}>
        <CartesianGrid vertical={false} />
        <XAxis
          dataKey="month"
          tickLine={false}
          axisLine={false}
          tickMargin={8}
          tickFormatter={(value) => formatMonth(value).slice(0, 3)}
        />
        <YAxis
          tickLine={false}
          axisLine={false}
          tickMargin={8}
          width={48}
          tickFormatter={(value) => compactCurrency.format(value)}
        />
        <ChartTooltip
          content={
            <ChartTooltipContent
              indicator="line"
              labelFormatter={formatMonth}
              valueFormatter={(value) => currency.format(Number(value))}
            />
          }
        />
        <Area
          dataKey="revenue"
          type="monotone"
          stroke="var(--color-revenue)"
          strokeWidth={2}
          fill="var(--color-revenue)"
          fillOpacity={0.1}
        />
      </AreaChart>
    </ChartContainer>
  )
}
```

### Icons

An `icon` in the config replaces the color key in the tooltip and legend.

```tsx title="components/examples/chart/icons.tsx"
"use client"

import {
  IconDeviceDesktop,
  IconDeviceMobile,
  IconDeviceTablet,
} from "@tabler/icons-react"
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"

import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

const chartData = [
  { month: "Jan", desktop: 186, tablet: 42, mobile: 80 },
  { month: "Feb", desktop: 305, tablet: 61, mobile: 200 },
  { month: "Mar", desktop: 237, tablet: 54, mobile: 120 },
  { month: "Apr", desktop: 173, tablet: 70, mobile: 190 },
]

const chartConfig = {
  desktop: {
    label: "Desktop",
    icon: IconDeviceDesktop,
    color: "var(--chart-1)",
  },
  tablet: {
    label: "Tablet",
    icon: IconDeviceTablet,
    color: "var(--chart-3)",
  },
  mobile: {
    label: "Mobile",
    icon: IconDeviceMobile,
    color: "var(--chart-2)",
  },
} satisfies ChartConfig

export function ChartIcons() {
  return (
    <ChartContainer
      aria-label="Bar chart of desktop, tablet and mobile visitors"
      config={chartConfig}
      className="h-56 w-full max-w-md"
    >
      <BarChart data={chartData}>
        <CartesianGrid vertical={false} />
        <XAxis
          dataKey="month"
          tickLine={false}
          axisLine={false}
          tickMargin={10}
        />
        <ChartTooltip content={<ChartTooltipContent />} />
        <ChartLegend content={<ChartLegendContent />} />
        <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
        <Bar dataKey="tablet" fill="var(--color-tablet)" radius={4} />
        <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
      </BarChart>
    </ChartContainer>
  )
}
```

### Light and dark colors

A `theme` object picks a different color in each theme. Switch the site theme to see it change.

```tsx title="components/examples/chart/theme.tsx"
"use client"

import { CartesianGrid, Line, LineChart, XAxis } from "recharts"

import {
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

const chartData = [
  { hour: "08:00", latency: 182 },
  { hour: "10:00", latency: 164 },
  { hour: "12:00", latency: 231 },
  { hour: "14:00", latency: 205 },
  { hour: "16:00", latency: 148 },
  { hour: "18:00", latency: 171 },
]

const chartConfig = {
  latency: {
    label: "Latency (ms)",
    theme: {
      light: "oklch(0.5 0.17 285)",
      dark: "oklch(0.75 0.14 190)",
    },
  },
} satisfies ChartConfig

export function ChartTheme() {
  return (
    <ChartContainer
      aria-label="Line chart of latency in milliseconds"
      config={chartConfig}
      className="h-52 w-full max-w-md"
    >
      <LineChart data={chartData} margin={{ left: 12, right: 12, top: 8 }}>
        <CartesianGrid vertical={false} />
        <XAxis
          dataKey="hour"
          padding={{ left: 16, right: 16 }}
          tickLine={false}
          axisLine={false}
          tickMargin={8}
        />
        <ChartTooltip content={<ChartTooltipContent indicator="line" />} />
        <Line
          dataKey="latency"
          type="linear"
          stroke="var(--color-latency)"
          strokeWidth={2}
          dot={{ r: 3, fill: "var(--color-latency)" }}
        />
      </LineChart>
    </ChartContainer>
  )
}
```

### Negative values

Color each bar with `<Cell />` and mark zero with `<ReferenceLine />`.

```tsx title="components/examples/chart/negative.tsx"
"use client"

import {
  Bar,
  BarChart,
  CartesianGrid,
  Cell,
  ReferenceLine,
  XAxis,
} from "recharts"

import {
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

const chartData = [
  { month: "Jan", change: 12.4 },
  { month: "Feb", change: -4.1 },
  { month: "Mar", change: 8.7 },
  { month: "Apr", change: -9.3 },
  { month: "May", change: 3.2 },
  { month: "Jun", change: 15.8 },
]

const chartConfig = {
  change: {
    label: "Change",
  },
  gain: {
    label: "Gain",
    color: "var(--chart-1)",
  },
  loss: {
    label: "Loss",
    color: "var(--chart-5)",
  },
} satisfies ChartConfig

export function ChartNegative() {
  return (
    <ChartContainer
      aria-label="Bar chart of gains and losses"
      config={chartConfig}
      className="h-56 w-full max-w-md"
    >
      <BarChart data={chartData}>
        <CartesianGrid vertical={false} />
        <XAxis
          dataKey="month"
          tickLine={false}
          axisLine={false}
          tickMargin={10}
        />
        <ReferenceLine y={0} />
        <ChartTooltip
          cursor={false}
          content={
            <ChartTooltipContent
              hideIndicator
              valueFormatter={(value) =>
                `${Number(value) > 0 ? "+" : ""}${value}%`
              }
            />
          }
        />
        <Bar dataKey="change" radius={4}>
          {chartData.map((item) => (
            <Cell
              key={item.month}
              fill={item.change < 0 ? "var(--color-loss)" : "var(--color-gain)"}
            />
          ))}
        </Bar>
      </BarChart>
    </ChartContainer>
  )
}
```

### Long labels

Long series names wrap in the legend and the tooltip, and large values stay on one line beside them.

```tsx title="components/examples/chart/long-labels.tsx"
"use client"

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

import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

const chartData = [
  {
    region: "North America and the Caribbean",
    enterprise: 1284032,
    midMarket: 842311,
    selfServe: 402118,
    partners: 220940,
    marketplace: 98110,
  },
  {
    region: "Europe, Middle East and Africa",
    enterprise: 1032544,
    midMarket: 760201,
    selfServe: 515720,
    partners: 180332,
    marketplace: 121004,
  },
  {
    region: "Asia Pacific",
    enterprise: 690210,
    midMarket: 612090,
    selfServe: 688402,
    partners: 140220,
    marketplace: 160550,
  },
]

const chartConfig = {
  enterprise: {
    label: "Enterprise contracts (annual)",
    color: "var(--chart-1)",
  },
  midMarket: {
    label: "Mid-market subscriptions",
    color: "var(--chart-2)",
  },
  selfServe: {
    label: "Self-serve card payments",
    color: "var(--chart-3)",
  },
  partners: {
    label: "Reseller and agency partners",
    color: "var(--chart-4)",
  },
  marketplace: {
    label: "Cloud marketplace listings",
    color: "var(--chart-5)",
  },
} satisfies ChartConfig

export function ChartLongLabels() {
  return (
    <ChartContainer
      aria-label="Bar chart of revenue by sales channel"
      config={chartConfig}
      className="h-80 w-full max-w-sm"
    >
      <BarChart data={chartData}>
        <XAxis
          dataKey="region"
          tickLine={false}
          axisLine={false}
          tickMargin={10}
          tickFormatter={(value) => value.split(/[ ,]/)[0]}
        />
        <ChartTooltip content={<ChartTooltipContent />} />
        <ChartLegend content={<ChartLegendContent />} />
        <Bar dataKey="enterprise" stackId="a" fill="var(--color-enterprise)" />
        <Bar dataKey="midMarket" stackId="a" fill="var(--color-midMarket)" />
        <Bar dataKey="selfServe" stackId="a" fill="var(--color-selfServe)" />
        <Bar dataKey="partners" stackId="a" fill="var(--color-partners)" />
        <Bar
          dataKey="marketplace"
          stackId="a"
          fill="var(--color-marketplace)"
          radius={[4, 4, 0, 0]}
        />
      </BarChart>
    </ChartContainer>
  )
}
```

### Right to left

Recharts does not mirror axes on its own. Set `reversed` on the x-axis and move the y-axis to the right. Pass `locale` to the tooltip to show the reader's digits.

```tsx title="components/examples/chart/rtl.tsx"
"use client"

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

import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@/components/ui/chart"

const chartData = [
  { month: "يناير", desktop: 186, mobile: 80 },
  { month: "فبراير", desktop: 305, mobile: 200 },
  { month: "مارس", desktop: 237, mobile: 120 },
  { month: "أبريل", desktop: 73, mobile: 190 },
  { month: "مايو", desktop: 209, mobile: 130 },
  { month: "يونيو", desktop: 214, mobile: 140 },
]

const chartConfig = {
  desktop: {
    label: "سطح المكتب",
    color: "var(--chart-1)",
  },
  mobile: {
    label: "الجوال",
    color: "var(--chart-2)",
  },
} satisfies ChartConfig

const numbers = new Intl.NumberFormat("ar-EG")

export function ChartRtl() {
  return (
    <div dir="rtl" className="w-full max-w-md">
      <ChartContainer
        aria-label="مخطط أعمدة لزوار سطح المكتب والجوال"
        config={chartConfig}
        className="h-60 w-full"
      >
        <BarChart data={chartData}>
          <CartesianGrid vertical={false} />
          <XAxis
            dataKey="month"
            reversed
            tickLine={false}
            axisLine={false}
            tickMargin={10}
          />
          <YAxis
            orientation="right"
            tickLine={false}
            axisLine={false}
            tickMargin={8}
            width={36}
            tickFormatter={(value) => numbers.format(value)}
          />
          <ChartTooltip content={<ChartTooltipContent locale="ar-EG" />} />
          <ChartLegend content={<ChartLegendContent />} />
          <Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
          <Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
        </BarChart>
      </ChartContainer>
    </div>
  )
}
```

## Keyboard

Recharts turns on `accessibilityLayer` by default, so the chart is one tab stop that you move through point by point.

| Key | Action |
| --- | --- |
| `Tab` | Focuses the chart and shows a focus ring. |
| `←` `→` | Moves the tooltip to the next or previous point. In a reversed axis the keys follow the screen. |
| `Enter` | Shows or hides the tooltip at the current point. |

## Accessibility

- Charts are images to screen readers. Name each one with a visible title nearby or an `aria-label` on the Recharts chart, and offer a table when people need the exact values.
- The tooltip and legend name every series in text, so a reader never has to match colors alone.
- With reduced motion, Recharts skips its enter animations and the tooltip jumps between points instead of gliding.
- Tooltip values are formatted in `en-US` unless you pass `locale`, so the server and browser render the same text.

## API reference

The parts sit on top of Recharts. Every Recharts prop still works on the chart components you put inside.

### ChartContainer

Provides the config, writes the color variables and renders a Recharts `<ResponsiveContainer />` around its child. Takes every `<div>` prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `config` | `ChartConfig` | – | Labels, icons and colors for each data key. |
| `children` | `ReactElement` | – | One Recharts chart, such as <BarChart />. |
| `initialDimension` | `{ width: number; height: number }` | `{ width: 320, height: 200 }` | The size used before the container is measured. |
| `id` | `string` | – | Names the chart in data-chart. Generated when omitted. |

| Attribute | Description |
| --- | --- |
| `data-slot="chart"` | The container. |
| `data-chart` | The id the color variables are scoped to. |
| `--color-KEY` | One color variable per configured key. |

### ChartConfig

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `ReactNode` | – |  |
| `icon` | `ComponentType` | – | Replaces the color key in the tooltip and legend. |
| `color` | `string` | – | Any CSS color or variable. |
| `theme` | `{ light: string; dark: string }` | – | Use instead of color for a color per theme. |

### ChartTooltip

The Recharts `<Tooltip />`, with a shorter glide between points. Takes every Recharts tooltip prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `content` | `ReactElement \| (props) => ReactNode` | – | Usually <ChartTooltipContent />. |
| `animationDuration` | `number` | `200` |  |
| `animationEasing` | `string` | `"ease-out"` |  |
| `defaultIndex` | `number` | – | Shows the tooltip at this point on first render. |

### ChartTooltipContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `indicator` | `"dot" \| "line" \| "dashed"` | `"dot"` |  |
| `hideLabel` | `boolean` | `false` |  |
| `hideIndicator` | `boolean` | `false` |  |
| `labelKey` | `string` | – | The config or data key to use for the label. |
| `nameKey` | `string` | – | The config or data key to use for each name. |
| `valueFormatter` | `(value, name, item) => ReactNode` | – | Formats each value and keeps the indicator and name. |
| `labelFormatter` | `(label, payload) => ReactNode` | – |  |
| `formatter` | `(value, name, item, index, payload) => ReactNode` | – | Replaces each row. |
| `locale` | `Intl.LocalesArgument` | `"en-US"` | Formats numeric values. |
| `color` | `string` | – | One indicator color for every row. |
| `className` | `string` | – |  |
| `labelClassName` | `string` | – |  |

| Attribute | Description |
| --- | --- |
| `data-slot="chart-tooltip"` | The box. |
| `data-slot="chart-tooltip-label"` | The label. |
| `data-slot="chart-tooltip-item"` | One row per series. |
| `data-slot="chart-tooltip-indicator"` | The color key. |
| `data-indicator` | On the key: dot, line or dashed. |
| `--chart-indicator` | The key's color. |

### ChartLegend

The Recharts `<Legend />`. Takes every Recharts legend prop, and lists series in the order you declare them instead of alphabetically.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `content` | `ReactElement \| (props) => ReactNode` | – | Usually <ChartLegendContent />. |
| `itemSorter` | `"value" \| "dataKey" \| (item) => number \| string \| null` | `null` | null keeps the declared order. |

### ChartLegendContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `hideIcon` | `boolean` | `false` |  |
| `nameKey` | `string` | – | The config or data key to use for each name. |
| `className` | `string` | – |  |

| Attribute | Description |
| --- | --- |
| `data-slot="chart-legend"` | The list. |
| `data-slot="chart-legend-item"` | One entry per series. |
| `data-slot="chart-legend-indicator"` | The color swatch. |

### useChart

Returns `{ config }` inside a `<ChartContainer />`, for building your own tooltip or legend content.

## Notes for AI assistants

- Install a component with the shadcn CLI: `npx shadcn@latest add https://hextaui.com/r/<name>.json`. It adds the source, the HextaUI theme tokens and any HextaUI components it depends on. `https://hextaui.com/r/all.json` installs every component.
- The code is then owned by the project, like shadcn/ui. There is no HextaUI npm package. HextaUI is MIT licensed and free for personal and commercial use.
- Behavior and accessibility come from Base UI (`@base-ui/react`). Compose with the `render` prop, not `asChild`.
- Styling uses Tailwind CSS v4 with theme tokens. Merge classes with `cn` from the `cn` package.
- Icons come from `@tabler/icons-react`.
- Import components from `@/components/ui/<name>`, hooks from `@/hooks/<name>` and utilities from `@/lib/<name>`.

Every HextaUI doc: https://hextaui.com/llms.txt
