Colour
Everything on this page is derived fromtokens/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 thresholdborder-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 usesurface-raised plus a hairline instead.
Gradients
Gradient stops stay as livevar() 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.