Componentcharts0.93 KB gzipZero dependencies
Funnel Chart
Horizontal funnel stages showing conversion percentages between steps
Preview
Live Preview
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add funnel-chartOr install the full npm package:
npm install bigbulluiUsage
Open in StackBlitzimport { FunnelChart } from "@/components/ui/funnel-chart";
<FunnelChart stages={stages} />Usage guidance
Use Funnel Chart when a ticket stub surface needs Horizontal funnel stages showing conversion percentages between steps Keep props minimal and prefer composition with sibling stubs.
Avoid Funnel Chart for long form flows or dense data grids where a dedicated layout block fits better.
Source code
funnel-chart.tsxZero 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 FunnelChartStage {
label: string;
value: number;
conversion?: number;
color?: string;
}
export interface FunnelChartProps extends React.HTMLAttributes<HTMLDivElement> {
stages: FunnelChartStage[];
height?: number;
}
export function FunnelChart({
stages,
height = 300,
className,
...props
}: FunnelChartProps) {
if (stages.length === 0) {
return <div className={cn("w-full", className)} {...props} />;
}
const maxValue = Math.max(...stages.map((s) => s.value), 1);
const n = stages.length;
const topY = 6;
const bandH = 88 / n;
const fullW = 64;
const cx = 54;
return (
<div
className={cn("w-full", "motion-reduce:animate-none", className)}
style={{ height: `${height}px` }}
>
<svg
className="h-full w-full"
viewBox="0 0 120 100"
preserveAspectRatio="xMidYMid meet"
>
{stages.map((stage, stageIdx) => {
const prevValue = stageIdx === 0 ? maxValue : stages[stageIdx - 1].value;
const topW = Math.max((Math.max(prevValue, 0) / maxValue) * fullW, 2);
const bottomW = Math.max((Math.max(stage.value, 0) / maxValue) * fullW, 2);
const y0 = topY + stageIdx * bandH;
const y1 = y0 + bandH - 2;
const midY = (y0 + y1) / 2;
const conversion = stage.conversion !== undefined ? `${stage.conversion}%` : "-";
return (
<g key={stage.label}>
<polygon
points={
`${cx - topW / 2},${y0} ` +
`${cx + topW / 2},${y0} ` +
`${cx + bottomW / 2},${y1} ` +
`${cx - bottomW / 2},${y1}`
}
fill={stage.color || "currentColor"}
opacity={0.28 + (0.5 * (n - stageIdx)) / n}
/>
<text
x={cx - fullW / 2 - 3}
y={midY + 3}
fontSize="7"
textAnchor="end"
className="motion-reduce:transition-none fill-muted-foreground font-mono uppercase"
>
{stage.label}
</text>
<text
x={cx + fullW / 2 + 3}
y={midY + 3}
fontSize="7"
textAnchor="start"
className="motion-reduce:transition-none fill-foreground font-mono"
>
{conversion}
</text>
</g>
);
})}
</svg>
</div>
);
}Props
| Prop | Type | Description |
|---|---|---|
| stages | FunnelChartStage[] | |
| height? | number |