# Bar Chart

Bars spring up in sequence when the chart enters view, show a value on hover, and keep a screen-reader table underneath.

**Category:** Display  
**Exports:** `BarChart`  
**npm dependencies:** motion (+ clsx, tailwind-merge for `cn`)

## Install

```bash
npx shadcn@latest add https://charcoalui.com/r/bar-chart.json
```
The file lands at `components/ui/bar-chart.tsx`; import from `@/components/ui/bar-chart`.

## API — Bar Chart
| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `data` | `{ label: string; value: number }[]` | — |  |
| `format` | `(value: number) => string` | — |  |
| `highlightMax` | `boolean` | `true` |  |
| `height` | `number` | `200` |  |

## Example — Bar Chart

```tsx
import { BarChart } from "@/components/ui/bar-chart";

export default function BarChartDemo({ highlightMax = true, height = 200 }: { highlightMax?: boolean; height?: number }) {
  return (
    <div className="w-full max-w-lg rounded-2xl border bg-background p-5">
      <p className="text-sm text-muted-foreground">Installs per day</p>
      <p className="mb-6 text-2xl font-semibold tracking-tight">2,418 this week</p>
      <BarChart
        title="Installs per day"
        highlightMax={highlightMax}
        height={height}
        format={(v) => `${v} installs`}
        data={[
          { label: "Mon", value: 248 },
          { label: "Tue", value: 312 },
          { label: "Wed", value: 296 },
          { label: "Thu", value: 468 },
          { label: "Fri", value: 402 },
          { label: "Sat", value: 318 },
          { label: "Sun", value: 374 },
        ]}
      />
    </div>
  );
}
```

## Source — components/ui/bar-chart.tsx

```tsx
"use client";

import * as React from "react";
import { AnimatePresence, motion, useInView, useReducedMotion } from "motion/react";
import { cn } from "@/lib/utils";

export type BarDatum = { label: string; value: number };

export type BarChartProps = {
  data: BarDatum[];
  format?: (value: number) => string;
  /** Highlight the largest bar in ember. */
  highlightMax?: boolean;
  height?: number;
  title?: string;
  className?: string;
};

export function BarChart({ data, format = String, highlightMax = true, height = 200, title = "Bar chart", className }: BarChartProps) {
  const reduce = useReducedMotion();
  const ref = React.useRef<HTMLDivElement>(null);
  const inView = useInView(ref, { once: true, amount: 0.4 });
  const [hover, setHover] = React.useState<number | null>(null);
  const max = Math.max(...data.map((d) => d.value), 1);
  const maxIndex = data.findIndex((d) => d.value === max);

  return (
    <figure ref={ref} className={cn("w-full", className)}>
      <div className="flex items-end gap-2 sm:gap-3" style={{ height }} aria-hidden onPointerLeave={() => setHover(null)}>
        {data.map((d, i) => {
          const h = (d.value / max) * 100;
          const hot = highlightMax && i === maxIndex;
          return (
            <div key={`${d.label}-${i}`} className="relative flex h-full min-w-0 flex-1 flex-col justify-end" onPointerEnter={() => setHover(i)}>
              <AnimatePresence>
                {hover === i && (
                  <motion.span
                    initial={{ opacity: 0, y: 4, scale: 0.9 }}
                    animate={{ opacity: 1, y: 0, scale: 1 }}
                    exit={{ opacity: 0, y: 4, transition: { duration: 0.1 } }}
                    className="absolute left-1/2 z-10 -translate-x-1/2 whitespace-nowrap rounded-md bg-foreground px-2 py-1 font-mono text-[11px] text-background"
                    style={{ bottom: `calc(${h}% + 8px)` }}
                  >
                    {format(d.value)}
                  </motion.span>
                )}
              </AnimatePresence>
              <motion.div
                className={cn("w-full origin-bottom rounded-t-lg transition-colors", hot ? "bg-ember" : hover === i ? "bg-foreground/80" : "bg-foreground/25")}
                style={{ height: `${h}%` }}
                initial={{ scaleY: reduce ? 1 : 0 }}
                animate={{ scaleY: inView ? 1 : 0 }}
                transition={{ type: "spring", stiffness: 180, damping: 22, delay: reduce ? 0 : i * 0.05 }}
              />
            </div>
          );
        })}
      </div>
      <div className="mt-2 flex gap-2 sm:gap-3" aria-hidden>
        {data.map((d, i) => (
          <span key={`${d.label}-${i}`} className="min-w-0 flex-1 truncate text-center font-mono text-[11px] text-muted-foreground">
            {d.label}
          </span>
        ))}
      </div>
      <table className="sr-only">
        <caption>{title}</caption>
        <tbody>
          {data.map((d, i) => (
            <tr key={`${d.label}-${i}`}>
              <th scope="row">{d.label}</th>
              <td>{format(d.value)}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </figure>
  );
}
```