Bar Chart

Component

Free, copy-pasteable Tailwind CSS Bar Chart component. Accessible, fully responsive, dark-mode ready, and customizable.

Install via CLI

Run this command to automatically add the component and its dependencies to your project.

npx @abhaysinghr516/business-wish add bar-chart
New to the CLI? Run npx @abhaysinghr516/business-wish init first to initialize your project.

Beautifully minimal Bar Chart components built on recharts. Every variant ships with precisely rounded bar geometries, a polished custom tooltip, highest-value highlighting, and seamless light/dark mode support.

Four variants cover all common use-cases: a standard vertical column chart, a horizontal leaderboard, a multi-series stacked breakdown on a dark surface, and a compact sparkline widget.

Basic Bar Chart

A clean vertical bar chart for standard time-series metrics — here, monthly visitors. The highest-value bar is fully opaque while the rest are at reduced opacity, drawing the eye immediately to the peak.

"use client";
import React from "react";
import {
  BarChart, Bar, XAxis, YAxis, CartesianGrid,
  Tooltip, ResponsiveContainer, Cell,
} from "recharts";
import { Users, TrendingUp, TrendingDown } from "lucide-react";

const monthlyVisitorsData = [
  { name: "Jan", visitors: 4200 }, { name: "Feb", visitors: 3100 },
  { name: "Mar", visitors: 5800 }, { name: "Apr", visitors: 2400 },
  { name: "May", visitors: 6500 }, { name: "Jun", visitors: 4800 },
  { name: "Jul", visitors: 8100 }, { name: "Aug", visitors: 6900 },
  { name: "Sep", visitors: 8500 }, { name: "Oct", visitors: 7200 },
  { name: "Nov", visitors: 9100 }, { name: "Dec", visitors: 10500 },
];

const CustomTooltip = ({ active, payload, label }: any) => {
  if (active && payload?.length) {
    return (
      <div className="bg-white dark:bg-neutral-900 border border-neutral-200/80 dark:border-neutral-800 p-3 rounded-2xl shadow-lg min-w-[150px]">
        <p className="text-[11px] font-semibold text-neutral-400 mb-2 tracking-widest uppercase">{label}</p>
        {payload.map((entry: any, i: number) => (
          <div key={i} className="flex items-center gap-2.5">
            <div className="w-1.5 h-1.5 rounded-full" style={{ backgroundColor: entry.color || entry.fill }} />
            <span className="text-[13px] font-medium text-neutral-500 flex-1 capitalize">{entry.name}</span>
            <span className="text-[13px] font-bold text-neutral-900 dark:text-white tabular-nums">
              {entry.value.toLocaleString()}
            </span>
          </div>
        ))}
      </div>
    );
  }
  return null;
};

