Componentcharts0.9 KB gzipZero dependencies

Stacked Bar Chart

Horizontal stacked bars with segmented rows and legend chips

Preview

Live Preview
40
60

January

50
50

February

30
70

March

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add stacked-bar

Or install the full npm package:

npm install bigbullui
import { StackedBarChart } from "@/components/ui/stacked-bar";

<StackedBarChart rows={rows} />

Usage guidance

Use Stacked Bar Chart when a ticket stub surface needs Horizontal stacked bars with segmented rows and legend chips Keep props minimal and prefer composition with sibling stubs.

Avoid Stacked Bar Chart for long form flows or dense data grids where a dedicated layout block fits better.

Source code

stacked-bar.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface StackedBarSegment {
  value: number;
  color?: string;
}

export interface StackedBarRow {
  label: string;
  segments: StackedBarSegment[];
}

export interface StackedBarProps extends React.HTMLAttributes<HTMLDivElement> {
  rows: StackedBarRow[];
  height?: number;
}

export function StackedBar({
  rows,
  height = 400,
  className,
  ...props
}: StackedBarProps) {
  return (
    <div
      className={cn("w-full space-y-4", "motion-reduce:animate-none", className)}
      style={{ height: `${height}px` }}
      {...props}
    >
      {rows.map((row, rowIdx) => {
        const totalValue = row.segments.reduce(
          (sum, seg) => sum + seg.value,
          0,
        );
        const segmentHeights = row.segments.map((seg) =>
          totalValue > 0 ? (seg.value / totalValue) * 100 : 0,
        );

        return (
          <div key={row.label}>
            <div className="relative flex h-44 flex-col items-stretch justify-end gap-1 rounded-md border border-border/60 bg-card p-2">
              {row.segments.map((seg, segIdx) => {
                const isAccent = seg.color?.includes("accent") || segIdx === 0;
                const heightPct = segmentHeights[segIdx];
                const colorClass = seg.color
                  ? "border-accent bg-accent text-accent-foreground"
                  : isAccent
                    ? "border-accent bg-accent text-accent-foreground"
                    : "border-border bg-muted text-muted-foreground";

                return (
                  <div
                    key={`${row.label}-${segIdx}`}
                    className={cn(
                      "relative flex items-center justify-center rounded-sm border transition-all duration-500",
                      "motion-reduce:transition-none",
                      colorClass,
                    )}
                    style={{
                      height: `${heightPct}%`,
                      transitionDelay: `${rowIdx * 50}ms`,
                    }}
                    title={`${seg.value}`}
                  >
                    <span className="font-mono text-[10px] font-bold">
                      {seg.value}
                    </span>
                  </div>
                );
              })}
            </div>
            <p className="mt-1 text-center font-mono text-[10px] uppercase text-muted-foreground">
              {row.label}
            </p>
          </div>
        );
      })}
    </div>
  );
}

export const StackedBarChart = StackedBar;

Props

PropTypeDescription
rowsStackedBarRow[]
height?number