Gross Margin Bridge
Walks gross profit period-over-period, isolating revenue and COGS effects, with a gross-margin-% hero and a bridge narrative.
Gross margin bridge — Q1 2026 · Q4 2025 → Q1 2026
$3.08M
+$391k gross profit · 63.5% margin
- Increase
- Decrease
- Balance
Gross profit rose from $2.69M to $3.08M in Q4 2025 → Q1 2026, led by Revenue (+$650k).
| Step | Change | Running |
|---|---|---|
| Start | $2.69M | |
| Revenue | +$650k | $3.34M |
| COGS | -$259k | $3.08M |
| End | +$391k | $3.08M |
Acme · GL sync · updated daily
Demo data is illustrative. Replace with your own typed data prop.
How it computes
Two-step gross-profit bridge decomposing the period-over-period change into revenue and COGS moves.
gm₀ = rev₀ − cogs₀; steps = [Δrev = rev₁−rev₀, ΔCOGS = −(cogs₁−cogs₀)]; gm% = gm₁/rev₁Assumptions
- Revenue and COGS are given at both period boundaries.
- A COGS increase is shown as a downward (unfavorable) move via negation.
- Margin % uses end-period revenue, per standard accounting.
Honest about
- Exactly two drivers (revenue, COGS) — opex, tax, and mix are out of scope.
- End-period margin is 0 (not ∞) when end revenue = 0.
- No product or channel mix split: the margin move is consolidated.
Use it with your data
Data contract
Passed via the data prop — an object: validated at runtime, so a bad shape degrades to the error state rather than crashing.
| Field | Type | Required |
|---|---|---|
| p0 | object | yes |
| p1 | object | yes |
Static props
import { FpaGrossMarginBridge } from "@/components/blocks/fpa-gross-margin-bridge/fpa-gross-margin-bridge";
<FpaGrossMarginBridge data={myData} />Client fetch (SWR)
const { data, isLoading, error } = useSWR("/api/metric", fetcher);
// route isLoading/error through <ChartStates>, then:
<FpaGrossMarginBridge data={data} />Server component (RSC)
const data = await fetch(url, {
next: { revalidate: 3600 },
}).then((r) => r.json());
<FpaGrossMarginBridge data={data} />Map arbitrary rows
import { mapRows } from "@/lib/sextant/column-map";
const out = mapRows(rows, { /* contractKey: "yourColumn" */ }, dataSchema);
if (out.ok) <FpaGrossMarginBridge 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 (gain, loss, anchor)
<FpaGrossMarginBridge
series={{ gain: { color: "var(--chart-3)", label: "…" } }}
/>Format numbers (currency / locale)
const eur = new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR",
notation: "compact",
});
<FpaGrossMarginBridge valueFormatter={(n) => eur.format(n)} />Replace or hide the narrative
<FpaGrossMarginBridge narrative="Your own one-liner." />
// …or hide it entirely:
<FpaGrossMarginBridge narrative={false} />Restyle any region
<FpaGrossMarginBridge
className="max-w-xl"
classNames={{ body: "bg-muted/20" }}
/>