Funnel Chart
An animated funnel chart with multi-layer halo rings, hover interactions, and staggered entrance animations
Preview
Installation
pnpm dlx shadcn@latest add https://ui.bklit.com/r/funnel-chart.jsonUsage
The Funnel Chart is a standalone component that renders an animated funnel visualization. Each segment represents a stage in a pipeline, with the width (or height in vertical mode) proportional to the value.
import { FunnelChart } from "@bklitui/ui/charts";
const data = [
{ label: "Visitors", value: 12400, displayValue: "12.4k" },
{ label: "Leads", value: 6800, displayValue: "6.8k" },
{ label: "Qualified", value: 3200, displayValue: "3.2k" },
{ label: "Proposals", value: 1500, displayValue: "1.5k" },
{ label: "Closed", value: 620, displayValue: "620" },
];
export default function SalesFunnel() {
return (
<FunnelChart
data={data}
color="var(--chart-1)"
layers={3}
/>
);
}Props
FunnelChart
| Prop | Type | Default | Description |
|---|---|---|---|
data | FunnelStage[] | required | Array of funnel stages |
orientation | "horizontal" | "vertical" | "horizontal" | Layout direction |
color | string | "var(--chart-1)" | Default color for all segments |
layers | number | 3 | Number of concentric halo rings per segment |
edges | "curved" | "straight" | "curved" | Edge style for segment shapes |
gap | number | 4 | Gap between segments in pixels |
staggerDelay | number | 0.12 | Stagger delay between segment animations (seconds) |
showPercentage | boolean | true | Show percentage badges |
showValues | boolean | true | Show value labels |
showLabels | boolean | true | Show stage name labels |
formatPercentage | (pct: number) => string | rounds to integer | Custom percentage formatter |
formatValue | (value: number) => string | locale string | Custom value formatter |
labelLayout | "spread" | "grouped" | "spread" | How labels are arranged within each segment |
labelOrientation | "vertical" | "horizontal" | auto | Stack direction for grouped labels |
labelAlign | "center" | "start" | "end" | "center" | Alignment of grouped labels |
hoveredIndex | number | null | - | Controlled hover state (segment index) |
onHoverChange | (index: number | null) => void | - | Callback when hover state changes |
grid | boolean | GridConfig | false | Background bands and grid lines |
renderPattern | (id: string, color: string) => ReactNode | - | Custom SVG pattern for the innermost ring |
className | string | - | Additional CSS class |
style | CSSProperties | - | Additional inline styles |
FunnelStage
| Property | Type | Description |
|---|---|---|
label | string | Stage name displayed below the segment |
value | number | Numeric value (first item is treated as 100%) |
displayValue | string? | Custom display string (overrides formatted value) |
color | string? | Override the chart-level color for this segment |
gradient | FunnelGradientStop[]? | Linear gradient for this segment |
GridConfig
When passing an object to grid, the following options are available:
| Property | Type | Default | Description |
|---|---|---|---|
bands | boolean | true | Show alternating background bands |
bandColor | string | "var(--color-muted)" | Color of the background bands |
lines | boolean | true | Show grid lines between segments |
lineColor | string | "var(--chart-grid)" | Color of the grid lines |
lineOpacity | number | 1 | Opacity of the grid lines |
lineWidth | number | 1 | Width of the grid lines in pixels |
Examples
Vertical
<FunnelChart
data={data}
orientation="vertical"
color="var(--chart-1)"
layers={3}
/>Straight Edges
<FunnelChart data={data} color="var(--chart-1)" layers={3} edges="straight" />Per-Segment Colors
Each segment can have its own color from the chart palette:
const data = [
{ label: "Awareness", value: 4100, color: "var(--chart-1)" },
{ label: "Interest", value: 2957, color: "var(--chart-2)" },
{ label: "Consideration", value: 1084, color: "var(--chart-3)" },
{ label: "Intent", value: 1038, color: "var(--chart-4)" },
{ label: "Purchase", value: 320, color: "var(--chart-5)" },
];
<FunnelChart data={data} layers={3} />Grouped Labels
Use labelLayout="grouped" to stack labels in a compact group:
<FunnelChart
data={data}
color="var(--chart-1)"
layers={3}
labelLayout="grouped"
labelAlign="center"
labelOrientation="vertical"
/>Grid
Pass grid to show alternating background bands and grid lines:
<FunnelChart data={data} color="var(--chart-1)" layers={3} grid />Pattern Fill
Use renderPattern with @visx/pattern to fill the innermost ring with a pattern while outer halo rings remain solid:
import { FunnelChart, PatternLines } from "@bklitui/ui/charts";
<FunnelChart
data={data}
color="var(--chart-3)"
layers={3}
renderPattern={(id, color) => (
<PatternLines
id={id}
height={8}
width={8}
stroke="rgba(255,255,255,0.35)"
strokeWidth={2}
orientation={["diagonal"]}
background={color}
/>
)}
/>Legend
Use hoveredIndex and onHoverChange to wire the funnel chart with a Legend for synchronized hover:
import { FunnelChart, Legend, LegendItemComponent, LegendMarker, LegendLabel } from "@bklitui/ui/charts";
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const legendItems = data.map((d) => ({
label: d.label,
value: d.value,
color: "var(--chart-1)",
}));
<FunnelChart
data={data}
color="var(--chart-1)"
layers={3}
hoveredIndex={hoveredIndex}
onHoverChange={setHoveredIndex}
/>
<Legend
items={legendItems}
hoveredIndex={hoveredIndex}
onHoverChange={setHoveredIndex}
>
<LegendItemComponent>
<LegendMarker />
<LegendLabel />
</LegendItemComponent>
</Legend>