← Dashboard
Zubeyr··Blockscharts

Bar Chart Card

Grouped bars comparing two periods side by side, with rounded caps and a legend.

Live demo ↗

Installation

Run the shadcn CLI in a project that already has components.json. Using the @tomei tab? Register the namespace once first.

pnpm dlx shadcn@latest add https://tomei.zubeyr.dev/ui/r/bar-chart-card.json
  • →Installs recharts from npm.
  • →Pulls in card.
  • →Files land in components/dashboard.

Installing by hand? Add the dependencies, then copy the source code below.

terminal.sh
pnpm add recharts

Usage

example.tsx
import { BarChartCard } from '@/components/dashboard/bar-chart-card'

const data = [
  { label: 'Jun 6', current: 8200, previous: 6100 },
  { label: 'Jun 14', current: 5600, previous: 4900 },
  { label: 'Jun 22', current: 4800, previous: 3900 },
  { label: 'Jun 30', current: 3400, previous: 2600 },
  { label: 'Jul 5', current: 1900, previous: 1200 },
]

export default function Demo() {
  return (
    <div className="grid min-h-svh place-items-center p-8">
      <BarChartCard
        title="Net sales"
        value="$15,000"
        change={5}
        data={data}
        currentLabel="Net sales"
        previousLabel="Comparison"
        formatOptions={{ style: 'currency', currency: 'USD', maximumFractionDigits: 0 }}
        className="w-full max-w-lg"
      />
    </div>
  )
}

Source code

bar-chart-card.tsx
'use client'

import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'

import { Card, LegendItem } from '@/registry/dashboard/components/card/card'

export type BarPoint = { label: string; current: number; previous?: number }

type BarChartCardProps = {
  title: React.ReactNode
  data: BarPoint[]
  value?: React.ReactNode
  change?: number
  currentLabel?: string
  previousLabel?: string
  formatOptions?: Intl.NumberFormatOptions
  action?: React.ReactNode
  height?: number
  className?: string
}

export function BarChartCard({
  title,
  data,
  value,
  change,
  currentLabel = 'This period',
  previousLabel = 'Previous period',
  formatOptions,
  action,
  height = 200,
  className,
}: BarChartCardProps) {
  const full = new Intl.NumberFormat('en', formatOptions)
  const compact = new Intl.NumberFormat('en', { ...formatOptions, notation: 'compact' })
  const hasPrevious = data.some((d) => d.previous !== undefined)

  return (
    <Card
      title={title}
      value={value}
      change={change}
      action={action}
      className={className}
      legend={
        <>
          <LegendItem color="var(--chart-1)">{currentLabel}</LegendItem>
          {hasPrevious && <LegendItem color="var(--chart-2)">{previousLabel}</LegendItem>}
        </>
      }
    >
      <ResponsiveContainer width="100%" height={height}>
        <BarChart data={data} barGap={2} margin={{ top: 8, right: 4, bottom: 0, left: 0 }}>
          <CartesianGrid vertical={false} stroke="var(--border)" />
          <XAxis
            dataKey="label"
            axisLine={false}
            tickLine={false}
            tick={{ fill: 'var(--muted-foreground)', fontSize: 12 }}
          />
          <YAxis
            width={44}
            axisLine={false}
            tickLine={false}
            tickCount={3}
            tickFormatter={(v: number) => compact.format(v)}
            tick={{ fill: 'var(--muted-foreground)', fontSize: 12 }}
          />
          <Tooltip
            cursor={{ fill: 'var(--muted)', opacity: 0.6 }}
            content={({ active, payload, label }) =>
              active && payload?.length ? (
                <div className="rounded-lg bg-popover px-3 py-2 text-xs text-popover-foreground shadow-(--shadow-card)">
                  <p className="mb-1 text-muted-foreground">{label}</p>
                  {payload.map((p) => (
                    <p key={String(p.dataKey)} className="flex items-center gap-2 tabular-nums">
                      <span className="size-2 rounded-full" style={{ background: p.color }} />
                      {full.format(Number(p.value))}
                    </p>
                  ))}
                </div>
              ) : null
            }
          />
          <Bar dataKey="current" fill="var(--chart-1)" radius={[4, 4, 0, 0]} maxBarSize={18} />
          {hasPrevious && (
            <Bar dataKey="previous" fill="var(--chart-2)" radius={[4, 4, 0, 0]} maxBarSize={18} />
          )}
        </BarChart>
      </ResponsiveContainer>
    </Card>
  )
}