Skip to main content

Colour

Everything on this page is derived from tokens/modulus-design-tokens.json at build time. No value, name or group is written by hand, so adding a token to the JSON makes it appear here with no code change. Light and dark are shown side by side rather than through the toolbar toggle, so the pairing is comparable at a glance. Swatches are painted with the resolved hex over a chequerboard, so any baked-in alpha is visible.

Semantic roles

Groups are the prefixes the roles already carry in the token file. A prefix shared by more than one role becomes its own group; roles with a unique first segment collect into the last group. Group order follows the file.

Contrast

This is the point of the page. Several values were tuned to clear specific ratios, and those numbers currently live only in $description strings that nobody opens. Every ratio below is computed live from the resolved values, not copied from a description. Where a description records a figure, the measured number is shown against it and any disagreement is flagged — a disagreement means a value moved without its description following. Each foreground role is measured against both surface-page and surface-card, in both modes, and the worst of the two is what counts. Thresholds: 4.5 and above passes, 3.0 to 4.5 is large text only, below 3.0 fails.

Full per-surface breakdown

The worst case above is the minimum of these.

Foregrounds that sit on a fill

A role designed for a fill is measured against that fill, not against the page. cta-text on surface-page would be white on near-white — a comparison that never happens on screen, so reporting it as a failure would be noise. Which roles land here is derived from the names: -on-bg is explicit, and <x>-text pairs with <x>-bg unless the role’s description says it sits on transparent, which is what separates destructive-text from cta-text. Hover and active fills are included because the token file records label ratios against those states specifically.

Borders and other interface elements

Judged against the 3.0 floor for interface elements rather than 4.5, which is the threshold border-strong’s own description refers to. border-subtle is intentionally quiet — it is a divider, not an interface boundary.

Special case: text on the high-intent gradient

cta-intent-text sits on a gradient, and axe reports a gradient background as incomplete — it cannot resolve one background colour, so the automated a11y check will never pass or fail this pairing. These are the only numbers available for it, computed against each stop of the gradient token.

Special case: disabled opacity

disabled-opacity is not a colour, so it has no ratio of its own. What a reader actually sees is text-primary blended at that opacity over the surface, so that composite is what is measured.

Shadows

Both shadows resolve to a fully transparent colour in dark mode, which is deliberate: raised surfaces there use surface-raised plus a hairline instead.

Gradients

Gradient stops stay as live var() references in the generated CSS rather than being flattened to hex, so re-theming a primitive moves the gradient with it.

Effects

Opacity is stored as a percentage to match Figma, where the importer writes values verbatim. The build divides by 100 for CSS, driven by $extensions."com.modulus.cssTransform" rather than by token name.

Primitive ramps

The raw ramps the semantic roles reference. These carry no mode of their own.