Composite Tokens Explained
Composite Tokens Explained
Composite tokens explained in practical terms means understanding how tokens can bundle multiple related properties into a single unit. Unlike simple tokens that store single values, composite tokens group values that naturally belong together, such as all properties defining a typography style or shadow definition. This grouping maintains design coherence while simplifying token usage.
What Are Composite Tokens
Composite tokens are tokens whose values contain multiple properties rather than a single value. A typography composite token might include font family, size, weight, line height, and letter spacing. A shadow composite token might include offset, blur, spread, and color values.
Composite tokens capture design decisions that involve multiple interdependent values. Changing one property often requires adjusting others to maintain visual balance, so grouping them ensures they evolve together.
How Composite Tokens Work
Composite token definitions use structured values:
Typography composite:
{
"typography": {
"heading": {
"lg": {
"$type": "typography",
"$value": {
"fontFamily": "{font.family.sans}",
"fontSize": "{font.size.2xl}",
"fontWeight": "{font.weight.bold}",
"lineHeight": "{line.height.tight}",
"letterSpacing": "{letter.spacing.tight}"
}
}
}
}
}
Shadow composite:
{
"shadow": {
"md": {
"$type": "shadow",
"$value": [
{
"offsetX": "0px",
"offsetY": "4px",
"blur": "6px",
"spread": "-1px",
"color": "rgba(0, 0, 0, 0.1)"
},
{
"offsetX": "0px",
"offsetY": "2px",
"blur": "4px",
"spread": "-2px",
"color": "rgba(0, 0, 0, 0.1)"
}
]
}
}
}
Transformation unpacks composites for platform consumption:
CSS output:
.heading-lg {
font-family: var(--font-family-sans);
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-tight);
letter-spacing: var(--letter-spacing-tight);
}
:root {
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
}
Key Considerations
- Composite structure should match design tool representations
- Transformation complexity increases with composite tokens
- Not all platforms consume composites the same way
- Individual property access may be needed alongside composites
- W3C specification defines composite type structures
- Documentation should explain composite structure and usage
- Validation must check all constituent properties
- Composites can reference other tokens for their properties
Common Questions
When should composite tokens be used versus individual tokens?
Composite tokens suit properties that are designed together and should change together. Individual tokens suit properties that vary independently.
Use composites for:
- Typography styles where size, weight, and line height are tuned together
- Shadows where multiple shadow layers create a single effect
- Borders where width, style, and color form a deliberate combination
- Gradients where stops and directions define a complete gradient
Use individual tokens for:
- Colors that apply in various unrelated contexts
- Spacing values used independently across different properties
- Properties that frequently override just one aspect of a potential composite
Hybrid approaches can provide both. Individual tokens define the atomic values, while composite tokens bundle them for convenience:
{
"font": {
"size": { "lg": { "$value": "18px" } },
"weight": { "bold": { "$value": "700" } }
},
"typography": {
"body": {
"$value": {
"fontSize": "{font.size.lg}",
"fontWeight": "{font.weight.bold}"
}
}
}
}
How should composite tokens be transformed?
Transformation converts composite structure to platform-specific syntax.
CSS shorthand for simple composites:
// Shadow composite to CSS
function shadowToCSS(shadowToken) {
return shadowToken.$value
.map(s => `${s.offsetX} ${s.offsetY} ${s.blur} ${s.spread} ${s.color}`)
.join(', ');
}
Multiple properties for expanded output:
// Typography composite to CSS properties
function typographyToCSS(typoToken, tokenName) {
return `
.${tokenName} {
font-family: ${typoToken.$value.fontFamily};
font-size: ${typoToken.$value.fontSize};
font-weight: ${typoToken.$value.fontWeight};
line-height: ${typoToken.$value.lineHeight};
letter-spacing: ${typoToken.$value.letterSpacing};
}`;
}
Platform-specific structures for native:
// Typography composite to Swift
struct Typography {
let font: UIFont
let lineHeight: CGFloat
let letterSpacing: CGFloat
}
Style Dictionary requires custom transforms and formats for composite handling beyond basic types.
What about partial composite overrides?
Sometimes only one property of a composite needs to change for a specific context.
Spread and override pattern:
const headingLgEmphasis = {
...tokens.typography.heading.lg,
fontWeight: tokens.font.weight.black
};
CSS with individual property override:
.heading-lg-emphasis {
/* Apply full composite */
font-family: var(--typography-heading-lg-family);
font-size: var(--typography-heading-lg-size);
/* ... */
/* Override specific property */
font-weight: var(--font-weight-black);
}
Composite variants predefine expected overrides:
{
"typography": {
"heading": {
"lg": { "$value": { "fontWeight": "{font.weight.bold}" } },
"lg-emphasis": { "$value": { "fontWeight": "{font.weight.black}" } }
}
}
}
The approach depends on how frequently partial overrides occur and whether they represent intentional design decisions that warrant named tokens.
Summary
Composite tokens bundle multiple related properties into single units, maintaining design coherence for complex attributes like typography and shadows. Token definitions use structured values, while transformations unpack composites into platform-specific syntax. Composites suit properties designed together, while individual tokens serve independently varying values. Partial overrides can be handled through spread patterns, CSS property overrides, or predefining composite variants.
Buoy scans your codebase for design system inconsistencies before they ship
Detect Design Drift Free