Drivers
The driver tree breaks a result - operating profit, cash, margin, churn - into the drivers that make it, down to the levers people actually pull: price, volume, conversion, headcount. Each driver is a card with its value and its change against the plan, the actuals or any scenario, in green or red by which way is better; formulas are written in plain words, and a driver read in two places is drawn once with a link where it appears again.
Choose an input and move its slider: everything above it is worked out again at once, with how much a 1% rise moves the result. Save the what-if as a scenario, compare scenarios side by side, goal-seek a target, rank the inputs in a tornado chart, and explain the gap between plan and actual with a bridge whose steps always add up. The same model runs in Node, for a monthly job that writes the bridge into a report.
A shop's monthly profit
A fictional online homeware shop's October, shown as actual against plan. Advertising spend and orders each drive two things, so each appears twice. Choose an input and move its slider, try the upside, ask what conversion makes £30,000, or explain the gap.
Get the code
<script src="/assets/bmx-components.min.js"></script> <bmx-driver-tree id="dr-shop" label="Lantern & Loom, October 2026" style="inline-size: 100%; --bmx-driver-tree-height: 36rem"></bmx-driver-tree>
What moves subscription revenue
A subscription business's next twelve months of revenue, from customers won, customers lost and what each pays, opened on the sensitivity view: which assumptions matter most, moved 10% either way - or each across its own range.
// Node 18 or later, from the Gold package: load the month's actuals into the model and write the bridge.
import { readModel, compileModel, scenarioInputs, bridge, formatDriver } from './dist/node/bmx-driver-tree.mjs';
const { model } = readModel(JSON.parse(await readFile('profit.json', 'utf8')));
model.actuals = await loadActualsFromLedger('2026-10');
const gap = bridge(compileModel(model), scenarioInputs(model, 'plan'), scenarioInputs(model, 'actual'), model.root);
for (const step of gap.steps) report.line(step.id, formatDriver(model.nodes.find(n => n.id === model.root), step.delta, model));