Bar Chart

Component

Flat, bar chart visualizations powered by Recharts, four distinct variants for modern dashboards.

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>
  );
};