Building Design Systems and Theme Architectures
Create semantic design tokens, reusable component variants, themeable CSS custom properties, accessible states, and maintainable design-system foundations.
Semantic Tokens and Design-System Foundations
A design token represents a reusable design decision such as a color, spacing value, font size, border radius, shadow, or motion duration.
Tokens can be organized into primitive and semantic layers. Primitive tokens represent raw values, while semantic tokens describe how those values are used. For example, a primitive color might be blue-600, while a semantic token might be color-action-primary.
Semantic naming makes themes and redesigns easier because components depend on intent rather than a specific color name. If the brand palette changes, the semantic mapping can change without rewriting every component.
Sass maps can store compile-time tokens, while CSS custom properties expose runtime values to the browser.
$palette: (
blue-600: #2457c5,
gray-900: #171717,
gray-100: #f5f5f5
);
:root {
--color-action-primary: #{map.get($palette, blue-600)};
--color-text-primary: #{map.get($palette, gray-900)};
--color-surface: #{map.get($palette, gray-100)};
}
.button-primary {
background: var(--color-action-primary);
color: white;
}
.page {
color: var(--color-text-primary);
background: var(--color-surface);
}map.get() to map primitive Sass colors to semantic CSS custom properties. It requires @use "sass:map"; at the beginning of the file.
Components depend on semantic properties such as --color-action-primary. A new brand palette can be introduced by changing the token mapping rather than editing every component.Why are semantic design tokens often more maintainable than using raw color names throughout components?
Light and Dark Themes
Theme architecture should separate the semantic role of a value from its actual color. A surface token may be light in one theme and dark in another, while the component continues using the same property name.
CSS custom properties are a strong choice for runtime theme switching. A theme can be selected through an attribute or class on a root element, and all descendants can inherit the corresponding tokens.
Sass can generate the initial token declarations or organize theme data, but the browser handles runtime theme selection. For system preference support, consider the prefers-color-scheme media feature.
Test focus indicators, borders, disabled states, charts, illustrations, and shadows in both themes. Dark mode is not simply an inversion of every color.
:root,
[data-theme="light"] {
color-scheme: light;
--surface: #ffffff;
--text: #202020;
--border: #dddddd;
}
[data-theme="dark"] {
color-scheme: dark;
--surface: #171717;
--text: #f5f5f5;
--border: #444444;
}
.panel {
color: var(--text);
background: var(--surface);
border: 1px solid var(--border);
}color-scheme declaration informs the browser about supported native UI color schemes. Explicitly test native form controls and other interface elements in each supported theme.Which feature is especially useful for changing theme values without recompiling Sass?
Component Variants, States, and Accessibility
A reusable component should have clear base styles, supported variants, and interaction states. Sass maps and loops can generate predictable variant classes, while mixins can standardize recurring focus and responsive behavior.
Interactive states include hover, focus-visible, active, disabled, loading, success, and error. Not every component requires every state, but supported states should be defined consistently.
Accessibility should be considered in the underlying CSS design. Keyboard focus must remain visible, text contrast should be sufficient, and information should not be conveyed through color alone. Disabled and loading states should also be reflected appropriately in the component's HTML and application logic.
Keep component classes stable and avoid selectors that depend excessively on the exact internal markup hierarchy.
$button-variants: (
primary: #2457c5,
success: #218838,
danger: #c62828
);
.button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
border: 0;
border-radius: 0.5rem;
font: inherit;
cursor: pointer;
&:focus-visible {
outline: 3px solid #222;
outline-offset: 3px;
}
&:disabled {
opacity: 0.55;
cursor: not-allowed;
}
}
@each $name, $color in $button-variants {
.button--#{$name} {
background: $color;
color: white;
}
}disabled attribute when the action is unavailable.Which pseudo-class is particularly appropriate for showing a keyboard focus indicator?