Sextant
← Catalog
Block · growth

SaaS Quick Ratio

New + expansion against churn + contraction per period, with the SaaS quick ratio and the 4.0 healthy line.

State
Size
Mode

Growth · Quick Ratio

4.1

quick ratio · 4.0 is healthy

  • New + expansion
  • Churn + contraction

Quick ratio held at 4.1 — $1 lost to churn is offset by $4.1 of new + expansion; healthy SaaS runs 4.0.

Growth · Quick Ratio — quick ratio by period
PeriodQuick ratio
Q4 '252.3×
Jan '262.6×
Feb '261.8×
Mar '262.5×
Apr '263.0×
May '263.3×
Jun '263.7×
Jul '264.1×

Billing analytics · updated daily

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

How it computes

SaaS quick ratio: (new + expansion) ÷ (churn + contraction) — growth efficiency against losses.

ratio = (new + expansion) / (churn + contraction);  ∞ when losses ≤ 0

Assumptions

  • All four flows are monthly net changes, not cohorted.
  • Gains stack up and losses stack down on the chart.
  • 4.0× is treated as the healthy benchmark in the narrative.

Honest about

  • With zero churn and contraction the ratio is deliberately ∞ ("all growth, no headwind") rather than an error.
  • The 4× healthy line is a convention, not derived from your data.
  • Losses are stored negative for the stacked bars and re-absolved for the ratio — the underlying numbers stay honest.

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
periodstringyes
newMrrnumberyes
expansionnumberyes
churnnumberyes
contractionnumberyes

Static props

import { GrowthQuickRatio } from "@/components/blocks/growth-quick-ratio/growth-quick-ratio";

<GrowthQuickRatio data={myData} />

Client fetch (SWR)

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

Server component (RSC)

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

<GrowthQuickRatio data={data} />

Map arbitrary rows

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

const out = mapRows(rows, { /* contractKey: "yourColumn" */ }, dataSchema);
if (out.ok) <GrowthQuickRatio 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 (newMrr, expansion, churn, contraction, ratio)

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

Format numbers (currency / locale)

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

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

Replace or hide the narrative

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

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

Restyle any region

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

Drop the card chrome (headless plot)

import { GrowthQuickRatioPlot } from "@/components/blocks/growth-quick-ratio/growth-quick-ratio";

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