export const BasicBarChart = ({ height = 320, color = "#3B82F6" }) => {
  const total = monthlyVisitorsData.reduce((s, d) => s + d.visitors, 0);
  const h1 = monthlyVisitorsData.slice(0, 6).reduce((s, d) => s + d.visitors, 0);
  const h2 = monthlyVisitorsData.slice(6).reduce((s, d) => s + d.visitors, 0);
  const delta = ((h2 - h1) / h1) * 100;
  const isUp = delta >= 0;
  const maxVal = Math.max(...monthlyVisitorsData.map((d) => d.visitors));

  return (
    <div className="w-full bg-white dark:bg-[#0A0A0A] border border-neutral-200 dark:border-neutral-800/80 rounded-3xl p-6 shadow-sm">
      <div className="flex items-start justify-between mb-8">
        <div>
          <div className="flex items-center gap-2 mb-1">
            <Users className="w-3.5 h-3.5 text-neutral-400" strokeWidth={2.5} />
            <p className="text-[12px] font-semibold text-neutral-500 uppercase tracking-widest">Monthly Visitors</p>
          </div>
          <h3 className="text-[28px] font-bold text-neutral-900 dark:text-white tracking-tighter leading-none">
            {(total / 1000).toFixed(1)}k
          </h3>
          <p className="text-[13px] text-neutral-400 mt-1">Unique traffic this year</p>
        </div>
        <div className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[12px] font-semibold ${
          isUp ? "bg-green-50 dark:bg-green-500/10 text-green-600 dark:text-green-400"
               : "bg-red-50 dark:bg-red-500/10 text-red-600 dark:text-red-400"
        }`}>
          {isUp ? <TrendingUp className="w-3.5 h-3.5" strokeWidth={2.5} />
                : <TrendingDown className="w-3.5 h-3.5" strokeWidth={2.5} />}
          <span>{Math.abs(delta).toFixed(1)}% H2 vs H1</span>
        </div>
      </div>
      <div style={{ height, width: "100%" }}>
        <ResponsiveContainer width="100%" height="100%">
          <BarChart data={monthlyVisitorsData} margin={{ top: 8, right: 4, left: -24, bottom: 0 }}>
            <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#E5E5EA" strokeOpacity={0.8} />
            <XAxis dataKey="name" axisLine={false} tickLine={false} tick={{ fontSize: 11, fill: "#8E8E93" }} dy={10} />
            <YAxis axisLine={false} tickLine={false} tick={{ fontSize: 11, fill: "#8E8E93" }} tickFormatter={(v) => v >= 1000 ? `${v / 1000}k` : v} />
            <Tooltip content={<CustomTooltip />} cursor={{ fill: color, fillOpacity: 0.04 }} />
            <Bar dataKey="visitors" radius={[5, 5, 0, 0]} barSize={22} animationDuration={1400} animationEasing="ease-out">
              {monthlyVisitorsData.map((entry, i) => (
                <Cell key={i} fill={entry.visitors === maxVal ? color : `${color}55`} />
              ))}
            </Bar>
          </BarChart>
        </ResponsiveContainer>
      </div>
    </div>
  );
};

Horizontal Bar Chart

A leaderboard-style horizontal layout showing top countries by user count. The leading country bar is fully opaque; value labels render inline on the right for instant readability.

"use client";
import React from "react";
import {
  BarChart, Bar, XAxis, YAxis, CartesianGrid,
  Tooltip, ResponsiveContainer, Cell,
} from "recharts";
import { Globe } from "lucide-react";

const topCountriesData = [
  { name: "United States", users: 12450 },
  { name: "United Kingdom", users: 8230 },
  { name: "Canada", users: 6500 },
  { name: "Germany", users: 5120 },
  { name: "Australia", users: 4800 },
  { name: "Japan", users: 4100 },
];

export const HorizontalBarChart = ({ height = 320, color = "#8B5CF6" }) => {
  const maxUsers = Math.max(...topCountriesData.map((d) => d.users));

  return (
    <div className="w-full bg-white dark:bg-[#0A0A0A] border border-neutral-200 dark:border-neutral-800/80 rounded-3xl p-6 shadow-sm">
      <div className="mb-8">
        <div className="flex items-center gap-2 mb-1">
          <Globe className="w-3.5 h-3.5 text-neutral-400" strokeWidth={2.5} />
          <p className="text-[12px] font-semibold text-neutral-500 uppercase tracking-widest">Top Geographies</p>
        </div>
        <h3 className="text-[22px] font-bold text-neutral-900 dark:text-white tracking-tight">User Distribution</h3>
        <p className="text-[13px] text-neutral-400 mt-1">By country, all time</p>
      </div>
      <div style={{ height, width: "100%" }}>
        <ResponsiveContainer width="100%" height="100%">
          <BarChart data={topCountriesData} layout="vertical" margin={{ top: 0, right: 56, left: 0, bottom: 0 }}>
            <CartesianGrid strokeDasharray="3 3" horizontal={false} stroke="#E5E5EA" strokeOpacity={0.8} />
            <XAxis type="number" axisLine={false} tickLine={false} tick={{ fontSize: 11, fill: "#8E8E93" }} tickFormatter={(v) => v >= 1000 ? `${v / 1000}k` : v} />
            <YAxis dataKey="name" type="category" axisLine={false} tickLine={false} tick={{ fontSize: 12, fill: "#525252", fontWeight: 500 }} width={108} />
            <Tooltip cursor={{ fill: color, fillOpacity: 0.04 }} />
            <Bar
              dataKey="users"
              radius={[0, 5, 5, 0]}
              barSize={18}
              animationDuration={1400}
              animationBegin={100}
              label={{ position: "right", formatter: (v: number) => v >= 1000 ? `${(v / 1000).toFixed(1)}k` : v, fontSize: 11, fontWeight: 600, fill: "#8E8E93" }}
            >
              {topCountriesData.map((entry, i) => (
                <Cell key={i} fill={entry.users === maxUsers ? color : `${color}55`} />
              ))}
            </Bar>
          </BarChart>
        </ResponsiveContainer>
      </div>
    </div>
  );
};

Stacked Bar Chart

A dark-surface multi-series chart that stacks Mobile, Desktop, and Tablet sessions into a single column per day — ideal for understanding device-type composition over time.

"use client";
import React from "react";
import {
  BarChart, Bar, XAxis, YAxis, CartesianGrid,
  Tooltip, ResponsiveContainer,
} from "recharts";
import { Smartphone } from "lucide-react";

const deviceTrafficData = [
  { name: "Mon", mobile: 4200, desktop: 2800, tablet: 900 },
  { name: "Tue", mobile: 5100, desktop: 3100, tablet: 1100 },
  { name: "Wed", mobile: 3800, desktop: 2400, tablet: 800 },
  { name: "Thu", mobile: 6400, desktop: 3900, tablet: 1500 },
  { name: "Fri", mobile: 7200, desktop: 4500, tablet: 1800 },
  { name: "Sat", mobile: 8500, desktop: 4100, tablet: 2100 },
  { name: "Sun", mobile: 6800, desktop: 3500, tablet: 2400 },
];

const legend = [
  { key: "mobile", label: "Mobile", color: "#3B82F6" },
  { key: "desktop", label: "Desktop", color: "#818CF8" },
  { key: "tablet", label: "Tablet", color: "#C084FC" },
];

export const StackedBarChart = ({ height = 320 }) => (
  <div className="w-full bg-[#0A0A0F] border border-white/[0.06] rounded-3xl p-6 shadow-2xl overflow-hidden">
    <div className="flex flex-col sm:flex-row sm:items-start justify-between mb-8 gap-4">
      <div>
        <div className="flex items-center gap-2 mb-1">
          <Smartphone className="w-3.5 h-3.5 text-neutral-500" strokeWidth={2.5} />
          <p className="text-[12px] font-semibold text-neutral-500 uppercase tracking-widest">Device Traffic</p>
        </div>
        <h3 className="text-[22px] font-bold text-white tracking-tight">Platform Breakdown</h3>
        <p className="text-[13px] text-neutral-500 mt-1">Last 7 days by device type</p>
      </div>
      <div className="flex flex-wrap gap-2">
        {legend.map((l) => (
          <div key={l.key} className="flex items-center gap-1.5 bg-white/5 border border-white/[0.07] px-2.5 py-1 rounded-full">
            <div className="w-1.5 h-1.5 rounded-full" style={{ backgroundColor: l.color }} />
            <span className="text-[11px] font-medium text-neutral-400">{l.label}</span>
          </div>
        ))}
      </div>
    </div>
    <div style={{ height, width: "100%" }}>
      <ResponsiveContainer width="100%" height="100%">
        <BarChart data={deviceTrafficData} margin={{ top: 8, right: 4, left: -24, bottom: 0 }}>
          <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#ffffff" strokeOpacity={0.04} />
          <XAxis dataKey="name" axisLine={false} tickLine={false} tick={{ fontSize: 11, fill: "#525252" }} dy={12} />
          <YAxis axisLine={false} tickLine={false} tick={{ fontSize: 11, fill: "#525252" }} tickFormatter={(v) => v >= 1000 ? `${v / 1000}k` : v} />
          <Tooltip cursor={{ fill: "#ffffff", fillOpacity: 0.04 }} />
          <Bar dataKey="mobile" stackId="a" fill="#3B82F6" radius={[0, 0, 4, 4]} barSize={28} animationDuration={1000} />
          <Bar dataKey="desktop" stackId="a" fill="#818CF8" animationDuration={1000} animationBegin={100} />
          <Bar dataKey="tablet" stackId="a" fill="#C084FC" radius={[4, 4, 0, 0]} animationDuration={1000} animationBegin={200} />
        </BarChart>
      </ResponsiveContainer>
    </div>
  </div>
);

Widget Bar Chart

A compact spark-bar card designed for tight dashboard grids. Shows the latest value, a live percentage delta badge, and seven days of bar history — the most recent bar is highlighted in full color.

"use client";
import React from "react";
import { BarChart, Bar, Tooltip, ResponsiveContainer, Cell } from "recharts";
import { Activity, ArrowUpRight } from "lucide-react";

const widgetMetricData = [
  { name: "M", value: 120 }, { name: "T", value: 150 },
  { name: "W", value: 110 }, { name: "T", value: 180 },
  { name: "F", value: 210 }, { name: "S", value: 250 },
  { name: "S", value: 220 },
];

export const WidgetBarChart = ({ color = "#F59E0B" }) => {
  const latest = widgetMetricData[widgetMetricData.length - 1].value;
  const prev = widgetMetricData[widgetMetricData.length - 2].value;
  const isUp = latest >= prev;
  const delta = (((latest - prev) / prev) * 100).toFixed(1);

  return (
    <div className="w-full max-w-[340px] bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-3xl pt-5 pb-4 px-5 shadow-sm hover:shadow-md transition-shadow duration-300 overflow-hidden">
      <div className="flex items-start justify-between mb-5">
        <div>
          <div className="flex items-center gap-1.5 mb-2">
            <Activity className="w-3.5 h-3.5 text-neutral-400" strokeWidth={2.5} />
            <p className="text-[11px] font-semibold text-neutral-500 uppercase tracking-widest">Acquisitions</p>
          </div>
          <div className="flex items-baseline gap-1.5">
            <span className="text-[32px] font-bold text-neutral-900 dark:text-white leading-none tracking-tighter">{latest}</span>
            <span className="text-[13px] font-medium text-neutral-400">this week</span>
          </div>
        </div>
        <div className={`flex items-center gap-1 text-[11px] font-semibold mt-1 px-2 py-1 rounded-full ${
          isUp ? "bg-green-50 dark:bg-green-500/10 text-green-600 dark:text-green-400"
               : "bg-red-50 dark:bg-red-500/10 text-red-600 dark:text-red-400"
        }`}>
          <ArrowUpRight className="w-3 h-3" style={{ transform: isUp ? "none" : "rotate(90deg)" }} strokeWidth={2.5} />
          <span>{Math.abs(Number(delta))}%</span>
        </div>
      </div>
      <div className="h-[72px] w-full">
        <ResponsiveContainer width="100%" height="100%">
          <BarChart data={widgetMetricData} margin={{ top: 4, right: 0, left: 0, bottom: 0 }}>
            <Tooltip
              cursor={{ fill: color, fillOpacity: 0.06 }}
              contentStyle={{ borderRadius: "12px", border: "none", boxShadow: "0 4px 20px rgba(0,0,0,0.1)", fontSize: "12px", fontWeight: 600, padding: "6px 10px" }}
              itemStyle={{ color: "#171717" }}
              labelStyle={{ display: "none" }}
              formatter={(v: any) => [`${v}`, "New"]}
            />
            <Bar dataKey="value" radius={[4, 4, 4, 4]} barSize={20} animationDuration={800}>
              {widgetMetricData.map((_, i) => (
                <Cell key={i} fill={i === widgetMetricData.length - 1 ? color : `${color}40`} />
              ))}
            </Bar>
          </BarChart>
        </ResponsiveContainer>
      </div>
    </div>
  );
};