Funnel

How many people reach each step of a flow and where they drop off, against the previous period or split by segment.

Pattern

≈ 1,389 tokens per use (tool 890 · guidance 319 · typical payload 180)How token costs are estimated

Example: Funnel

How's our checkout funnel this week?

Checkout funnel

Oct 1–7 vs Sep 24–30

5.6%Up 0.5 points vs Sep 24–30
Viewed product to Purchased
  1. Viewed product25,060 · 100% of the first step
  2. 34% continue, 16,540 leave

    Added to cart8,520 · 34% of the first step
  3. 55% continue, 3,830 leave

    Started checkout4,690 · 19% of the first step
  4. 30% continue, 3,280 leaveBiggest drop

    Purchased1,410 · 5.6% of the first step
Checkout funnel
StepCount% of first stepStep conversionSep 24–30
Viewed product25,060100%–24,200
Added to cart8,52034%34%8,090
Started checkout4,69019%55%4,520
Purchased1,4105.6%30%1,240

Checkout loses the most: 70% who start don't finish.

Recovering 5% after Started checkout ≈ +$10,500 per period: 3,280 people leave there; at $64 per conversion, if they then convert like everyone else.

When to use

  • The user asks how a multi-step flow converts or where people drop off: checkout, signup, onboarding, a trial-to-paid path.
  • You have per-step counts from analytics, optionally for a previous period or by segment (device, channel, plan).

When not to use

  • The steps aren't sequential, or people can enter at any step. Use a Chart or a Data Table.
  • The question is about one number over time. Use a KPI Card or a chart.
  • You don't have real counts. Don't invent or estimate them.

How the agent should use it

  • Every count must come from the tool result, in funnel order. Percentages, drop-offs, the biggest drop and the overall conversion are computed by the component, so the agent never does the arithmetic.
  • Set previous per step only when you have the same steps for the comparison period, and name that period in period.compareLabel.
  • Give segments the same steps in the same order, at most 4. A segment whose counts doesn't have one value per step is left out, with a note saying why.
  • Add value.perConversion only when you know the real value of one conversion (e.g. average order value). The card then sizes the biggest drop in one line.
  • Keep insight to one sentence that names the step and a number from the data.
  • Drill-down and export are the user's requests, not actions you've taken: fetch the step's detail when asked.
  • Lifecycle: this is analytics data, so set fetchedAt (and staleAfterMs, e.g. an hour for a live dashboard or a day for weekly reports) on the call. When the user re-queries the same funnel (a new period, another segment split), mark the old call superseded by the new one. No expiresAt.

What it shows

PartNotes
HeaderTitle, period ("Oct 1–7 vs Sep 24–30"), and the overall conversion, large, with its change in percentage points when every step has previous, then "Visited to Purchased" as one quiet line
RowsOne per step: label, count (compact, e.g. "25.1K"), share of the first step and a bar whose width is the count relative to the first step (with a minimum so small steps still show)
Previous periodNot drawn per step: it drives the header's change, and each step's previous count is in the hidden table
ConnectorsBetween steps: step-to-step conversion, e.g. "52% continue" (the number who leave is read by screen readers). The highest drop rate is marked "Biggest drop" with an icon
Data checkWhen a step has more people than the step before, the connector says "Step counts go up at Purchased: check the data" instead of hiding it
OpportunityWith value.perConversion, one line: "Recovering 5% after Started checkout ≈ +$10,500", per period like the one shown. It assumes the recovered people convert like everyone else from that step on, at perConversion each; that assumption is read by screen readers and documented here rather than printed
Insight, sourceThe agent's sentence under the bars; the source (linked when url is http(s)) and the as-of date share one quiet footer line

Segments

With segments, the header gets a segmented control: All, one option per segment, and Compare. A segment view shows the same funnel with that segment's counts and its own overall conversion (no previous period, since segments don't carry one). Compare leads with each segment's overall conversion in one row, then small grouped bars per step, one per segment, each as a share of that segment's own first step, so conversion rates line up even when the segments differ in size. Segments are told apart by their name next to each bar and the legend order, not only by fill.

The view is local to the card. When the user drills into a step while a segment is selected, its id is included in the result.

Interaction

The card never locks: it's a view of the data with ways into it.

  • Drill into a step: the footer's "Drill into step" menu lists every step.
  • Export CSV: builds the CSV in the browser (one row per step: count, % of first step, step conversion, previous, and each segment's count), downloads it, and tells the agent. Fields are escaped, and text that a spreadsheet could run as a formula is prefixed with '.

Accessibility

The bars are hidden from screen readers. Each view is a figure with a one-sentence summary (aria-label) and a visually hidden table of steps × count, % of first step, step conversion and previous (or per-segment counts in Compare). Compact counts carry the full number in a visually hidden span, and the rolling overall conversion keeps its real value in one too. Changes show a sign, an arrow and text, and "Biggest drop" is text with an icon, so color is never the only cue. The segment control is a native radio group (arrow keys move between options) with a visible focus ring, and view switches and exports are announced in a polite live region. Bar animations are skipped with reduced motion.

Installation

npx shadcn@latest add https://your-site.com/r/funnel.json

Usage

app/api/chat/route.ts
import { tool } from "ai"
import { funnelTool } from "@/components/genui/funnel.schema"

const tools = {
  [funnelTool.name]: tool({
    description: funnelTool.description,
    inputSchema: funnelTool.inputSchema,
  }),
}

Without actions the funnel is display-only, so give the tool an execute that returns immediately. With them, the agent (or your host) receives one result per press:

{ action: "drill-down", stepId: "checkout", segmentId: "mobile" } // segmentId only in a segment view
{ action: "export" } // the CSV is already downloaded in the browser
<Funnel
  {...part.input}
  status={part.state === "input-streaming" ? "streaming" : "ready"}
  onResult={(r) => sendToAgent(part.toolCallId, r)}
/>

With LangGraph

Push it from a graph node with ui.push({ name: "show_funnel", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.

Restoring from history

There's no finished state to restore. Render the stored tool input again; the card stays live and opens on All.

Schema

PropTypeNotes
titlestringUp to 60 characters
steps{ id, label, count, previous? }[]2–8, in funnel order. Data-bound
period{ label, compareLabel? }?e.g. "Oct 1–7" and "Sep 24–30"
segments{ id, label, counts }[]?Up to 4; one count per step. Data-bound
value{ perConversion?, currency }?Value of one conversion, for the opportunity line. Data-bound
insightstring?One sentence, up to 200 characters
source{ label, url? }?Data-bound
asOfstring?ISO date or date-time. Data-bound
actions("drill-down" | "export")[]?Omit for display only

Host props: status (streaming, ready), onResult and className.

Source

On this page