# Pairing laws — depth and adia-specific notes

_Load when a "which token" answer needs the law's reasoning or its edge
cases, not just the rule. Stable doctrine, hand-maintained; the volatile
rosters live in the two GENERATED siblings. Sources: the framework's
`component-token-contract.md` + `material-color-bridge-contract.md`
(monorepo), mirrored here for consumer installs._

## 1 · Pairing — ink on its own palette's ground

The defect this prevents: `neutral` ink on a `danger-container` fill reads
fine in light, breaks in dark — the two palettes' schemes flip on different
curves. The ink for ANY palette's surface/container/background is that same
palette's `-on-surface` family; the ink for its solid accent fill is
`-on-{p}`. If you're writing a fill from palette A and ink from palette B,
stop — one of them is the wrong role.

## 2 · States as families

`-hover/-active/-disabled` siblings are real, tuned tokens — not derivable.
`opacity: .5` on a disabled control also fades its border and any icon
differently than the tuned pair does, and `color-mix()` breaks under scheme
flip. Where the roster shows no state sibling (e.g. `background`), the role
genuinely has no state — don't invent one.

## 3 · Scheme baked in

Every role is a `light-dark()` pair. Consequences: write colors once; a
subtree forced to one scheme (`color-scheme: dark` on a preview pane)
re-resolves every role inside it automatically; per-color
`@media (prefers-color-scheme)` overrides fork the source of truth and are
the first thing to rot. The embedded-app pattern's scheme owner is the
shell element's `data-scheme`, not `html` — test dark mode there.

## 4 · Elevation = the surface ladder

Two axes, deliberately distinct: `-dim…-bright` shifts perceived light;
`-lowest…-highest` shifts stacking prominence. A card that must read
"raised" takes a `-high` tier, not a shadow bump; a recessed well takes
`-dim`/`-low`. The `--a-canvas-*` aliases map onto this ladder (see the
alias table) — pick the tier, not a hand-tuned value.

## 5 · On-accent fixed light — the operator ruling

`-on-{p}` resolves to the palette's light end in BOTH schemes, for all
palettes — including `warning`, where white-on-amber measurably misses
WCAG AA. Ruled 2026-07-16 (framework color-bridge contract, steps 9–10):
the tokens are upstream-canonical; fill-pair contrast is an upstream
design decision, accepted at kit level. Do not patch locally (dark text,
auto-contrast, per-component overrides) — a change here arrives as a
regenerated upstream export, never an app-side fix. Canvas-TEXT contrast
(body text on page surfaces) is a different matter and IS gated (the
framework's `verify:contrast`, 4.5:1 AA).

## 6 · Named purpose over coincidental value

- `--a-data-0..9` color IDENTITY (chart series, groups, tracks); the
  intent palettes color STATE. Mixing them makes a chart lie.
- Text/icons on a filled primary disc: `--a-chrome-light` (theme-stable
  against any fill) — radio dots, step circles, badge counters.
- A token whose current value happens to look right is still the wrong
  token if its name says otherwise — the next palette regeneration breaks
  exactly these.
