Sextant
← Catalog
Block · product

Feature Adoption

Features ranked by share of active users with a core-adoption line, surfacing the leader and the laggard.

State
Size
Mode

Feature Adoption · Q2 2026

79%

Dashboards leads · 3/6 above core

  • Adoption

Adoption bottomed at 11.0% in Integrations (vs Dashboards at 79%).

Feature Adoption · Q2 2026 — feature adoption vs prior period
FeatureAdoptionPrev
Dashboards79%74%
Reports58%56%
Alerts52%47%
API38%29%
Mobile24%25%
Integrations11%10%

Product analytics · active users · updated hourly

Demo data is illustrative. Replace with your own typed data prop.

How it computes

Per-feature adoption share ranked descending; features at or above a core threshold are highlighted.

rank by adoptionPct;  core = adoptionPct ≥ coreThreshold (default 40%)

Assumptions

  • Adoption percentages are passed through directly — no recomputation.
  • Ranking is purely by adoption share, with no time-weighting.
  • Core vs non-core is a single 40% cutoff by default.

Honest about

  • This is a pure visual encoding — the numbers are exactly your inputs, with no transformation.
  • The 40% "core" line is a convention, not a derived threshold.
  • Empty data yields a "no usage yet" state; malformed data degrades to the error state via schema validation.

Use it with your data

Data contract

Passed via the data prop — an array of objects: validated at runtime, so a bad shape degrades to the error state rather than crashing.

FieldTypeRequired
featurestringyes
adoptionPctnumberyes
prevPctnumberyes

Static props

import { ProductFeatureAdoption } from "@/components/blocks/product-feature-adoption/product-feature-adoption";

<ProductFeatureAdoption data={myData} />

Client fetch (SWR)

const { data, isLoading, error } = useSWR("/api/metric", fetcher);
// route isLoading/error through <ChartStates>, then:
<ProductFeatureAdoption data={data} />

Server component (RSC)

const data = await fetch(url, {
  next: { revalidate: 3600 },
}).then((r) => r.json());

<ProductFeatureAdoption data={data} />

Map arbitrary rows

import { mapRows } from "@/lib/sextant/column-map";

const out = mapRows(rows, { /* contractKey: "yourColumn" */ }, dataSchema);
if (out.ok) <ProductFeatureAdoption data={out.data} />;

Make it yours

Every customization below is an optional prop — omit them all and you get the defaults shown above. Recolor, reformat, restyle, or drop the card chrome without forking the component.

Recolor or rename a series (core, belowCore)

<ProductFeatureAdoption
  series={{ core: { color: "var(--chart-3)", label: "…" } }}
/>

Format numbers (currency / locale)

const eur = new Intl.NumberFormat("de-DE", {
  style: "currency",
  currency: "EUR",
  notation: "compact",
});

<ProductFeatureAdoption valueFormatter={(n) => eur.format(n)} />

Replace or hide the narrative

<ProductFeatureAdoption narrative="Your own one-liner." />

// …or hide it entirely:
<ProductFeatureAdoption narrative={false} />

Restyle any region

<ProductFeatureAdoption
  className="max-w-xl"
  classNames={{ body: "bg-muted/20" }}
/>

Drop the card chrome (headless plot)

import { ProductFeatureAdoptionPlot } from "@/components/blocks/product-feature-adoption/product-feature-adoption";

// Just the chart body — bring your own card/layout:
<ProductFeatureAdoptionPlot data={data} config={config} />