Skip to content
KONIGI Patterns

Funnel

People need to see where participants leave a sequence of steps.

Updated September 10, 2026

Problem

People need to see where participants leave a process, rather than only how many started and finished.

Solution

Show a bar for each step in order, using a common scale. Include the count reaching each step and clearly distinguish conversion from the previous step from conversion across the whole process.

Define who is included, the allowed sequence, and the time available to finish. Track the same population through the steps rather than counting unrelated groups at each one.

Use clear bars and labels instead of decorative tapered shapes. Let people compare relevant segments or periods when an overall result would hide differences.

Use when

The process has a meaningful ordered sequence and people can investigate or improve a step.

Don’t use when

The path is a graph rather than a line. If people arrive from several entry points and take different routes, a funnel picks one story and hides the rest—that is what the Sankey pattern is for.

Trade-offs

A funnel hides the timing and alternative routes within the process. The largest drop may be intentional or difficult to change. Different audiences can also have very different results that disappear in the combined view.

Checklist

  • Is the step order real and mandatory, or imposed on a messier reality?
  • Is both step conversion and overall conversion shown?
  • What is the conversion window, and does it suit the decision cycle?
  • Is this the same set of people at each step, or independently counted?
  • Can someone enter mid-funnel, and where do they appear?
  • Is the funnel segmented, or is one number averaging incompatible populations?
  • Does anything show whether a drop-off is new or long-standing?
  • Is the biggest drop actually the most improvable one?
  • Are the counts shown, not only the percentages?
  • Is quantity encoded as length rather than as a tapered area?

Compare

Amplitude and Mixpanel made this the centrepiece of product analytics, with conversion windows, segmentation and step reordering as first-class controls, which is what turns a static chart into an analysis tool. Google Analytics ships a goal-funnel view built on session-scoped steps, which is why its numbers so often disagree with a product-analytics tool measuring the same flow on user scope. Grafana has no funnel panel and teams approximate it with a sorted bar chart, which works and loses the step-conversion arithmetic that makes the pattern useful. Sentry inverts the framing entirely, measuring where sessions fail rather than where they convert, which answers the same “where do we lose people” question from the error side.

Sankey and path shows branching routes. Cohort grid follows groups over elapsed time. Ratio and rate explains the denominator.

Stacked composition shows parts of a total. Compare periods helps identify when drop-off changed.

Funnel anatomy Five steps as plain horizontal bars, each carrying two numbers: the share who moved from the step above, and the share of the original population still present. The fourth step converts at ninety-one percent and only twelve percent of the original cohort ever reached it. Two numbers per step, not one Step From last Of all Viewed product — 100% Added to cart 41% 41% Began checkout 53% 22% Entered payment 55% 12% Paid 91% 11% 1 2 1 THE BIGGEST DROP Fifty-nine percent leave between viewing and the cart. That's the finding, and it's a shape, not a number. 2 91% OF WHAT A step converting at 91% looks healthy until you notice only 12% ever reached it. Three definitional questions decide whether the chart is true at all, and they are the ones that get skipped. Is the sequence real, or can people skip steps and arrive mid-way? What is the window, given somebody who started Tuesday and converted Friday either counts or doesn't? And is it the same people, because counting distinct users per step independently lets a later step exceed an earlier one. Plain bars, not a taper. A trapezoid encodes quantity as area, which reads worse than length for no gain but the metaphor.
Wireframe — the pattern's anatomy, not any one product's version of it

Implementation

Each step's drop is the finding, so the step-to-step percentage matters more than the absolute width. A funnel that only shows totals hides which stage is actually broken.

Tokens
--card--card-foreground--muted-foreground--border--status-warn--chart-1
StepFrom lastOf all
  1. Viewed product—100%
  2. Added to cart41%41%
  3. Began checkout53%22%
  4. Entered payment55%12%
  5. Paid91%11%

10,000 people, same users, 7-day window from first view

Funnel.tsxTakes counts and computes both percentages, colours the biggest drop, and prints the window the counts were taken over.

import { cn } from "@/lib/utils";

/** A step is a name and how many of the cohort reached it. Both percentages
 *  are computed here, so the chart can never show one without the other. */
export type Step = { name: string; count: number };

