# Chart

> The base every chart builds on: a frame that holds its height, theme series colors, a themed tooltip and legend, and loading, empty, and error views.

- Name: `chart`
- Status: beta
- Categories: charts
- File: the install writes `chart.tsx` to your `ui` alias, `@/components/ui` by default.
- Other registry items: `@arbokui/button`, `@arbokui/motion-timing`, `@arbokui/skeleton`. The install adds them.
- Gallery: https://arbokui.com/components/chart
- Registry item: https://arbokui.com/r/chart.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/chart
   ```

## Use it for

- Building a chart we do not ship, like a scatter or radar chart, so it looks and behaves like the others.
- Put the Recharts chart inside Chart's render function. Spread \`name\` on the chart, \`animation\` on each series, and \`tooltipAnimation\` on the Tooltip.
- Wrap the Recharts chart in Recharts' AnimationControllerProvider with \`animationController\`, so it draws in once it is on screen and ends drawn when the page prints.
- Pass \<ChartTooltipContent /> as a Recharts Tooltip's content for a tooltip in theme colors.
- Use chartSeries(config) for each series' label and color, and chartColor(i) for a single color.
- Spread chartAxisProps on each axis and chartGridProps on the grid, so they match the charts we ship.
- Pass a skeleton that looks like your chart, so it loads in its own shape. If you build your own legend, call useChartLegendGlide(ref, waiting) so its items glide when values land.

## Avoid it for

- A bar, line, area, or donut chart. Use BarChart, LineChart, AreaChart, or DonutChart. They already use this frame.
- A small trend inside a table cell or next to a number. Use Sparkline.
- A single number. Show the number in text.
- More than five series. The theme has five chart colors, and a sixth series repeats the first color.

## Props

- `title`: `ReactNode`. A visible title, shown as the figure's caption. It names the chart.
- `aria-label`: `string`. Names the chart when it has no visible title. TypeScript requires title, aria-label, or aria-labelledby.
- `size`: `"sm" | "md" | "lg"`. Default `"md"`. Height: 5, 7, or 9 times --control-h-md, so density scales it. Every state keeps it.
- `loading`: `boolean`. Default `false`. Shows a loading shape at the same height and sets aria-busy on the plot area. When data arrives, the shape fades out while the chart draws under it.
- `loadingText`: `string`. Default `"Loading chart"`. Read out to screen readers while loading.
- `error`: `ReactNode`. Shows this message in place of the chart. true shows "The chart could not load."
- `onRetry`: `() => void`. Shows a retry button under the error.
- `retryText`: `string`. Default `"Try again"`. The retry button's text.
- `empty`: `boolean`. Default `false`. True when there is nothing to draw. Shows emptyText.
- `emptyText`: `ReactNode`. Default `"No data yet"`. Shown when the chart is empty.
- `legend`: `{ key: string; label: ReactNode; color: string; value?: ReactNode }[]`. Legend entries, shown under the chart while it loads and once it draws.
- `skeleton`: `"bars" | "area" | "line" | "dots" | "points" | "ring"`. Default `"bars"`. The loading shape. Pick the one that looks like the chart: area, line, dots for a swarm, points for a scatter, ring for a pie or donut.
- `children`: `({ animation, tooltipAnimation, animationController, name }) => ReactNode`. Draws the chart once it is ready. Leave it out to show only the frame and its states.
- `className`: `string`. Extra classes. Later classes win, so className='h-96' replaces the height.

## Accessibility

- Renders a \<figure>. A title becomes its \<figcaption>, which names the figure and the chart's SVG. TypeScript requires title, aria-label, or aria-labelledby.
- Keeps one height in every state, set in CSS, so the page does not move when data arrives. The height is in the server HTML. When the page prints, it asks the browser to keep a chart that fits on one page whole. In the light theme, it asks the browser to print legend dots and loading shapes even with backgrounds off.
- While loading, the plot area has aria-busy and the loading shapes are hidden from screen readers. With motion off, the shape goes at once. Reduced motion keeps the fade.
- A role="status" line stays on the page outside the plot area. It reads out loading, the error, and the empty text when they are plain text.
- The error view shows a retry Button when you pass onRetry, so keyboard users can try again.
- Recharts' keyboard layer stays on: the chart's SVG is one tab stop, the arrow keys move between points, and the tooltip reads each point out.
- The SVG takes the focus-ring style, so keyboard focus shows on the chart.
- Series colors default to --chart-1 through --chart-5. The theme tests hold each to 3:1 against the page background, in light and dark. The legend and tooltip also name each series in text.
- Series draw in over three times --motion-duration-base, and only once the chart is on screen. A chart off screen waits with no timer running. When the page prints, every series jumps to its last frame, even in a chart never seen. The tooltip moves over --motion-duration-base. At 0ms, motion off, charts draw with no animation. Recharts also skips animation when the system asks for reduced motion.

## Examples

### A chart we do not ship, on the frame

```tsx
"use client";

import { AnimationControllerProvider, CartesianGrid, Scatter, ScatterChart, Tooltip, XAxis, YAxis } from "recharts";

import { Chart, ChartTooltipContent, chartAxisProps, chartColor, chartGridProps } from "@/components/ui/chart";

const homes = [
  { size: 80, price: 210 },
  { size: 120, price: 290 },
  { size: 160, price: 410 },
  { size: 210, price: 520 },
];

export function PriceBySize() {
  return (
    <Chart title="Price by size" empty={homes.length === 0}>
      {({ animation, tooltipAnimation, animationController, name }) => (
        <AnimationControllerProvider value={animationController}>
          <ScatterChart responsive className="size-full" data={homes} {...name}>
            <CartesianGrid {...chartGridProps} />
            <XAxis dataKey="size" name="Size" unit=" m²" type="number" {...chartAxisProps} />
            <YAxis dataKey="price" name="Price" unit="K" type="number" {...chartAxisProps} />
            <Tooltip content={<ChartTooltipContent hideLabel />} cursor={{ stroke: "var(--input)" }} {...tooltipAnimation} />
            <Scatter name="Homes" fill={chartColor(0)} {...animation} />
          </ScatterChart>
        </AnimationControllerProvider>
      )}
    </Chart>
  );
}
```

### Loading, empty, and error

```tsx
<Chart title="Sales" loading />

<Chart title="Sales" empty emptyText="No sales this week" />

<Chart title="Sales" error="Sales could not load." onRetry={() => refetch()} />
```

### A legend and a tooltip in theme colors

```tsx
import { ChartLegend, ChartTooltipContent, chartSeries } from "@/components/ui/chart";

const series = chartSeries({ desktop: { label: "Desktop" }, mobile: { label: "Mobile" } });

<ChartLegend items={series} />

<Tooltip content={<ChartTooltipContent valueFormat={{ style: "currency", currency: "USD" }} />} />
```
