Opacity

The only scale with no appearance of its own — it is a relationship to whatever sits behind it.

base × N like the other scales, with a base of 4%, so --opacity-8 is 32%. Sub-base steps use the same fraction convention as everywhere else: -0-25 and -0-5 are 1% and 2%, and -1-5 is 6%.

The previews below are the token used as a mix rather than as the opacity property, which is what every consumer in this system actually does — and the only preview that stays honest, since a partly transparent thing has no colour until something is behind it.

Levels

Thirteen steps, and the intervals between them are not even.

TokenValuePreview
--opacity-0…
--opacity-0-25…
--opacity-0-5…
--opacity-1…
--opacity-1-5…
--opacity-2…
--opacity-3…
--opacity-4…
--opacity-6…
--opacity-8…
--opacity-12…
--opacity-16…
--opacity-20…
--opacity-25…

The steps bunch at the transparent end and thin out toward opaque, which looks arbitrary written down and is obvious on screen: 4% to 8% is a doubling and reads as a clear change, while 80% to 100% is barely a difference at all. Fine control is worth having exactly where small numbers do visible work — tints, overlays, hairline fills — and worth nothing at the top, where the only question left is whether a thing is opaque.

Almost everything this system draws in a tint sits in the bottom third. The guide overlay is --opacity-1, its dashed edge --opacity-8, the content overlay --opacity-1 again — the whole visualisation layer on two steps. The top of the scale is unused so far, and stays for the same reason the unreached breakpoints do: it is deliverable, just not yet needed.

Read the number as a multiplier, not a percentage. --opacity-4 is 16%, and --opacity-25 is 100%. That is the same base × N rule the whole system uses, but this is the one category where it can be misread, because the value happens to be a percentage too — everywhere else the multiplier produces px and no confusion is possible.

Percentages, not decimals

The values are stored as 64% rather than 0.64, and that single choice is what lets one token serve both uses. opacity accepts either — but color-mix() requires a percentage, so a decimal token would work in one place and silently fail in the other. Percentages cost nothing and remove the fork.

It fails silently, which is the part worth knowing: an invalid percentage does not warn, it makes the whole color-mix() invalid, and the declaration is dropped as though it had never been written. --opacity-0 was exactly this until this page was built — it held a bare 0, so any mix using it was discarded. Nothing looked wrong, because a dropped background and a 0% mix are the same nothing. It is 0% now.