type Props = {
  /** In sequence. The first step is the population everything is measured against. */
  steps: Step[];
  /** The conversion window, printed under the chart. Somebody who started
   *  Tuesday and paid Friday either counts or does not, and the chart has to
   *  say which. */
  window: string;
  barWidth?: number;
};

const pct = (n: number) => `${Math.round(n * 100)}%`;
const fmt = new Intl.NumberFormat("en-GB");

export function Funnel({ steps, window, barWidth = 160 }: Props) {
  const base = steps[0]?.count ?? 0;
  const rows = steps.map((s, i) => ({
    ...s,
    ofAll: s.count / base,
    fromLast: i === 0 ? null : s.count / steps[i - 1].count,
  }));
  // The finding is the biggest single drop, and it gets the only colour.
  const worst = rows.reduce((w, r, i) => (r.fromLast !== null && (w < 0 || r.fromLast < rows[w].fromLast!) ? i : w), -1);

  return (
    <div className="w-full max-w-[480px] rounded-lg border bg-card p-4">
      <div className="flex items-baseline border-b pb-2 text-[11px] uppercase tracking-wide text-muted-foreground">
        <span className="flex-1">Step</span>
        <span className="w-20 text-right">From last</span>
        <span className="w-14 text-right">Of all</span>
      </div>
      <ol className="mt-1 text-xs">
        {rows.map((r, i) => (
          <li key={r.name} className="flex items-center py-2">
            <span className="w-28 text-card-foreground" title={`${fmt.format(r.count)} people`}>{r.name}</span>
            {/* Length, never area. A taper reads worse for no gain but the metaphor. */}
            <span
              className={cn("h-3.5 rounded-[1px]", i === worst ? "bg-status-warn" : "bg-chart-1")}
              style={{ width: Math.max(1, Math.round(r.ofAll * barWidth)) }}
            />
            <span className={cn("ml-auto w-20 text-right tabular-nums", i === worst ? "text-status-warn" : "text-muted-foreground")}>
              {r.fromLast === null ? "—" : pct(r.fromLast)}
            </span>
            <span className="w-14 text-right tabular-nums text-card-foreground">{pct(r.ofAll)}</span>
          </li>
        ))}
      </ol>
      <p className="mt-2 border-t pt-2 text-[11px] text-muted-foreground">
        {fmt.format(base)} people, {window}
      </p>
    </div>
  );
}

demo.tsxHow it is called: five checkout steps as counts, so the percentages are derived rather than typed.

import { Funnel } from "./Funnel";

/**
 * Five steps from a checkout. Ten thousand viewed, 1,083 paid. The counts are
 * chosen so the two percentages land on the drawing's: 41, 53, 55 and 91 from
 * the step before, 11% of everyone at the end.
 */
export default function Demo() {
  return (
    <Funnel
      steps={[
        { name: "Viewed product", count: 10000 },
        { name: "Added to cart", count: 4100 },
        { name: "Began checkout", count: 2170 },
        { name: "Entered payment", count: 1190 },
        { name: "Paid", count: 1083 },
      ]}
      window="same users, 7-day window from first view"
    />
  );
}
What it renders. Identical markup in both panes, with only the token values changing.

Examples

Screenshots are grouped by product and include the capture date. Dashed boxes highlight this pattern. Notes appear below each screenshot and when you hover over a box.

Tableau Public

Published workbooks show how individual authors arrange charts and interactions in Tableau.

User Funnel & Conversion Rates September 11, 2026 Tableau Public embed view; workbook published by Tetiana Berezhna medium · light · desktop-web
This published workbook shows overall conversion at each funnel step but omits conversion from the preceding step. Readers must calculate that themselves to compare losses between stages. The tapered rendering makes comparisons harder than aligned bars would. Another panel places rates based on very different registration counts at the same visual weight, showing why the denominator matters.
  • Funnel The tapered shape makes step quantities harder to compare than aligned bars.
  • Funnel Only overall conversion is labeled; step-to-step conversion requires calculation.
  • Ratio and rate Similar-looking rates are based on very different registration counts.
  • Header KPI strip Summary counts appear without a comparison value.
  • Tabs as genres Worksheet tabs use metric names that may need explanation.