Composite vs Simple Tokens
Let's explore how composite tokens differ from simple ones, analyzing their main use cases with practical examples
TLDR;
Composite tokens are sets of values that, when combined, represent a complex visual property. They are not limited to a single value but define a more articulated visual aspect that may include multiple stylistic parameters.
Examples of composite tokens
Typography styles: A token encapsulating font family, size, weight, line height, and other textual attributes.
Shadows: A token that defines shadow color, offset, blur, and spread.
Border effects: A token that includes border thickness, color, and style.
Using composite tokens simplifies design and reduces the risk of stylistic inconsistencies.
Additionally, they facilitate maintenance: any change in a composite token is automatically reflected in all elements.
Key differences between simple and composite tokens
Number of Properties
Simple tokens represent a single value (e.g., a color, a spacing unit, a font size).
Composite tokens combine multiple simple tokens to create more complex styles.
Use Case Complexity
Simple tokens define fundamental design decisions, like
"color.primary"
for the primary brand color or"spacing.medium"
for a standard padding value.Composite tokens encapsulate higher-level design concepts, such as
"button.primary"
(combining background color, border style, and text properties).
Dependency on Other Tokens
Simple tokens are atomic and self-contained.
Composite tokens are always built from simple tokens and depend on them to function.
Tokens Architecture
Simple tokens exist at the base level of the design token hierarchy.
Composite tokens operate at either the semantic or specific (component-level) layers.
Which tier did they belong to?
Composite tokens cannot be part of the base layer because they aggregate multiple simple values. Instead, they belong to one of the following levels:
Option A - Semantic Tier
At this level, composite tokens define styles that have a meaning in the design system, without being tied to a specific component.
Use Case: Typography Styles
A general "heading" style that could be applied to multiple text elements.
{
"typography.heading": {
"fontFamily": "{font.base}",
"fontSize": "{size.32}",
"fontWeight": "{weight.bold}",
"lineHeight": "{lineHeight.large}"
}
}
These tokens allow designers and developers to use typography.heading
across the system without worrying about low-level properties.
Option B - Component-Specific Tier
At this level, composite tokens define styles for specific UI elements like buttons, modals, or cards.
Use Case: Button Shadow
A shadow style that applies only to buttons and not to other elements.
{
"shadow.button.default": {
"color": "{color.shadow}",
"offsetX": "0px",
"offsetY": "2px",
"blurRadius": "4px",
"spreadRadius": "0px"
}
}
By defining component-specific composite tokens, teams can ensure a consistent design language while retaining flexibility for different UI elements.
Practical Examples of Composite Tokens
1. Composite Typography Style
A composite token for a typographic style could include multiple properties such as font family, size, weight, and line height.
Example in JSON:
{
"typography.heading": {
"fontFamily": "Arial, sans-serif",
"fontSize": "32px",
"fontWeight": "bold",
"lineHeight": "40px"
}
}
Example in CSS:
.heading {
font-family: var(--typography-heading-fontFamily);
font-size: var(--typography-heading-fontSize);
font-weight: var(--typography-heading-fontWeight);
line-height: var(--typography-heading-lineHeight);
}
In this case, the "typography.heading" composite token collects all the properties needed to define a heading style. When the value of any parameter (such as fontSize
) changes in the token, the update is reflected in all elements using this style.
2. Composite Shadow
Another example is a composite shadow token, which defines the shadow color, offset, and blur for an elevation effect.
Example in JSON:
{
"shadow.elevation.1": {
"color": "#000000",
"offsetX": "0px",
"offsetY": "4px",
"blurRadius": "8px",
"spreadRadius": "0px"
}
Example in CSS:
.button {
box-shadow: var(--shadow-elevation-1-color) var(--shadow-elevation-1-offsetX) var(--shadow-elevation-1-offsetY) var(--shadow-elevation-1-blurRadius) var(--shadow-elevation-1-spreadRadius);
}
This "shadow.elevation.1" composite token ensures that if the shadow parameters change, all elements using this token will be updated simultaneously.
3. Composite Border
A composite border token can define multiple properties, such as color, thickness, and border style.
Example in JSON:
{
"border.button": {
"color": "#4CAF50",
"width": "2px",
"style": "solid"
}
}
Example in CSS:
.button {
border: var(--border-button-width) var(--border-button-style) var(--border-button-color);
}
By centralizing border styles within a composite token, designers and developers can easily update the button border across all instances without manual modifications.
Maintaining "One Token, One Value" in Practice
At first glance, composite tokens may seem to contradict this principle since they contain multiple values.
However, composite tokens do not break this rule — rather, they adhere to it in a structured way:
Each composite token still has a singular meaning: While a composite token may reference multiple values, it defines a single visual entity (e.g., "shadow.elevation.1" represents a cohesive shadow style).
They are built from atomic tokens: Composite tokens are assembled from simple tokens, ensuring that atomic values remain reusable and manageable.
They serve a semantic role: Unlike base tokens, which are purely about raw values, composite tokens exist to express a higher-level concept like "typography.heading" or "border.button."
Best Practices
To ensure composite tokens remain aligned with this principle, follow these best practices:
Use references to simple tokens: Avoid hardcoding values inside composite tokens. Instead, reference atomic tokens to maintain modularity.
Keep composite tokens meaningful: They should represent one clear design decision, even if multiple properties are involved.
Separate structural and component-level tokens: Semantic composite tokens should not be tied to a specific component unless they are explicitly component-specific.
Example: A Good Composite Token Structure
{
"shadow.elevation.1": {
"color": "{color.shadow.default}",
"offsetX": "{spacing.0}",
"offsetY": "{spacing.2}",
"blurRadius": "{spacing.4}",
"spreadRadius": "{spacing.0}"
}
}
Each value inside shadow.elevation.1
still adheres to the "One Token, One Value" principle because they are references to separate atomic tokens. This approach allows for better reusability and consistency across a design system.