The two-layer token model
A semantic color token system has two distinct layers. The primitive layer contains the full color vocabulary — every shade available, named by color and lightness step. Primitive names are descriptive but not meaningful: --color-teal-300 is a specific lightness of teal, nothing more. The semantic layer maps intent onto primitives — --color-action-default might reference --color-teal-300 in the light theme and --color-teal-400 in the dark theme. Components consume only semantic tokens. This separation is the key insight: components become indifferent to the underlying color values because they only care about semantic intent. A button that uses --color-action-default will look correct in any theme as long as the token is mapped correctly.
Common semantic categories
A minimal semantic layer needs five categories to cover most interface needs. Surface tokens describe backgrounds at various depths: surface-primary for page background, surface-secondary for card backgrounds, surface-elevated for floating layers. Border tokens describe edge treatments: border-default, border-subtle, border-focus. Text tokens describe content: text-primary, text-secondary, text-placeholder, text-inverse. Interactive tokens describe action states: action-default, action-hover, action-pressed, action-disabled. Status tokens describe system feedback: status-success, status-warning, status-error, status-info. The Nocturne Tech collection maps cleanly onto this framework — every color in the set has an obvious semantic role, which is why it exports so cleanly as a token set.
Starting with semantic tokens in a new project
The fastest way to adopt semantic tokens is to start from a well-structured collection and assign semantic names to the colors you already know you need. A useful first pass is a small set of CSS custom properties organized by semantic role — surface, border, text, interactive, and status — defined for both light and dark themes. The hardest part of semantic naming is resisting the temptation to use color names — calling a token --color-blue-hover instead of --color-action-hover defeats the purpose. A useful rule: if you can replace a semantic token name with a color name and the name still makes sense, it is not semantic enough. Surface, action, status, border, and text are semantic. Blue, coral, and amber are not.