KQ-1313 · FUNNEL/FLOW
Funnel Flow
A conversion funnel drawn as a flow: stages are bars as long as the people who reached them, ribbons carry the ones who went on, and from each stage a drop-off branch peels away and fades. A seeded pool of particles runs it, and at each stage a particle goes on only if its lane falls inside the share that converted, so the dots split exactly as the people did; pointing at a stage freezes its particles where they are while the rest keep flowing and the details swap in on snap, and the segment switch re-weights every stage and branch on glide while particles already in flight follow their lanes onto the new shapes. The stages are a list of buttons named with full sentences: arrows move between them and freeze each, Enter pins, Escape unpins, and the segment is a radio group.
all segments · 2.3% end to end · most lost at activated
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/funnel-flow
Props
- particles
- number
- default 100
- How many particles run the funnel at once, 20 to 200. The pool is seeded and warmed up, so it is full from the first frame.
- segment / defaultSegment
- string
- default the first segment
- Which of segments the flow is weighted by. A change glides every stage and branch to its new size. Controlled or uncontrolled.
- onSegmentChange
- (id: string) => void
- default —
- Fires from the segment switch with the new segment's id.
- speed
- number
- default 1
- How fast the particles run, as a multiple of 64px a second.
- stages
- FunnelStage[]
- default defaultFunnelStages
- The stages in order, each an id and a label.
- segments
- FunnelSegment[]
- default defaultFunnelSegments
- The segments: id, label, the people who reached each stage, and optionally the median hours from the stage before and the top reason people left at each stage.
- stage / defaultStage
- string | null
- default null
- The pinned stage: frozen, with its details shown, until it is unpinned. Controlled or uncontrolled.
- onStageChange
- (id: string | null) => void
- default —
- Fires from the press or key that pinned or unpinned a stage.
- seed
- number
- default 7
- Seeds the particles' lanes and pace, so the same page always draws the same flow.
- format
- (count: number) => string
- default —
- Formats a count of people. Defaults to grouped digits in locale.
- locale
- string
- default "en-US"
- The locale for counts.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the numbers have arrived: loading shows the funnel's skeleton, error a retry.
- onRetry
- () => void
- default —
- Try again was pressed after the numbers failed to load.
- title
- string
- default "Conversion"
- The heading.
- subtitle
- string
- default —
- A quieter line under the heading, before the first stage's count.
- label
- string
- default —
- The funnel's accessible name. Defaults to the title.
- sound
- boolean
- default false
- Play a swish when the segment changes and a plip when a stage is pinned or the keyboard moves to it, pitched by how many of its people the stage keeps. Particles never sound.
- disabled
- boolean
- default false
- Shows the flow but refuses segment changes and pins.
- className
- string
- default —
- Classes for the outer panel.
| Prop | Type | Default | Description |
|---|---|---|---|
| particles | number | 100 | How many particles run the funnel at once, 20 to 200. The pool is seeded and warmed up, so it is full from the first frame. |
| segment / defaultSegment | string | the first segment | Which of segments the flow is weighted by. A change glides every stage and branch to its new size. Controlled or uncontrolled. |
| onSegmentChange | (id: string) => void | — | Fires from the segment switch with the new segment's id. |
| speed | number | 1 | How fast the particles run, as a multiple of 64px a second. |
| stages | FunnelStage[] | defaultFunnelStages | The stages in order, each an id and a label. |
| segments | FunnelSegment[] | defaultFunnelSegments | The segments: id, label, the people who reached each stage, and optionally the median hours from the stage before and the top reason people left at each stage. |
| stage / defaultStage | string | null | null | The pinned stage: frozen, with its details shown, until it is unpinned. Controlled or uncontrolled. |
| onStageChange | (id: string | null) => void | — | Fires from the press or key that pinned or unpinned a stage. |
| seed | number | 7 | Seeds the particles' lanes and pace, so the same page always draws the same flow. |
| format | (count: number) => string | — | Formats a count of people. Defaults to grouped digits in locale. |
| locale | string | "en-US" | The locale for counts. |
| status | "ready" | "loading" | "error" | "ready" | Whether the numbers have arrived: loading shows the funnel's skeleton, error a retry. |
| onRetry | () => void | — | Try again was pressed after the numbers failed to load. |
| title | string | "Conversion" | The heading. |
| subtitle | string | — | A quieter line under the heading, before the first stage's count. |
| label | string | — | The funnel's accessible name. Defaults to the title. |
| sound | boolean | false | Play a swish when the segment changes and a plip when a stage is pinned or the keyboard moves to it, pitched by how many of its people the stage keeps. Particles never sound. |
| disabled | boolean | false | Shows the flow but refuses segment changes and pins. |
| className | string | — | Classes for the outer panel. |