Tree

Sum, difference, product and quotient, each holding its operands: the form for a figure put together from factors, folded to its formula until a reader opens it.

import { Sum, Difference, Product, Quotient, Ref } from "@umriss-ui/calculation";

Product and Difference (a − b − c) take two or more operands, Quotient exactly two, and Sum any number – rows from data, one or none included (see Rows from data). A quantity used twice is defined once with an id and stands elsewhere as a Ref, which shows its name and number but never its derivation again.

Examples

Show a share against a target

format="percent" shows a ratio as per cent while it stays a ratio in every operation; a target is missed, never violated.

import { Calculation, Given, Quotient } from "@umriss-ui/calculation";

export default function ShareWithTarget() {
  return (
    <Calculation aria-label="Utilisation of the Web team, week 12">
      <Quotient label="Utilisation, Web team" format="percent" target={0.85}>
        <Given label="Hours booked on projects" value={141} unit="h" />
        <Given label="Hours the team can be planned for" value={176} unit="h" />
      </Quotient>
    </Calculation>
  );
}

Take several amounts away

Difference takes every operand after the first away from it, each with its minus in the operator column.

import { Calculation, Difference, Given } from "@umriss-ui/calculation";

export default function DifferenceOfThree() {
  return (
    <Calculation aria-label="Plannable hours, Chloe Durand, week 12">
      <Difference label="Plannable hours, Chloe" unit="h">
        <Given label="Capacity" value={40} unit="h" />
        <Given label="Leave" value={8} unit="h" />
        <Given label="Meetings" value={5} unit="h" />
        <Given label="Support rota" value={4} unit="h" />
      </Difference>
    </Calculation>
  );
}

Multiply quantities with units

A unit is a label you write, never converted or checked; decimals fixes the places shown while every operation uses the full value.

import { Calculation, Given, Product } from "@umriss-ui/calculation";

export default function ProductWithUnits() {
  return (
    <Calculation aria-label="Diesel cost of tour T-01">
      <Product label="Diesel, tour T-01" unit="€" decimals={2}>
        <Given label="Distance" value={87} unit="km" />
        <Given label="Consumption" value={0.095} unit="l/km" />
        <Given label="Diesel price" value={1.68} unit="€/l" decimals={2} />
      </Product>
    </Calculation>
  );
}

Use a quantity twice

Give a quantity an id where it first belongs and write Ref wherever it is used again; hover either place and both light up.

import { Calculation, Difference, Given, Quotient, Ref } from "@umriss-ui/calculation";

export default function UsedTwice() {
  return (
    <Calculation aria-label="Overspend of Marketing, February">
      <Quotient label="Overspend, share of budget" format="percent">
        <Difference label="Overspend" unit="€">
          <Given label="Actual, February" value={77420} unit="€" />
          <Given id="budget" label="Budget, February" value={68000} unit="€" />
        </Difference>
        <Ref to="budget" />
      </Quotient>
    </Calculation>
  );
}

Build operands from data

.map inside an operator works; folded, a line with more than four operands says how many it holds instead of a long formula.

import { Calculation, Difference, Given, Product, Quotient } from "@umriss-ui/calculation";

/* Data from the operations world, written out here so the example runs on its own. */

const at = (day: number, hours: number, minutes = 0) => new Date(2026, 2, day, hours, minutes).getTime();
const MINUTE = 60_000;

/** Tuesday, 17 March 2026, 10:30 - the moment the screens are read at. */
const NOW = at(17, 10, 30);

interface Service {
  id: string;
  name: string;
  team: string;
  /** 1 is customer-facing and pages at night; 3 waits for the morning. */
  tier: 1 | 2 | 3;
  /** The latency objective: the 95th percentile stays below this, in ms. */
  latencySlo: number;
  /** The availability promised for a month, in per cent. */
  availabilityTarget: number;
}

const SERVICES: readonly Service[] = [
  { id: "checkout", name: "Checkout", team: "Payments", tier: 1, latencySlo: 300, availabilityTarget: 99.95 },
  { id: "billing", name: "Billing", team: "Payments", tier: 1, latencySlo: 400, availabilityTarget: 99.9 },
  { id: "sign-in", name: "Sign-in", team: "Identity", tier: 1, latencySlo: 200, availabilityTarget: 99.95 },
  { id: "search", name: "Search", team: "Discovery", tier: 1, latencySlo: 250, availabilityTarget: 99.9 },
  { id: "images", name: "Image service", team: "Discovery", tier: 2, latencySlo: 500, availabilityTarget: 99.5 },
  { id: "notifications", name: "Notifications", team: "Messaging", tier: 2, latencySlo: 800, availabilityTarget: 99.5 },
  { id: "webhooks", name: "Webhooks", team: "Integrations", tier: 2, latencySlo: 1000, availabilityTarget: 99.5 },
  { id: "reports", name: "Reporting", team: "Insights", tier: 3, latencySlo: 2000, availabilityTarget: 99 },
];

/** The month so far: 1 March 00:00 to now, in minutes. */
const MONTH_MINUTES = (NOW - at(1, 0)) / MINUTE;

/** Minutes each service was down this month. With `MONTH_MINUTES` and the
    service's target, what an availability is worked out from. */
const DOWNTIME_MINUTES: Readonly<Record<string, number>> = {
  checkout: 34,
  billing: 18,
  "sign-in": 41,
  search: 85,
  images: 120,
  notifications: 12,
  webhooks: 210,
  reports: 95,
};

