Balancing Specificity and Scalability
Three strategies to strike a balance between specificity and scalability
How can we balance making design tokens specific enough to be useful, but general enough to be reusable across our system?
When creating tokens with broad applicability—aimed at enhancing reusability and scalability—how do we mitigate the risk of unintended consequences when implementing changes in the future?
At its core, the challenge lies in finding the sweet spot between creating specific tokens for precise control and maintaining a scalable system that minimizes unintended ripple effects.
Imagine two different scenarios:
Scenario 1: Multiple Specific Design Tokens
In this scenario, we create individual design tokens for each adjustable property, resulting in a highly specific but less scalable system.
// Button tokens
$button-background-color: #007bff;
$button-text-color: #ffffff;
$button-border-radius: 4px;
$button-padding: 10px 20px;
$button-font-size: 16px;
// Input tokens
$input-border-color: #cccccc;
$input-background-color: #ffffff;
$input-text-color: #333333;
$input-padding: 8px 12px;
$input-font-size: 14px;
// Card tokens
$card-background-color: #f8f9fa;
$card-border-color: #e9ecef;
$card-border-radius: 8px;
$card-padding: 16px;
$card-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
// Usage example
.button {
background-color: $button-background-color;
color: $button-text-color;
border-radius: $button-border-radius;
padding: $button-padding;
font-size: $button-font-size;
}
.input {
border-color: $input-border-color;
background-color: $input-background-color;
color: $input-text-color;
padding: $input-padding;
font-size: $input-font-size;
}
.card {
background-color: $card-background-color;
border-color: $card-border-color;
border-radius: $card-border-radius;
padding: $card-padding;
box-shadow: $card-shadow;
}
This is equivalent to using hard-coded values, which largely defeats the purpose of building a design system in the first place.
In this case, if we make a change in some place, it will not propagate elsewhere, so we can be sure that that change will not cause unintended effects anywhere else.
In other words, the scope of every design token is minimal, basically 1:1.
Scenario 2: Single Alias Token Applied Across Components
This scenario, while extreme, effectively illustrates the point. If we assign a new value to this single global design token, the change would ripple throughout the entire system.
// Global token
$color-primary: #007bff;
// Using the global token for multiple components
.button {
background-color: $color-primary;
border-color: $color-primary;
}
.link {
color: $color-primary;
}
.header {
border-bottom: 2px solid $color-primary;
}
.icon {
fill: $color-primary;
}
In this example, we have a single $color-primary
token that's used across various components buttons, links, headers, and icons.
Now, let's say we decide to change this global token:
// Changing the global token
$color-primary: #ff4500;
This change would propagate throughout the entire system, affecting all components using this token. The buttons, links, headers, and icons would all now use the new color (#ff4500) instead of the original blue (#007bff).
While this approach provides consistency and makes it easy to update the entire system at once, it also demonstrates the potential for unintended consequences. For instance, the new orange color might look great on buttons but could make text links hard to read or clash with other elements in the header.
This scenario effectively illustrates the point that when we create a single, widely-used token, changes to that token can have far-reaching effects throughout the system. It underscores the importance of carefully considering the scope and usage of our design tokens to balance between system-wide consistency and the flexibility to make targeted changes.
Strategies
1. Use both specific and alias tokens
There is no one-size-fits-all solution. As shown in the code example below, you can create both specific tokens (--button-primary-background
) and more general tokens (--color-primary
). This allows for flexibility in how you apply these tokens across your design system.
/* Example of a more specific token */
--button-primary-background: #007bff;
/* Example of a more general token */
--color-primary: #007bff;
/* Usage depends on your needs */
.button-primary {
background-color: var(--button-primary-background);
/* or */
background-color: var(--color-primary);
}
You might use the more specific token for a particular component that needs unique styling while using the general token for broader application across multiple components.
2. Component-level tokens for commonly
re-themed elements
Component-level tokens are design tokens that are specific to particular UI components. These elements are frequently customized or re-themed across different parts of an application or different products within a design system.
This approach allows for more granular control over individual components while still maintaining some level of consistency across the system.
For example, consider the button component shown in the code:
:root {
--button-background: #007bff;
--button-text-color: #ffffff;
--button-border-radius: 4px;
}
.button {
background-color: var(--button-background);
color: var(--button-text-color);
border-radius: var(--button-border-radius);
}
In this case, we have created specific tokens for the button's background color, text color, and border-radius.
Benefits:
Customize the button component without affecting other elements that might use similar colors or styles.
All instances of the button component use the same styles
Easier theming simply updating these component-specific tokens without worrying about unintended side effects on other elements.
It makes the code more readable and self-explanatory.
3. Scoped-semantic tokens
Semantic-level tokens describe the purpose or meaning of a design element, rather than its specific visual properties.
By categorizing these tokens into "ink" and "interface" sets, for example, we create a clear separation between color tokens used for text and those used for UI elements.
We could refer to these sets as "scope".
Here's an example of how this strategy can be implemented:
:root {
/* Ink tokens */
--ink-primary: #333333;
--ink-secondary: #666666;
/* Interface tokens */
--interface-background: #ffffff;
--interface-border: #e0e0e0;
}
.card {
background-color: var(--interface-background);
border: 1px solid var(--interface-border);
color: var(--ink-primary);
}
The "ink" (--ink-primary
and --ink-secondary
) are used for text colors.
The "interface" tokens (--interface-background
and --interface-border
) are used for UI element colors.
Benefits:
It's easier to update the entire color scheme by changing just a few token values, rather than updating individual component styles.
The naming convention makes it clear what each token is used for, improving code readability and maintainability.