DAU/MAU Stickiness
DAU/MAU stickiness over time with the month-over-month move — whether the product is becoming a daily habit.
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.
| Date | DAU | MAU |
|---|---|---|
| Jan 6, 2026 | 8,400 | 21,800 |
| Jan 13, 2026 | 8,950 | 23,100 |
| Jan 20, 2026 | 9,620 | 24,700 |
| Jan 27, 2026 | 10,280 | 26,200 |
| Feb 3, 2026 | 11,100 | 27,900 |
| Feb 10, 2026 | 11,820 | 28,100 |
| Feb 17, 2026 | 12,450 | 28,200 |
| Feb 24, 2026 | 13,050 | 29,100 |
| Mar 3, 2026 | 13,620 | 29,800 |
| Mar 10, 2026 | 14,100 | 31,200 |
| Mar 17, 2026 | 14,680 | 32,400 |
| Mar 24, 2026 | 15,140 | 33,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 priorAssumptions
- 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.
| Field | Type | Required |
|---|---|---|
| date | string | yes |
| dau | number | yes |
| mau | number | yes |
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} />