Skip to content

Inverse Tokens

The problem

In a light theme (direction "down"), content is pushed to darker tones for contrast. But when a surface's tone crosses below 50 (dark enough to be "dark-mode-like"), dark content on a dark surface loses readability. The same happens in reverse for dark themes.

The solution

Systema generates inverse versions for every content and container entity:

  • inv-content -- uses the base direction's hue (so the color character stays the same) but the flipped direction's tone. If normal content goes darker, inv-content goes lighter.
  • inv-containers -- same principle applied to container backgrounds.

When to use inversions

ScenarioUse
Light card on a dark surfaceinv-containers for the card background
White text on a dark buttoninv-content for the text color
Dark overlay with light UI elementsinv-content on overlays
Accessibility mode with forced high contrastinv tokens naturally provide the opposite contrast direction

Inverse collection merging

Direct and inverse tokens live in the same Figma variable collection, which keeps the collection count low. Inverse tokens are distinguished by a path prefix:

PrefixMeaning
inv/Inverse content
on-inv/Content computed on an inverse container
inv-on-inv/Inverse content on an inverse container

The standalone inv/ path segment marks inverse content only. Inverse containers are distinguished by an inv-<entity> name segment instead (e.g. inv-containers-0), not an inv/ path prefix.

For example, in the collection color/containers (balanced structure), on surface level 0:

text
enabled/surfaces-0/containers-0/brand          -- direct container token
enabled/surfaces-0/inv-containers-0/brand      -- inverse container token (same collection)

And for content on that surface, in color/content:

text
on-surfaces/surfaces-0/enabled/c-0/brand       -- direct content token
on-surfaces/surfaces-0/inv/enabled/c-0/brand   -- inverse content token (same collection)

Contrast values

Inverse tokens may show different contrast values than their direct counterparts. This is because the flipped tone direction may hit the gamut ceiling (tone 0 or 100) sooner. The preview shows the real measured contrast for both direct and inverse swatches, computed from the actual rendered tones via contrastRatio() in src/lib/hct-engine.ts — Material's Contrast.ratioOfTones() under the WCAG model, or measured APCA Lc under the APCA model (APCA is "coming soon"). The displayed number follows the theme's active contrast model.

Fixed tokens

Fixed tokens are a mode-independent companion to the inverse system. They apply to Frame and top-layer entities only.

  • fixedEnabled is the master switch and is off by default.
  • When enabled, a Fixed seed colour and direction override the active mode, so the fixed tokens stay constant regardless of which mode is active.
  • fixedConfig chooses between inheriting the active settings or using a custom configuration. Custom uses fixedContrastRatios and fixedColorIds instead of the inherited values.
  • Fixed tokens land in single-mode color/fixed/{entityName} collections.
  • Fixed also emits inverse variants under an inv prefix, mirroring the inverse convention above.