All Taste components
Dashboard widgets
Bar chart card
Bookings per service this month next to last month, as grouped bars.
grouped bars · 6 categories · axis · grid · legend
Bookings by service
October vs September
399
15.3%OctoberSeptember
export interface BarDatum { label: string; current: number; previous: number }
const sample: BarDatum[] = [
{ label: "Haircut", current: 142, previous: 118 },
{ label: "Colour", current: 86, previous: 92 },
{ label: "Spa", current: 64, previous: 51 },
{ label: "Bridal", current: 22, previous: 15 },
{ label: "Nails", current: 48, previous: 40 },
{ label: "Grooming", current: 37, previous: 30 },
];
export function BarChartCard({
data = sample,
title = "Bookings by service",
periods = ["October", "September"],
max = 160,
}: { data?: BarDatum[]; title?: string; periods?: [string, string]; max?: number }) {
const total = data.reduce((s, d) => s + d.current, 0);
const before = data.reduce((s, d) => s + d.previous, 0);
const change = ((total / before - 1) * 100).toFixed(1);
const w = data.length * 100;
const h = (v: number) => (v / max) * 100;
return (
<section className="w-full rounded-lg border border-border bg-bg p-4">
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<h3 className="text-sm font-medium text-fg">{title}</h3>
<p className="text-[12.5px] text-fg-subtle">{periods[0]} vs {periods[1]}</p>
</div>
<div className="text-right">
<p className="text-xl font-medium tabular-nums">{total}</p>
<span className="rounded-full bg-success-soft px-1.5 py-0.5 text-xs font-medium tabular-nums text-success-fg">+{change}%</span>
</div>
</div>
<div className="mt-4 grid grid-cols-[auto_minmax(0,1fr)] gap-x-2.5">
<div className="-my-1.5 flex h-[180px] flex-col justify-between text-right text-[11px] leading-3 tabular-nums text-fg-subtle" aria-hidden>
{[1, 0.75, 0.5, 0.25, 0].map((f) => <span key={f}>{max * f}</span>)}
</div>
<svg viewBox={"0 0 " + w + " 100"} preserveAspectRatio="none" className="h-[180px] w-full overflow-visible" role="img"
aria-label={data.map((d) => d.label + ": " + d.current).join(", ")}>
{[0, 25, 50, 75, 100].map((g) => (
<line key={g} x1="0" x2={w} y1={g} y2={g} className="text-border" stroke="currentColor" vectorEffect="non-scaling-stroke" />
))}
{data.map((d, n) => (
<g key={d.label}>
<rect x={n * 100 + 28} width="20" y={100 - h(d.previous)} height={h(d.previous)} className="fill-border-strong" />
<rect x={n * 100 + 52} width="20" y={100 - h(d.current)} height={h(d.current)} className="fill-primary" />
</g>
))}
</svg>
<div className="col-start-2 mt-2 flex text-[11.5px] text-fg-subtle" aria-hidden>
{data.map((d) => <span key={d.label} className="min-w-0 flex-1 truncate text-center">{d.label}</span>)}
</div>
</div>
<div className="mt-3.5 flex gap-4 text-xs text-fg-muted">
<span className="inline-flex items-center gap-1.5"><span className="size-2 rounded-sm bg-primary" />{periods[0]}</span>
<span className="inline-flex items-center gap-1.5"><span className="size-2 rounded-sm bg-border-strong" />{periods[1]}</span>
</div>
</section>
);
}
The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.