Semantic Tokens
Semantic tokens are named aliases that map to your primitive design tokens, providing meaningful, context-specific names for your design system.
What are semantic tokens?
Semantic tokens give meaningful names to your primitive tokens, making your design system more maintainable and easier to understand.
Instead of using generic names like primary-500 or neutral-700, you can use semantic names like:
text-primary- for primary text colorbackground-surface- for surface backgroundsborder-default- for default borderstext-error- for error messages
These semantic names map to your primitive tokens, creating a layer of abstraction.
Why use semantic tokens?
Better communication
Semantic names communicate intent, not just appearance:
/* Without semantic tokens */
.button-primary {
background: var(--color-primary-500);
color: var(--color-neutral-0);
}
/* With semantic tokens */
.button-primary {
background: var(--background-primary);
color: var(--text-on-primary);
}The semantic version is clearer about the button’s purpose.
Easier updates
When design changes, you only update the mapping:
// Before design change
{
"semantic": {
"text-primary": "color-primary-500"
}
}
// After design change - only update mapping
{
"semantic": {
"text-primary": "color-primary-600"
}
}All components using text-primary automatically get the new color.
Consistent theming
Semantic tokens make it easier to support multiple themes:
// Light theme
{
"semantic": {
"background-surface": "color-neutral-0",
"text-primary": "color-neutral-900"
}
}
// Dark theme
{
"semantic": {
"background-surface": "color-neutral-900",
"text-primary": "color-neutral-0"
}
}Defining semantic tokens
Basic configuration
Add semantic tokens to your ngcorex.config.ts:
import { defineConfig } from "@ngcorex/css";
export default defineConfig({
tokens: {
colors: {
primary: {
500: "#3b82f6",
},
neutral: {
0: "#ffffff",
900: "#171717",
},
},
},
semantic: {
"text-primary": "color-primary-500",
"text-on-primary": "color-neutral-0",
"background-primary": "color-primary-500",
"background-surface": "color-neutral-0",
"text-on-surface": "color-neutral-900",
},
});Generated CSS
ngCorex generates CSS variables for both primitive and semantic tokens:
:root {
/* Primitive tokens */
--color-primary-500: #3b82f6;
--color-neutral-0: #ffffff;
--color-neutral-900: #171717;
/* Semantic tokens */
--text-primary: var(--color-primary-500);
--text-on-primary: var(--color-neutral-0);
--background-primary: var(--color-primary-500);
--background-surface: var(--color-neutral-0);
--text-on-surface: var(--color-neutral-900);
}Using semantic tokens
In CSS
Use semantic variables in your stylesheets:
.button {
background: var(--background-primary);
color: var(--text-on-primary);
padding: var(--spacing-md);
border-radius: var(--radius-md);
}
.card {
background: var(--background-surface);
color: var(--text-on-surface);
border: 1px solid var(--border-default);
box-shadow: var(--shadow-md);
}
.alert-error {
background: var(--background-error);
color: var(--text-on-error);
padding: var(--spacing-md);
border-radius: var(--radius-md);
}In components
Semantic tokens make components more readable:
/* Without semantic tokens */
.header {
background: var(--color-neutral-900);
color: var(--color-neutral-0);
}
/* With semantic tokens */
.header {
background: var(--background-header);
color: var(--text-on-header);
}Semantic token patterns
Text colors
{
"semantic": {
"text-primary": "color-neutral-900",
"text-secondary": "color-neutral-700",
"text-tertiary": "color-neutral-500",
"text-inverse": "color-neutral-0",
"text-primary-on-primary": "color-neutral-0",
"text-error": "color-error-500",
"text-success": "color-success-500",
"text-warning": "color-warning-500"
}
}Background colors
{
"semantic": {
"background-surface": "color-neutral-0",
"background-surface-alt": "color-neutral-100",
"background-primary": "color-primary-500",
"background-error": "color-error-100",
"background-success": "color-success-100",
"background-warning": "color-warning-100"
}
}Border colors
{
"semantic": {
"border-default": "color-neutral-300",
"border-strong": "color-neutral-500",
"border-primary": "color-primary-500",
"border-error": "color-error-500"
}
}Spacing
{
"semantic": {
"spacing-component": "spacing-md",
"spacing-section": "spacing-lg",
"spacing-page": "spacing-xl"
}
}Best practices
1. Use descriptive names
Good:
text-primary- clearly indicates it’s for primary textbackground-surface- indicates it’s for surface backgroundsborder-default- indicates it’s the default border
Bad:
text-1- doesn’t indicate purposebg-2- unclear meaningborder-a- meaningless
2. Group by purpose
Organize semantic tokens by their use case:
{
"semantic": {
// Text
"text-primary": "...",
"text-secondary": "...",
// Backgrounds
"background-surface": "...",
"background-primary": "...",
// Borders
"border-default": "...",
"border-strong": "..."
}
}3. Use consistent prefixes
Use prefixes to group related tokens:
text-*for text colorsbackground-*for backgroundsborder-*for bordersspacing-*for spacingradius-*for border radius
4. Document your semantic tokens
Keep a reference of what each semantic token represents:
{
"semantic": {
// Primary action elements (buttons, links)
"background-primary": "color-primary-500",
// Main content areas
"background-surface": "color-neutral-0",
// Primary text on light backgrounds
"text-primary": "color-neutral-900"
}
}5. Keep semantic tokens stable
Avoid changing semantic token names. If you need to change the underlying value, update the mapping instead:
// Don't do this - breaks all references
// "text-primary-old" -> "color-primary-500"
// "text-primary" -> "color-primary-600"
// Do this - keeps references intact
// "text-primary" -> "color-primary-600" (was "color-primary-500")Advanced patterns
Theme switching
Use semantic tokens with multiple themes:
// light-theme.json
{
"colors": {
"neutral": { "0": "#ffffff", "900": "#171717" },
"primary": { "500": "#3b82f6" }
},
"semantic": {
"background-surface": "color-neutral-0",
"text-primary": "color-neutral-900"
}
}
// dark-theme.json
{
"colors": {
"neutral": { "0": "#171717", "900": "#ffffff" },
"primary": { "500": "#60a5fa" }
},
"semantic": {
"background-surface": "color-neutral-0",
"text-primary": "color-neutral-900"
}
}Switch themes by loading different config files.
Component-specific semantics
Create semantic tokens for specific components:
{
"semantic": {
// Button
"button-primary-bg": "color-primary-500",
"button-primary-text": "color-neutral-0",
// Card
"card-bg": "color-neutral-0",
"card-border": "color-neutral-300",
// Alert
"alert-error-bg": "color-error-100",
"alert-error-text": "color-error-700"
}
}When to use semantic tokens
Semantic tokens are most useful when:
- Building large applications - Provides consistent naming across components
- Working with a team - Makes design intent clear to everyone
- Planning for theming - Makes theme switching easier
- Documenting design decisions - Creates a design vocabulary
When to skip semantic tokens
You might not need semantic tokens when:
- Building simple projects - Primitive tokens may be sufficient
- Rapid prototyping - Skip the extra layer initially
- Using utilities heavily - Utility classes may provide enough abstraction
Next steps
To learn more:
- Design Tokens - understanding primitive tokens
- Utilities - using semantic tokens with utilities
- Configuration - configuring semantic tokens