Bar Chart
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-chartnpx @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>
);
};