Raw colors and semantic colors answer different questions
A raw palette might contain blue-500, gray-50, and red-600. Those names describe a color family or scale position. A semantic layer contains names such as color-background, color-text, color-primary, and color-danger. Those names describe purpose.
:root {
--blue-600: #2563eb;
--slate-50: #f8fafc;
--slate-950: #020617;
--color-background: var(--slate-50);
--color-text: var(--slate-950);
--color-primary: var(--blue-600);
}Components should usually consume the semantic layer. A button that references --color-primary can survive a rebrand or theme change without knowing which raw shade currently supplies that role.
Start with a small role vocabulary
Do not create hundreds of semantic tokens before the product has those needs. A compact system can begin with background, surface, primary, secondary, accent, text, muted text, and border. Add success, warning, danger, focus, selected, and disabled roles when the component system requires them.
| Role | Typical job | Review question |
|---|---|---|
| Background | Page canvas | Do text, focus, and surface boundaries remain visible? |
| Surface | Cards, panels, menus | Is elevation clear without depending only on a tiny color difference? |
| Primary | Main interactive emphasis | Do text/icon combinations pass in every state? |
| Text / muted | Content hierarchy | Is muted text still readable at its actual size? |
| Border | Separation and control boundaries | Is the boundary visible where it carries functional meaning? |
Theme by remapping roles
Light and dark themes work best when components keep their semantic references and the theme changes which raw values fill those roles. This avoids component-specific inversion logic and makes theme review systematic.
[data-theme="dark"] {
--color-background: var(--slate-950);
--color-surface: var(--slate-900);
--color-text: var(--slate-50);
--color-primary: var(--blue-400);
}Tokens do not guarantee accessibility
Semantic naming improves consistency, but it does not make a color pairing accessible by itself. The same primary token may be safe as a large filled button and unsafe as small text on a white background. Document which role combinations are permitted and test component states, not just token values.
Keep export formats secondary to the model
CSS variables, JSON, SCSS, and JavaScript objects are transport formats. The important part is the stable semantic model behind them. If every platform uses the same role names and meanings, teams can generate platform-specific files without losing the design intent.
A maintainable token review
- List the raw palette independently from semantic roles.
- Assign the smallest useful role set.
- Define component-level tokens only when a component truly needs independent control.
- Map light and dark themes through the same semantic vocabulary.
- Audit allowed contrast pairings and focus/status states.
- Export to code only after the naming and role model are stable.
Practical checklist
- Keep raw palette names separate from semantic role names
- Use a small stable role vocabulary first
- Map themes through roles rather than component-specific inversions
- Test permitted role pairings and component states
- Export code only after the semantic model is stable
Sources and standards
CSS custom-property syntax in the examples was checked against the W3C specification.
Published and reviewed by the Colorrow Editorial Team on August 15, 2026. Suggestions and corrections can be sent to contact.colorrow@gmail.com.