Components

LeadLifecycleFunnel

A predefined lifecycle visualisation for marketing lead-score funnels. Renders the fixed lifecycle stages as an inverted-triangle funnel with the legend outside, pulls data-quality flags (like "Poor Data") out of the funnel into an Alert above the chart, and shows a tooltip on hover.

Overview

LeadLifecycleFunnel is not a generic funnel — for that, use ChartFunnel. It is tailored for a fixed set of marketing lifecycle stages (Awareness → Maintenance) and takes the raw API response as input.

The component:

  • Accepts the API statuses array directly — no manual mapping required.
  • Auto-detects data-quality flags (default: Poor Data) and renders each as a warning Alert above the funnel. Quality flags are excluded from the lifecycle denominator.
  • Draws an inverted-triangle SVG funnel with the legend in a right-hand column row-aligned to each segment.
  • Shows a tooltip on hover (and on keyboard focus) with the raw count and share.
  • Defaults to the chart-token palette (chart-1, chart-2, …, chart-cat-*). Pass useDataColors to honour each status's color field.
  • Clamps tiny stages to a minimum visible width so 0.55%-level rows don't disappear.

The generic ChartFunnel is unchanged. Use that for checkout funnels, email funnels, signup funnels — anywhere the question is "where do users drop off?". Use LeadLifecycleFunnel only for the predefined lead-score lifecycle funnel with fixed stages.

Usage

import { LeadLifecycleFunnel } from '@marmoui/ui';

The expected data shape mirrors the lifecycle API exactly:

type LifecycleStatus = {
  label: string;     // e.g. "Awareness"
  leads: number;     // e.g. 6465
  color?: string;    // user-set tone from the API; ignored unless `useDataColors` is true
};

type ApiResponse = {
  statuses: LifecycleStatus[];
  status: 'success';
};

Examples

API shape — drop in the response

Pass apiResponse.statuses straight to the component. Poor Data is auto-extracted into a warning Alert; the remaining six stages render in lifecycle order using the chart palette.

Default — chart-token palette, Poor Data flagged

No mapping needed: the component reads `label` / `leads` directly from each row and auto-detects `Poor Data` as a quality flag.

Leads Funnel

  1. Awareness6,465 · 88.33%
  2. Consideration158 · 2.16%
  3. Decision109 · 1.49%
  4. Conversion109 · 1.49%
  5. Retention474 · 6.48%
  6. Maintenance4 · 0.05%
const apiResponse = {
statuses: [
  { label: 'Poor Data',     color: '#f00000', leads: 9342 },
  { label: 'Awareness',     color: '#e6b58e', leads: 6465 },
  { label: 'Consideration', color: '#17960c', leads: 158  },
  { label: 'Decision',      color: '#1bcaab', leads: 109  },
  { label: 'Conversion',    color: '#b762bb', leads: 109  },
  { label: 'Retention',     color: '#30367e', leads: 474  },
  { label: 'Maintenance',   color: '#c5973d', leads: 4    },
],
};

<LeadLifecycleFunnel
ariaLabel="Lead lifecycle"
statuses={apiResponse.statuses}
onReviewFlag={(label) => router.push(`/leads/review?flag=${label}`)}
/>

Honour the API colors

Set useDataColors to use each status's color field as the segment fill instead of the chart palette. Useful when the customer has heavily themed their lifecycle.

`useDataColors` — fills come from the API

Each segment uses the user-defined hex from the API. When the customer hasn't set per-stage colors, omit this flag.

Leads Funnel

  1. Awareness6,465 · 88.33%
  2. Consideration158 · 2.16%
  3. Decision109 · 1.49%
  4. Conversion109 · 1.49%
  5. Retention474 · 6.48%
  6. Maintenance4 · 0.05%
<LeadLifecycleFunnel
ariaLabel="Lead lifecycle"
statuses={apiResponse.statuses}
useDataColors
onReviewFlag={(label) => {}}
/>

Without data colors (default)

The same data — minus useDataColors. Every segment uses the categorical chart palette in lifecycle order.

Default chart palette

