Your design system has spacing-md, spacing-medium, and space-m all referring to 16px. Three developers made three different assumptions, and now your spacing is inconsistent across 200 components.
This is the cost of ad-hoc naming. The fix isn't a style guide that nobody reads—it's a naming convention that makes the right choice obvious.
Why Naming Matters More Than You Think
Design token names are API surface. Once they're in production code, changing them means coordinating updates across every component, every platform, every developer. Poor names create:
- Ambiguity: Does
color-text-primarymean the primary color applied to text, or the primary text color? - Inconsistency: Some tokens use
sm/md/lg, others usesmall/medium/large, others use1/2/3 - Scalability issues: You have
spacing-smandspacing-lg. Where doesspacing-xsorspacing-xlgo in the scale? - AI confusion: Code generation tools work best with predictable, systematic naming
A good naming convention makes these problems impossible.
The Anatomy of a Token Name
The most maintainable token names follow this structure:
[category]-[concept]-[variant]-[state]
Breaking this down:
Category (required): The high-level type
color,spacing,typography,border,shadow,animation
Concept (required): What role or element this applies to
- For colors:
text,background,border,surface,interactive - For spacing:
inline,stack,inset,layout - For typography:
heading,body,label,caption
Variant (optional): Size, intensity, or hierarchy
- Scale:
xs,sm,md,lg,xl,2xl, etc. - Hierarchy:
primary,secondary,tertiary - Intensity:
subtle,moderate,strong
State (optional): Interactive or contextual state
hover,active,focus,disabled,selected,error,warning
Examples
color-text-primary
color-text-secondary
color-text-disabled
color-background-surface
color-background-surface-hover
color-background-overlay
spacing-inline-sm
spacing-inline-md
spacing-stack-lg
typography-heading-xl
typography-body-md
typography-label-sm
border-radius-sm
border-width-thick
shadow-elevation-low
shadow-elevation-high
Rule 1: Use Semantic Names for Application Tokens
Application tokens (the ones used directly in components) should describe purpose, not appearance.
❌ Bad (literal):
--blue-500
--gray-200
--spacing-16
--font-18
You can't tell what these are for without context.
✅ Good (semantic):
--color-text-primary
--color-background-subtle
--spacing-stack-md
--typography-body-lg
The purpose is embedded in the name.
The Exception: Primitive Tokens
Primitive/base tokens that serve as raw values can use literal names:
/* Primitives: literal names OK */
--primitive-blue-400: #60A5FA;
--primitive-blue-500: #3B82F6;
--primitive-blue-600: #2563EB;
/* Application tokens: semantic names required */
--color-interactive-primary: var(--primitive-blue-500);
--color-interactive-primary-hover: var(--primitive-blue-600);
This two-tier system gives you flexibility at the primitive level while maintaining clarity at the application level.
Rule 2: Use Consistent Scale Notation
Pick one scale system and use it everywhere. The most common options:
T-shirt sizes (recommended for beginners)
xs, sm, md, lg, xl, 2xl, 3xl
Pros: Intuitive, easy to remember, familiar to designers
Cons: Limited granularity, what comes before xs?
Numeric scales
100, 200, 300, 400, 500, 600, 700, 800, 900
Pros: Infinite extensibility, clear ordering
Cons: Less intuitive meaning, requires documentation
Ordinal numbers
1, 2, 3, 4, 5, 6, 7, 8
Pros: Simple, compact
Cons: No semantic meaning, hard to insert between values
My recommendation: Start with t-shirt sizes. Migrate to numeric scales when you need more granularity.
Mixing scales (don't do this)
❌ Bad:
--spacing-sm /* t-shirt */
--spacing-medium /* full word */
--spacing-3 /* number */
--spacing-large /* full word again */
This creates confusion. Stick to one system.
Rule 3: Nest Concepts Hierarchically
Structure token names from general → specific.
❌ Bad (flat):
--primary-text-color
--text-color-secondary
--disabled-text
--text-error-color
The category appears in different positions, making these hard to sort and autocomplete.
✅ Good (hierarchical):
--color-text-primary
--color-text-secondary
--color-text-disabled
--color-text-error
All text color tokens are grouped together. Autocomplete becomes useful. Sorting makes sense.
Grouping in Code
This hierarchical naming makes code organization intuitive:
// tokens/color.ts
export const colorTokens = {
text: {
primary: 'hsl(0 0% 9%)',
secondary: 'hsl(0 0% 45%)',
disabled: 'hsl(0 0% 70%)',
error: 'hsl(0 84% 60%)',
},
background: {
surface: 'hsl(0 0% 100%)',
subtle: 'hsl(0 0% 97%)',
overlay: 'hsla(0 0% 0% / 0.5)',
},
border: {
default: 'hsl(0 0% 89%)',
strong: 'hsl(0 0% 70%)',
focus: 'hsl(215 100% 50%)',
},
};
When using tools like FramingUI that manage design tokens programmatically, this structure translates directly to type-safe token references.
Rule 4: Be Explicit About States
Interactive components have states. Token names should make state-specific tokens obvious.
❌ Bad (ambiguous):
--color-button: #3B82F6;
--color-button-2: #2563EB;
What is button-2? Is it a variant? A darker shade? Hover state?
✅ Good (explicit):
--color-interactive-primary: #3B82F6;
--color-interactive-primary-hover: #2563EB;
--color-interactive-primary-active: #1D4ED8;
--color-interactive-primary-disabled: #93C5FD;
No guessing required.
Common State Suffixes
-hover
-active
-focus
-disabled
-selected
-error
-warning
-success
-loading
Rule 5: Don't Encode Implementation Details
Token names should describe what, not how.
❌ Bad (implementation details):
--color-rgb-255-0-0
--spacing-16px
--font-inter-18
--border-1px-solid
These names break when the implementation changes. What if you switch from px to rem? From RGB to HSL? From Inter to a different font?
✅ Good (purpose-based):
--color-error-primary
--spacing-stack-md
--typography-body-lg
--border-width-thin
These names remain meaningful regardless of implementation.
Real-World Example: Button Component Tokens
Here's how these rules come together for a complete button system:
/* Base colors (primitives) */
--primitive-blue-400: #60A5FA;
--primitive-blue-500: #3B82F6;
--primitive-blue-600: #2563EB;
--primitive-blue-700: #1D4ED8;
/* Application tokens (semantic) */
--color-interactive-primary: var(--primitive-blue-500);
--color-interactive-primary-hover: var(--primitive-blue-600);
--color-interactive-primary-active: var(--primitive-blue-700);
--color-interactive-primary-disabled: var(--primitive-blue-400);
--color-text-on-primary: #FFFFFF;
--color-text-on-primary-disabled: rgba(255, 255, 255, 0.6);
--spacing-inline-sm: 0.75rem;
--spacing-inline-md: 1rem;
--spacing-inline-lg: 1.5rem;
--spacing-stack-xs: 0.5rem;
--spacing-stack-sm: 0.75rem;
--border-radius-md: 0.375rem;
--border-radius-lg: 0.5rem;
--typography-label-sm: 0.875rem;
--typography-label-md: 1rem;
Using these tokens in a component:
// Button.tsx
const buttonStyles = {
backgroundColor: 'var(--color-interactive-primary)',
color: 'var(--color-text-on-primary)',
paddingInline: 'var(--spacing-inline-md)',
paddingBlock: 'var(--spacing-stack-sm)',
borderRadius: 'var(--border-radius-md)',
fontSize: 'var(--typography-label-md)',
'&:hover': {
backgroundColor: 'var(--color-interactive-primary-hover)',
},
'&:active': {
backgroundColor: 'var(--color-interactive-primary-active)',
},
'&:disabled': {
backgroundColor: 'var(--color-interactive-primary-disabled)',
color: 'var(--color-text-on-primary-disabled)',
},
};
Every token name clearly communicates its purpose.
Naming for AI Code Generation
AI tools like Claude, Cursor, and GitHub Copilot benefit from consistent naming patterns. When token names follow predictable conventions, AI can:
Autocomplete accurately:
// AI sees this pattern:
color-text-primary
color-text-secondary
// And correctly suggests:
color-text-tertiary // ✅
color-text-error // ✅
// Instead of:
text-color-3 // ❌
color-tertiary-text // ❌
Generate variants systematically:
// You define:
spacing-inline-sm
spacing-inline-md
// AI generates:
spacing-inline-lg // ✅ follows pattern
spacing-inline-xl // ✅ extends logically
Infer relationships:
When you use a semantic token like color-interactive-primary, AI tools can infer you'll need color-interactive-primary-hover, color-text-on-interactive-primary, etc.
Tools like FramingUI make this even more powerful by providing AI agents direct access to your token schema, enabling them to suggest tokens that actually exist in your system.
Creating Your Naming Convention
Here's a practical process:
1. Document Your Structure
Create a naming convention guide:
# Token Naming Convention
## Structure
[category]-[concept]-[variant]-[state]
## Categories
- color
- spacing
- typography
- border
- shadow
- animation
## Scale System
xs, sm, md, lg, xl, 2xl, 3xl
## State Suffixes
-hover, -active, -focus, -disabled
2. Define Token Categories
List out all token types you'll need:
// tokens/schema.ts
export const tokenCategories = {
color: ['text', 'background', 'border', 'surface', 'interactive'],
spacing: ['inline', 'stack', 'inset', 'layout'],
typography: ['heading', 'body', 'label', 'caption', 'code'],
border: ['radius', 'width'],
shadow: ['elevation', 'color'],
animation: ['duration', 'easing'],
};
3. Establish Variants
Define your scales:
// tokens/scales.ts
export const sizeScale = ['xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl'];
export const hierarchyScale = ['primary', 'secondary', 'tertiary'];
export const intensityScale = ['subtle', 'moderate', 'strong'];
4. Generate Tokens Programmatically
Use your convention to generate tokens systematically:
// tokens/generate.ts
const textColorTokens = hierarchyScale.map(level => ({
name: `color-text-${level}`,
value: textColors[level],
}));
const spacingInlineTokens = sizeScale.map(size => ({
name: `spacing-inline-${size}`,
value: spacingValues[size],
}));
Migrating Existing Tokens
Already have inconsistent token names? Here's how to migrate:
1. Audit Current Tokens
# Find all CSS custom properties
grep -r "\-\-" src/ | grep ":" | cut -d: -f1 | sort | uniq
2. Create Mapping
// tokens/migration-map.ts
export const tokenMigration = {
// Old name → New name
'blue-500': 'color-interactive-primary',
'gray-200': 'color-background-subtle',
'spacing-md': 'spacing-inline-md',
'font-lg': 'typography-body-lg',
};
3. Automated Replacement
// scripts/migrate-tokens.ts
import { tokenMigration } from './tokens/migration-map';
// Replace old tokens with new ones across codebase
Object.entries(tokenMigration).forEach(([oldToken, newToken]) => {
// Use your preferred find-and-replace tool
// Be careful with partial matches!
});
4. Deprecation Period
Support both old and new names temporarily:
:root {
/* New name (canonical) */
--color-interactive-primary: #3B82F6;
/* Old name (deprecated, will be removed) */
--blue-500: var(--color-interactive-primary);
}
Add console warnings when deprecated tokens are used, then remove after a migration period.
Common Pitfalls to Avoid
Pitfall 1: Over-abstraction
❌ Bad:
--token-a
--token-b
--token-c
Too abstract. No meaning.
✅ Good:
--color-text-primary
--color-text-secondary
--color-text-tertiary
Pitfall 2: Over-specification
❌ Bad:
--color-button-primary-background-hover-with-icon-left-aligned
Too specific. This becomes unmaintainable.
✅ Good:
--color-interactive-primary-hover
Component layout concerns don't belong in token names.
Pitfall 3: Inconsistent Casing
❌ Bad:
--Color-Text-Primary /* PascalCase */
--color_text_secondary /* snake_case */
--colorTextTertiary /* camelCase */
✅ Good:
--color-text-primary
--color-text-secondary
--color-text-tertiary
Pick one: kebab-case for CSS, camelCase for JavaScript objects.
Conclusion
Good token naming is boring and predictable—and that's the point. When everyone can predict what a token should be named, your design system becomes self-documenting.
Follow these rules:
- Semantic names for application tokens, literal for primitives
- Consistent scales (pick one, use everywhere)
- Hierarchical structure (general → specific)
- Explicit states (no ambiguous suffixes)
- Purpose over implementation (what, not how)
When you get naming right, everything else gets easier: onboarding new developers, AI code generation, refactoring, theming, multi-platform support.
Your design system becomes a multiplier instead of a bottleneck.
Tools like FramingUI help enforce these conventions by managing tokens as structured data rather than scattered CSS variables, making it impossible to introduce inconsistent names and enabling AI agents to work with your design system reliably.
Start with a small, well-named token set. Expand systematically. Your future self (and your AI coding assistant) will thank you.