# Bar Chart Interactive

> An interactive bar chart with tabbed metric selection built with TanStack Charts.

A shadcn/ui block from the [@pacekit](https://ui.pacekit.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/pacekit/tanstack-charts-bar-interactive).

- Page, with a live preview: https://shadcn.exchange/r/pacekit/tanstack-charts-bar-interactive
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/pacekit/tanstack-charts-bar-interactive-light-a1bd2e8fae053998.png
- Registry item: https://ui.pacekit.dev/r/tanstack-charts-bar-interactive.json
- Listed: 2026-10-21, source last changed 2026-10-21

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/pacekit/tanstack-charts-bar-interactive.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@tanstack/charts`, `@tanstack/react-charts`, `d3-array`, `d3-scale`
- Registry items: `card`, `chart`

## Files

### `components/tanstack-charts/bar/bar-chart-interactive.tsx`

```tsx
import { barY, defineChart } from "@tanstack/charts";
import { motion } from "@tanstack/charts/motion";
import { tooltip } from "@tanstack/charts/tooltip";
import { Chart } from "@tanstack/react-charts/core";
import { useMemo, useState } from "react";

import { extent, max } from "d3-array";
import { scaleLinear, scaleTime } from "d3-scale";

import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import type { ChartConfig } from "@/components/ui/chart";

// Generate 3 months of daily visitor data (April 1 to June 30, 2024)
const startTime = new Date(2024, 3, 1).getTime();
const DAY_MS = 24 * 60 * 60 * 1000;

const chartData = Array.from({ length: 91 }, (_, i) => {
    const date = new Date(startTime + i * DAY_MS);
    const dayOfWeek = date.getDay();
    const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
    const baseFactor = isWeekend ? 0.65 : 1.0;

    const desktop = Math.round((180 + Math.sin(i * 0.15) * 70 + Math.cos(i * 0.05) * 50) * baseFactor + (i % 7) * 12);
    const mobile = Math.round(
        (130 + Math.cos(i * 0.12) * 55 + Math.sin(i * 0.08) * 35) * (isWeekend ? 1.15 : 0.85) + (i % 5) * 10,
    );

    return {
        date,
        desktop: Math.max(50, desktop),
        mobile: Math.max(40, mobile),
    };
});

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

const motionRenderer = motion({
    initial: "always",
    transition: { type: "spring", stiffness: 140, damping: 16, mass: 0.8 },
});

const formatTooltipDate = (date: unknown) => {
    if (!(date instanceof Date)) return "";
    return date.toLocaleDateString("en-US", {
        month: "short",
        day: "numeric",
        year: "numeric",
    });
};

type MetricTab = "desktop" | "mobile";

export const BarChartInteractive = () => {
    const [activeTab, setActiveTab] = useState<MetricTab>("desktop");

    const totals = useMemo(() => {
        const totalDesktop = chartData.reduce((acc, curr) => acc + curr.desktop, 0);
        const totalMobile = chartData.reduce((acc, curr) => acc + curr.mobile, 0);

        return {
            desktop: totalDesktop,
            mobile: totalMobile,
        };
    }, []);

    const definition = useMemo(() => {
        const xDomain = extent(chartData, (d) => d.date) as [Date, Date];
        const activeColor = chartConfig[activeTab].color;
        const yMax = max(chartData, (d) => d[activeTab]) ?? 0;

        const marks = [
            barY(chartData, {
                id: "visitor-bar", // Stable mark ID for morph animations
                key: (d) => d.date.toISOString(), // Stable item keys
                x: (d) => d.date,
                y: (d) => d[activeTab],
                fill: activeColor,
                radius: 2,
            }),
        ];

        return defineChart(
            {
                renderer: motionRenderer,
                motion: {
                    transition: { type: "spring", stiffness: 140, damping: 16, mass: 0.8 },
                },
                marks,
                scales: {
                    x: {
                        scale: scaleTime().domain(xDomain),
                    },
                    y: {
                        scale: scaleLinear()
                            .domain([0, Math.ceil(yMax / 50) * 50])
                            .nice(),
                        grid: true,
                    },
                },
            },
            {
                focus: "group-x",
                tooltip: {
                    use: tooltip,
                    motion: { type: "spring", stiffness: 140, damping: 16 },
                    className: "font-sans! shadow-none! capitalize!",
                    content: (points) => {
                        const datum = points[0]?.datum as (typeof chartData)[number];
                        if (!datum) return { rows: [] };

                        return {
                            title: formatTooltipDate(points[0].xValue),
                            rows: [
                                {
                                    label: chartConfig[activeTab].label,
                                    value: Number(datum[activeTab]).toLocaleString("en-US"),
                                    color: activeColor,
                                },
                            ],
                        };
                    },
                },
            },
        );
    }, [activeTab]);

    return (
        <Card className="gap-0 py-4 sm:py-0">
            <CardHeader className="flex flex-col items-stretch border-b p-0! sm:flex-row">
                {/* Title Context */}
                <div className="flex flex-1 flex-col justify-center gap-1 px-6 pt-4 pb-3 sm:pt-6 sm:pb-6">
                    <CardTitle className="text-lg">User Activity Highlights</CardTitle>
                    <CardDescription>Showing total visitors for the last 3 months</CardDescription>
                </div>

                {/* Tab Controls */}
                <div className="flex">
                    {(["desktop", "mobile"] as const).map((key) => {
                        return (
                            <button
                                key={key}
                                data-active={activeTab === key}
                                className="data-[active=true]:bg-muted/50 hover:bg-muted/30 flex flex-1 flex-col justify-center gap-1 border-t px-6 py-4 text-left transition-colors even:border-l sm:border-t-0 sm:border-l sm:px-8 sm:py-6"
                                onClick={() => setActiveTab(key)}>
                                <span className="text-muted-foreground text-xs tracking-wider uppercase">
                                    {chartConfig[key].label}
                                </span>
                                <span className="text-lg leading-none font-bold sm:text-2xl">
                                    {totals[key].toLocaleString("en-US")}
                                </span>
                            </button>
                        );
                    })}
                </div>
            </CardHeader>

            <CardContent className="px-2 pt-4 sm:p-6 sm:pt-8">
                {/* Active Legend Indicator */}
                <div className="text-muted-foreground mb-4 flex items-center justify-end gap-2 text-xs font-medium">
                    <span
                        className="h-2.5 w-2.5 rounded-full transition-colors duration-300"
                        style={{ backgroundColor: chartConfig[activeTab].color }}
                    />
                    <span>{chartConfig[activeTab].label} Visitors</span>
                </div>

                <div className="h-[280px] w-full">
                    <Chart
                        definition={definition}
                        renderer={motionRenderer}
                        height={280}
                        ariaLabel={`User activity bar chart displaying ${activeTab} data`}
                    />
                </div>
            </CardContent>
        </Card>
    );
};

export default BarChartInteractive;
```
