Token naming is not a stylistic preference. It is a structural decision that determines whether your design system can be mechanically operated by both humans and tools. A name like --color-interactive-500 does something a hex value cannot: it tells every reader of that token what role it plays, in which state, at which scale position. The hex value #4F46E5 tells you a color. Nothing else.
This post walks through the naming conventions we have seen work in production, the conventions that look reasonable but degrade under maintenance pressure, and the specific properties that make a token name resolvable for tools like Flowstep.
The alias chain: how token names acquire meaning
A design token system typically has two layers. The primitive layer defines raw values with no interpretation:
/* Primitive tokens */
--color-violet-100: #EDE9FE;
--color-violet-200: #DDD6FE;
--color-violet-300: #C4B5FD;
--color-violet-400: #A78BFA;
--color-violet-500: #8B5CF6;
--color-violet-600: #7C3AED;
--color-violet-700: #6D28D9;
--color-violet-800: #5B21B6;
--color-violet-900: #4C1D95;
The semantic layer takes those primitives and assigns them roles:
/* Semantic tokens */
--color-interactive-default: var(--color-violet-700);
--color-interactive-hover: var(--color-violet-800);
--color-interactive-disabled: var(--color-violet-300);
--color-interactive-text: #FFFFFF;
The alias chain is the link from semantic to primitive. When this chain is intact, changing the brand color from violet to blue means updating the primitive scale and watching the semantic layer follow automatically. When the chain is broken (a semantic token pointing directly at a hex value instead of a primitive), that propagation breaks.
Most teams understand this pattern in principle. The challenge is maintaining it under time pressure. When someone needs to add a new state quickly, the fastest path is a hardcoded hex value directly in the component token. That decision, made dozens of times, is where design token drift originates.
Naming structures that hold up
After working through a significant number of design system configurations, a few naming patterns consistently produce token files that are both maintainable by humans and resolvable by tools.
Category-role-state
The most reliable structure encodes three things in sequence: the category (what type of property), the role (what UI function), and the state (what interaction condition).
--color-surface-primary-default
--color-surface-primary-hover
--color-surface-primary-disabled
--color-border-interactive-default
--color-border-interactive-focus
--color-border-interactive-error
--spacing-component-button-horizontal
--spacing-component-button-vertical
--spacing-component-input-horizontal
The verbosity is a feature, not a bug. When a token name is read in a generated file, a developer or another tool can infer the token's role without looking up its value. The token name is documentation.
Component-scoped tokens as the downstream anchor
Component-scoped tokens are the most resolvable layer for generation purposes. A component token is explicit about its target:
--button-bg-primary-default: var(--color-interactive-default);
--button-bg-primary-hover: var(--color-interactive-hover);
--button-bg-primary-disabled: var(--color-interactive-disabled);
--button-text-primary: var(--color-interactive-text);
--button-border-radius: var(--radius-md);
The cost of component tokens is maintenance surface. Every component in the system gets its own token namespace, which means changes to a component's visual language require updating both the component tokens and the semantic tokens they alias from. For a team of two or three, this overhead is real. For teams with a more complex system and multiple platforms, component tokens are often the only way to maintain cross-platform consistency without per-platform forks.
We are not arguing that every team needs full component token coverage. We are saying that when component tokens exist, generation can be precise. When they do not exist, generation works from semantic tokens, which requires more inference.
Names that look reasonable but degrade
Generic role names without state encoding
A token named --color-primary carries no state information. Is it the default state of a primary interactive element? The pressed state? The visited state? The background of a primary surface? The answer matters for different components. A system that uses --color-primary throughout its component definitions is forcing developers to know the intent from context, not from the name.
The degradation appears when a second designer joins the team. They look at --color-primary and apply it to a component where the first designer would have used a specific interaction state. Both choices look correct from the token name. The visual output is inconsistent.
The color-500 shorthand as a semantic name
Using scale positions as semantic names is a common shortcut: --color-interactive-500 where 500 is a Tailwind-style scale position. This is better than a raw hex because it is at least named and lives in the semantic layer. But the 500 suffix encodes a primitive position, not a semantic role.
The problem surfaces when you need a hover state. Is hover --color-interactive-600? That requires every consumer of the token to know the scale convention and infer the hover step from it. Compare this to --color-interactive-hover, which states the intent directly.
This is a tradeoff, not a hard error. Scale-position names work well for utility systems where the caller is expected to choose the appropriate step. They work poorly for component-level design systems where the intent is for tokens to encode design decisions, not palette positions.
Flat namespaces in large systems
A flat namespace means all tokens at the same level with no nesting:
--primary: #6D28D9;
--primary-dark: #5B21B6;
--primary-light: #8B5CF6;
--secondary: #0EA5E9;
--error: #EF4444;
--warning: #F59E0B;
At 20 tokens this is manageable. At 200 tokens in a system with multiple themes and platform targets, a flat namespace without category structure becomes difficult to navigate and nearly impossible to parse programmatically without additional documentation.
Systems that start flat and grow organically often never recover the structure. Adding nesting later requires renaming tokens that are already referenced in production code, which has a non-trivial migration cost. The structural investment is much cheaper at the start than as a refactor.
What makes a token name resolvable
For a tool like Flowstep, or any system that needs to interpret a design brief against a token set, resolvability comes down to three properties.
First: the token name encodes its role, not just its value. --color-interactive-default is resolvable because "interactive" and "default" are role descriptors a system can match against a brief that says "primary action button". --color-violet-700 is not resolvable in the same way because 700 is a scale position, not a role.
Second: states are explicit. A system that has separate tokens for default, hover, focus, disabled, and pressed states is unambiguous. A system that uses a single token for all states of a component requires the consumer to apply the right value in the right context, which is a human judgment call, not a mechanical one.
Third: the alias chain is intact. Semantic tokens alias to primitives, not to raw values. When this chain is broken, the connection between the semantic layer and the source of truth is severed. Updating the primitive does not propagate, and generation tools cannot walk the chain to verify consistency.
None of these properties require a specific naming convention. They require intentional structure. A system can use snake_case, camelCase, or kebab-case as long as the role, category, and state information is present in the name and the alias chain is intact.
A note on completeness vs. coverage
A complete token system is not necessarily a good one. A system that has 800 tokens but inconsistent state encoding and broken alias chains is harder to work with than a system that has 120 tokens with complete semantic coverage for the components it actually uses.
The goal is not exhaustive coverage of every possible UI state in the abstract. The goal is precise coverage of the components and states that the product actually uses, with names that encode intent and alias chains that propagate changes correctly. A smaller, well-structured system is almost always more maintainable than a large, inconsistent one, and it is certainly more resolvable for any tool that needs to interpret it.