Design Color Tokens with Explicit Roles and Tested Pairs
Build primitive, semantic and component tokens from a small palette, then export and test the roles that consume them.
Updated : Rechecked technical claims, replaced unsupported generalizations, and added reproducible examples and source links.
A useful color token tells a maintainer what may change together. A primitive names a value; a semantic token names a role. When a brand shade changes, inspect the semantic roles that depend on it rather than replacing every matching HEX string.
Write a small dependency chain
:root {
--blue-700: #1D4ED8;
--neutral-0: #FFFFFF;
--action-primary: var(--blue-700);
--text-on-action: var(--neutral-0);
}
.button-primary {
background: var(--action-primary);
color: var(--text-on-action);
}The primitive blue is reusable. The action token commits it to an interactive surface. The text-on-action token records the pairing that makes the button readable. Avoid calling a color "accessible-blue": its contrast depends on its partner and role.
Record the pair, not a palette-wide score
A reproducible text and background example.
Same pair, larger text.
##FFFFFF / ##1D4ED8
Text-on-action / action-primary in the sample tokens.
1.000000:1 WCAG contrast; 0.0000 Lc APCA.
Normal-text AA: Fail. Large-text AA: Fail. Labels apply to text contrast only.
Reproduce this pair and try a fixStore the two inputs, target category and result with the component. If a dark theme changes either token, run the check again. Hover, pressed and focus styles deserve separate records because their neighboring surfaces may differ.
Choose a named interchange format
CSS custom properties, a generic JSON object and DTCG tokens serve different consumers. DTCG 2025.10 represents an sRGB color as an object with colorSpace and normalized components. A string such as "#1D4ED8" alone is not that color-value object. Check what your importer expects rather than assuming every JSON export is interchangeable.
{
"action": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.11372549019607843, 0.3058823529411765, 0.8470588235294118],
"alpha": 1,
"hex": "#1d4ed8"
}
}
}Export, import and inspect
Open the sample palette in Studio, export the format your project uses, and add role aliases in that project. Generated tints and shades are raw choices; import success does not mean every resulting component passes its checks.