Sextant
← Catalog
Block · product

DAU/MAU Stickiness

DAU/MAU stickiness over time with the month-over-month move — whether the product is becoming a daily habit.

State
Size
Mode

User Stickiness · DAU/MAU

45%

DAU/MAU · 15,140 DAU

  • Stickiness

Stickiness fell -0.1% MoM to 44.8% — driven by MAU growth outpacing DAU.

User Stickiness · DAU/MAU — weekly DAU and MAU
DateDAUMAU
Jan 6, 20268,40021,800
Jan 13, 20268,95023,100
Jan 20, 20269,62024,700
Jan 27, 202610,28026,200
Feb 3, 202611,10027,900
Feb 10, 202611,82028,100
Feb 17, 202612,45028,200
Feb 24, 202613,05029,100
Mar 3, 202613,62029,800
Mar 10, 202614,10031,200
Mar 17, 202614,68032,400
Mar 24, 202615,14033,800

Product analytics · updated hourly

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

How it computes

DAU/MAU stickiness ratio per week — the share of monthly users active on a given day.

stickiness = dau / mau (0 when mau ≤ 0);  MoM compares latest vs ~4 weeks prior

Assumptions

  • DAU and MAU are reported independently per week.
  • A MAU spike without a DAU rise reads as falling stickiness.
  • DAU is assumed to be a subset of MAU (not enforced).

Honest about

  • Stickiness is 0 when MAU ≤ 0 — no divide-by-zero.
  • With fewer than five weeks of history the month-over-month baseline falls back to the latest point (delta ≈ 0).
  • The axis auto-scales above the peak; the ratio is never visually capped at 1.0.

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
datestringyes
daunumberyes
maunumberyes

Static props

import { ProductEngagementStickiness } from "@/components/blocks/product-engagement-stickiness/product-engagement-stickiness";

<ProductEngagementStickiness data={myData} />

Client fetch (SWR)

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

Server component (RSC)

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

<ProductEngagementStickiness data={data} />

Map arbitrary rows

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

const out = mapRows(rows, { /* contractKey: "yourColumn" */ }, dataSchema);
if (out.ok) <ProductEngagementStickiness 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 (stickiness)

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

Format numbers (currency / locale)

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

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

Replace or hide the narrative

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

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

Restyle any region

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

Drop the card chrome (headless plot)

import { ProductEngagementStickinessPlot } from "@/components/blocks/product-engagement-stickiness/product-engagement-stickiness";

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