/* The services a booking passes through, in order. */
const PATH = ["sign-in", "search", "checkout", "billing", "notifications"];

export default function FromData() {
  return (
    <Calculation aria-label="Availability of a booking, March so far">
      <Product label="Availability of a booking" format="percent" decimals={2}>
        {SERVICES.filter((service) => PATH.includes(service.id)).map((service) => (
          <Quotient key={service.id} label={`Availability, ${service.name}`} format="percent" decimals={2}>
            <Difference label={`Minutes up, ${service.name}`} unit="min">
              <Given label="Minutes this month" value={MONTH_MINUTES} unit="min" />
              <Given label={`Minutes down, ${service.name}`} value={DOWNTIME_MINUTES[service.id]} unit="min" />
            </Difference>
            <Given label="Minutes this month" value={MONTH_MINUTES} unit="min" />
          </Quotient>
        ))}
      </Product>
    </Calculation>
  );
}

Hold a figure to its limits

limits are assessed through core's assess(); the verdict stands as a word beneath the number, never as a colour alone.

import { Calculation, Given, Quotient, Sum } from "@umriss-ui/calculation";

export default function Limits() {
  return (
    <Calculation aria-label="Cost per stop, tour T-03">
      <Quotient
        label="Cost per stop, tour T-03"
        unit="€/stop"
        decimals={2}
        limits={[
          { value: 60, side: "upper", severity: "warning" },
          { value: 100, side: "upper", severity: "alarm" },
        ]}
      >
        <Sum label="Cost of tour T-03" unit="€" decimals={2}>
          <Given label="Driver" value={216.2} unit="€" decimals={2} />
          <Given label="Diesel" value={71.37} unit="€" decimals={2} />
          <Given label="Truck, one day" value={145} unit="€" decimals={2} />
        </Sum>
        <Given label="Stops" value={5} unit="stops" />
      </Quotient>
    </Calculation>
  );
}

When to use something else

Keyboard

The keys of Calculation apply here.

API

OperatorProps

NameTypeDefaultDescription
childrenReactNode—The operands, in order: quantities, givens and references.

Shown in: Show a share against a target, Take several amounts away, Multiply quantities with units and 42 more

labelrequiredstring—What the quantity is called. It stands on its line, wherever it is an operand, and in the accessible sentence. from QuantityProps

Shown in: Show a share against a target, Take several amounts away, Multiply quantities with units and 50 more

idstring—Makes the quantity referable: <Ref to> stands for it elsewhere. Needed only for a quantity used more than once. from QuantityProps

Shown in: Use a quantity twice, The availability of a service (Installation), Write a tree (Calculation) and 10 more

unitstring—A label written after the number - "min", "pcs", "€". Never converted and never checked; the calculation does no unit algebra. from QuantityProps

Shown in: Show a share against a target, Take several amounts away, Multiply quantities with units and 42 more

format"percent"—percent presents a plain ratio as per cent: 0.916 is shown as 91.6 %. The quantity stays a ratio in every operation. from QuantityProps

Shown in: Show a share against a target, Use a quantity twice, Build operands from data and 17 more

decimalsnumberone digit for a percentage, at most two for a derived number, a given as it was givenFixed number of fraction digits as shown. Rounding happens only in presentation; every operation uses the full value. from QuantityProps

Shown in: Multiply quantities with units, Build operands from data, Hold a figure to its limits and 29 more

targetnumber—The value this quantity is meant to reach - missed, never violated. from QuantityProps

Shown in: Show a share against a target, The availability of a service (Installation), Write a tree (Calculation) and 6 more

limitsreadonly Limit[]—Bounds the quantity is assessed against, through core's assess(). from QuantityProps

Shown in: Hold a figure to its limits, Density (Calculation), Hold an interim to its limits (Chain) and 5 more

explanationstring—A sentence of plain text beside the line: why the quantity is here, or what it means. from QuantityProps

Shown in: Say where a number came from (Given), A number one metric does not have (Metrics), Staff movement as a chain (Metrics) and 2 more

asideReactNode—Content of the caller's own beside the line - a sparkline, a link. It never takes part in the calculation. from QuantityProps

Shown in: Say where a number came from (Given)

emphasis"strong" | "muted"—How much the line stands out among the others (ADR-0049): strong to be read first, muted to recede - a muted line still counts in full. Not on the Result, which is already the heaviest line. from QuantityProps

Shown in: A stronger line (Calculation), A quieter line (Calculation), Emphasis inside a group (Calculation)

rule"above"—above rules the line off from the ones before it, where a section starts that is not an interim. Not on the Result. from QuantityProps

Shown in: A rule above (Calculation), Emphasis inside a group (Calculation)

RefProps

NameTypeDefaultDescription
torequiredstring—The id of the quantity this reference stands for.

Shown in: Use a quantity twice, The availability of a service (Installation), Write a tree (Calculation) and 10 more

Types on this page

Limit

From @umriss-ui/core.

A bound: a value, a side, a severity. No tolerance band as input. A band is derived - two limits of equal severity on opposite sides enclose one - and as an input type it would force a null on one edge in the one-sided case.

Side

From @umriss-ui/core.

Whether a limit is an upper or a lower bound.

type Side = "upper" | "lower";

Severity

From @umriss-ui/core.

How seriously a violation weighs: one severity that advises and one that calls for action. Exactly two; for a third distinction one sets a second limit.

type Severity = "warning" | "alarm";

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/calculation

Getting started

Writing a calculation

In practice

API index