Cycles `chart-1`, `chart-2`, …, `chart-cat-teal`, `chart-cat-violet`, etc.

Leads Funnel

  1. Awareness6,465 · 88.33%
  2. Consideration158 · 2.16%
  3. Decision109 · 1.49%
  4. Conversion109 · 1.49%
  5. Retention474 · 6.48%
  6. Maintenance4 · 0.05%
<LeadLifecycleFunnel
ariaLabel="Lead lifecycle"
statuses={apiResponse.statuses}
onReviewFlag={(label) => {}}
/>

No quality flags

If statuses contains no Poor Data row (or no row whose label matches qualityFlagLabels), the Alert disappears and the lifecycle bars stand alone.

Clean lifecycle

Leads Funnel

  1. Awareness6,465 · 88.33%
  2. Consideration158 · 2.16%
  3. Decision109 · 1.49%
  4. Conversion109 · 1.49%
  5. Retention474 · 6.48%
  6. Maintenance4 · 0.05%
const clean = apiResponse.statuses.filter((s) => s.label !== 'Poor Data');

<LeadLifecycleFunnel
ariaLabel="Lead lifecycle"
statuses={clean}
/>

How it fixes the legacy widget

Legacy problemLeadLifecycleFunnel behaviour
Inverted-triangle funnel with 7 stages collapsing tiny segments to 0pxSVG inverted-triangle funnel. Slice widths are proportional to leads, clamped to minSlicePercent (default 4%) so 0.55% stages stay legible.
"Poor Data" rendered as a stageAuto-extracted via qualityFlagLabels (default ["Poor Data"]). Renders as a warning Alert above the funnel with an optional Review action. Excluded from the lifecycle denominator.
Unpredictable user colors on a sequential lifecycleDefault fill is the chart palette (chart-1chart-cat-*). Pass useDataColors to honour the API color field when needed.
Legend inside / under the funnel competing with segmentsLegend lives outside the funnel, in a right-hand column row-aligned to each segment. Hovering either side highlights the matching segment.
No tooltip on hoverHover (or keyboard-focus) any segment to surface a tooltip with the raw count and share.
Percentages calculated against the inflated total (including Poor Data)Stage percentages use the clean lifecycle total. Quality-flag percentages use the grand total — the difference is what makes the Alert numbers meaningful.

Props

PropTypeDefaultDescription
statusesLifecycleStatus[]Lifecycle data from the API. Order is preserved.
qualityFlagLabelsstring[]["Poor Data"]Labels whose rows are pulled out as warning Alerts (case-insensitive match).
useDataColorsbooleanfalseUse each status's color field as the fill. When false, the chart palette is used.
onReviewFlag(label: string) => voidWhen set, an action button appears in each quality-flag Alert.
reviewLabelstring"Review"Label for the quality-flag Alert action button.
flagDescription(flag) => ReactNodebuilt-in copyOverride the body copy of the quality-flag Alert.
ariaLabelstringRequired. Describes the chart for assistive tech.
formatValue(value: number) => stringtoLocaleStringOverride the numeric formatter for legend rows and tooltips.
heightnumber280Funnel SVG height in pixels.
minSlicePercentnumber4Minimum visual slice width so tiny stages stay visible.
palettestring[]chart-1…chart-cat-pinkOverride the default categorical palette.
classNamestringClass applied to the wrapper.

LifecycleStatus

FieldTypeDescription
labelstringStage name. Matched against qualityFlagLabels to decide whether the row becomes a flag or a stage.
leadsnumberNumber of leads in the stage / flag.
colorstring?User-defined fill from the API. Ignored unless useDataColors is true.

When to use this vs the generic funnel

You're building…Use
The lead-score lifecycle widget (fixed stages from the API, optional Poor Data flag)LeadLifecycleFunnel
A checkout funnel, signup funnel, email funnel, or any "where do users drop off" questionChartFunnel
A free-form ranked bar chartBarList or MarmoBarChart
  • Chart — generic ChartFunnel for non-lifecycle funnels.
  • Widget — drop LeadLifecycleFunnel into a Widget tile.
  • Dashboard tiles — the legacy widget review explaining why this redesign exists.