Semantic roles beat colour names
A palette of named colours does not survive contact with a product. Roles do — and they make the accessibility checks answerable.
Every design system eventually learns the same lesson: --blue-600is a fact about a colour, not an instruction about where to use it. The moment the brand shifts, every usage has to be re-read to work out which ones meant “primary action” and which ones just happened to be blue.
Two layers, not one
Keep the primitive layer — the actual values, named for what they are — and add a semantic layer on top that names what they do:
--color-background,--color-surface,--color-border--color-text,--color-text-muted--color-primary,--color-accent--color-success,--color-warning,--color-danger
Components reference only the semantic layer. Rebranding then means changing the mapping in one file, and a dark theme is a second mapping rather than a second codebase.
Roles make accessibility checkable
This is the part that pays for itself. Once a colour has the role “text” and another has “background”, the question “does this palette work?” stops being vague. There is a specific list of pairs that must pass, at specific thresholds, and it can be checked automatically.
That is exactly what Colorstruct’s inspector does with the roles you assign: body text on page, muted text on page, primary control on page, border on surface. Unassigned colours are simply not checked, because there is nothing to check them against.
A practical starting set
Five to seven colours covers most interfaces: a page background, a surface, a text colour, a muted text colour, a primary, an accent, and a border. Status colours can often be derived from the accent hue rather than added as new hues, which keeps the palette coherent.