# Bar Chart

> Bars that compare numbers across categories, side by side or stacked, with a themed tooltip, legend, and states.

- Name: `bar-chart`
- Status: beta
- Categories: charts
- File: the install writes `bar-chart.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `recharts`, `react-is`. The install adds them.
- Other registry items: `@txbrendan/chart`, `@txbrendan/chart-key`, `@txbrendan/dither`, `@txbrendan/dither-motion`, `@txbrendan/motion-timing`. The install adds them.
- Gallery: https://txbrendan.dev/components/bar-chart
- Registry item: https://txbrendan.dev/r/bar-chart.json

## Install

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

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

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

   ```json
   {
     "registries": {
       "@txbrendan": "https://txbrendan.dev/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@txbrendan` in a public list that txbrendan/ui 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 @txbrendan/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @txbrendan/bar-chart
   ```

## Use it for

- Comparing amounts across a few categories, like visitors per month or sales per region.
- Pass data, a config with one entry per series, and the categoryKey that names each bar.
- Use stacked when the parts add up to a total that matters.
- Pass valueFormat, like { style: "currency", currency: "USD" }, to format the tooltip and the y axis.
- Dithered, each bar's dots snap to its own edges, and hidden dots fill in near the pointer. Pass thin to keep the dots even, thin them toward the axis, or thin them down each bar. Pass fill, intro, hover, or idle to change how they thin and move, or set those on a Dither around the chart.
- Pass legend="top" for a key beside the title in place of the tooltip box. It shows the bar under the pointer, and its numbers roll as you move. Press a series in the key to hide or show it.

## Avoid it for

- A trend over many points in time. Use LineChart or AreaChart.
- Parts of one whole. Use DonutChart for up to five parts.
- More than five series. The theme has five chart colors.
- Dozens of categories. Bars get too thin. Show the top few, or use a table.

## Props

- `data`: `TData[]`. One object per category, like { month: "Jan", desktop: 186 }.
- `config`: `{ [key in keyof TData]?: { label: string; color?: string } }`. The series to draw, in order. Colors default to --chart-1 through --chart-5.
- `categoryKey`: `keyof TData`. The field that names each category, shown on the x axis.
- `title`: `ReactNode`. A visible title. It names the chart. TypeScript requires title, aria-label, or aria-labelledby.
- `aria-label`: `string`. Names the chart when it has no visible title.
- `size`: `"sm" | "md" | "lg"`. Default `"md"`. Height: 5, 7, or 9 times --control-h-md, so density scales it.
- `stacked`: `boolean`. Default `false`. Stacks the series in one bar per category.
- `valueFormat`: `Intl.NumberFormatOptions`. Formats tooltip values, and y axis ticks in short form, like $12.3K.
- `locale`: `string`. Default `"en-US"`. The locale for numbers. Fixed, so server and browser print the same text.
- `legend`: `boolean | "top"`. Shows the legend. By default it shows when there are two or more series. "top" shows it as a key beside the title, with the bar under the pointer in place of the tooltip box, and the last bar at rest.
- `loading`: `boolean`. Default `false`. Shows skeleton bars at the same height. When data arrives, they fade out while the bars grow in.
- `error`: `ReactNode`. Shows this message in place of the chart. true shows a default message.
- `onRetry`: `() => void`. Shows a retry button under the error.
- `emptyText`: `ReactNode`. Default `"No data yet"`. Shown when data is empty.
- `texture`: `"flat" | "dither" | "gradient" | "grain"`. dither draws the bars as dots, with a glow on dark pages, gradient fades each bar down from its top, and grain speckles it. Hover fills the bar under the pointer. Left out, the chart follows the Texture around it.
- `thin`: `"none" | "plot" | "bar"`. Default `"none"`. Dithered, where the dots thin out: "none" keeps them even down each bar, "plot" fills them solid at the top of the plot and thins them toward a light base at the axis, so short bars stay lighter, and "bar" thins them down each bar from its own top to a light base at the axis.
- `fill`: `"scatter" | "steps"`. Default `"scatter"`. Dithered, with thin, how the dots thin: one by one, or in 8 even steps. Left out, the chart follows the Dither around it.
- `intro`: `"fade" | "sweep" | "rain" | "ripple" | "pop" | "fall"`. Default `"fade"`. Dithered, how the dots come in when the chart first shows. Any but "fade" brings the bars in as dots, in place of their grow. Motion off plays none. Left out, the chart follows the Dither around it.
- `hover`: `"fill" | "none"`. Default `"fill"`. Dithered, "fill" fills in hidden dots near the pointer, and they fade back when it leaves. Even dots fill in toward solid. Left out, the chart follows the Dither around it.
- `idle`: `"none" | "wave" | "twinkle" | "breathe" | "drift"`. Default `"none"`. Dithered, what the dots do at rest: a band of extra dots passing across, single dots blinking out and on, the thin edge filling in deeper and back, or soft patches of extra dots drifting across. Left out, the chart follows the Dither around it.
- `className`: `string`. Extra classes. Later classes win, so className='h-96' replaces the height.

## Accessibility

- Renders a \<figure> named by its title, or by aria-label. TypeScript requires one.
- Recharts' keyboard layer stays on: Tab focuses the chart, the arrow keys move between categories, and the tooltip reads each one out. With the key on top, the key follows the arrow keys too, and the tooltip stays on the page for screen readers only, so each category is still read out.
- The key on top sits beside the title, not in it, so the chart's name never changes as the key moves. Each value holds the width of its longest one, so the key stays still as its numbers roll.
- Each series in the key is a toggle button. A hidden series fades out where it is, leaves the tooltip, and keeps the y axis still. In a stack, the rest glide down to close the gap once it has faded. The last one shown cannot hide. Tab reaches these buttons first, then the chart.
- Keeps one height while it loads, draws, fails, or has no data, so the page does not move. 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.
- Loading, the error, and the empty text are read out through a status line that stays on the page.
- Series colors come from --chart-1 to --chart-5, which the theme tests hold to 3:1 against the page. The legend and tooltip name each series in text.
- Bars grow in over three times --motion-duration-base, once the chart is on screen. They finish at once when the page prints. At 0ms they draw at once, and Recharts skips the animation for reduced motion.
- Dithered dots move only while the theme's pulse runs, so reduced motion and motion off keep them still. Forced colors keeps them still too, and shows every grid line. A print shows the still dots.

## Examples

### Two series

```tsx
const data = [
  { month: "Jan", desktop: 186, mobile: 80 },
  { month: "Feb", desktop: 305, mobile: 200 },
  { month: "Mar", desktop: 237, mobile: 120 },
];

<BarChart
  title="Visitors by device"
  data={data}
  config={{ desktop: { label: "Desktop" }, mobile: { label: "Mobile" } }}
  categoryKey="month"
/>
```

### Key beside the title in place of the tooltip

```tsx
<BarChart
  title="Visitors by device"
  data={data}
  config={{ desktop: { label: "Desktop" }, mobile: { label: "Mobile" } }}
  categoryKey="month"
  legend="top"
/>
```

### Stacked, with currency

```tsx
<BarChart
  title="Revenue by plan"
  data={[
    { quarter: "Q1", pro: 8200, team: 4200 },
    { quarter: "Q2", pro: 9100, team: 6150 },
  ]}
  config={{ pro: { label: "Pro" }, team: { label: "Team" } }}
  categoryKey="quarter"
  stacked
  valueFormat={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
/>
```

### Dots that thin down each bar

```tsx
<BarChart
  title="Visitors by device"
  data={data}
  config={{ desktop: { label: "Desktop" }, mobile: { label: "Mobile" } }}
  categoryKey="month"
  texture="dither"
  thin="bar"
  intro="rain"
/>
```

### While it loads, and when it fails

```tsx
<BarChart
  aria-label="Visitors by device"
  data={data ?? []}
  config={{ desktop: { label: "Desktop" }, mobile: { label: "Mobile" } }}
  categoryKey="month"
  loading={isLoading}
  error={error ? "Visitors could not load." : undefined}
  onRetry={() => refetch()}
/>
```
