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.json

Usage

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

PropTypeDefaultDescription
dataFunnelStage[]requiredArray of funnel stages
orientation"horizontal" | "vertical""horizontal"Layout direction
colorstring"var(--chart-1)"Default color for all segments
layersnumber3Number of concentric halo rings per segment
edges"curved" | "straight""curved"Edge style for segment shapes
gapnumber4Gap between segments in pixels
staggerDelaynumber0.12Stagger delay between segment animations (seconds)
showPercentagebooleantrueShow percentage badges
showValuesbooleantrueShow value labels
showLabelsbooleantrueShow stage name labels
formatPercentage(pct: number) => stringrounds to integerCustom percentage formatter
formatValue(value: number) => stringlocale stringCustom value formatter
labelLayout"spread" | "grouped""spread"How labels are arranged within each segment
labelOrientation"vertical" | "horizontal"autoStack direction for grouped labels
labelAlign"center" | "start" | "end""center"Alignment of grouped labels
hoveredIndexnumber | null-Controlled hover state (segment index)
onHoverChange(index: number | null) => void-Callback when hover state changes
gridboolean | GridConfigfalseBackground bands and grid lines
renderPattern(id: string, color: string) => ReactNode-Custom SVG pattern for the innermost ring
classNamestring-Additional CSS class
styleCSSProperties-Additional inline styles

FunnelStage

PropertyTypeDescription
labelstringStage name displayed below the segment
valuenumberNumeric value (first item is treated as 100%)
displayValuestring?Custom display string (overrides formatted value)
colorstring?Override the chart-level color for this segment
gradientFunnelGradientStop[]?Linear gradient for this segment

GridConfig

When passing an object to grid, the following options are available:

PropertyTypeDefaultDescription
bandsbooleantrueShow alternating background bands
bandColorstring"var(--color-muted)"Color of the background bands
linesbooleantrueShow grid lines between segments
lineColorstring"var(--chart-grid)"Color of the grid lines
lineOpacitynumber1Opacity of the grid lines
lineWidthnumber1Width 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>