How to Create Component Tokens with Variants in DESIGN.md
Component tokens with variants in DESIGN.md are created by defining a base component in the YAML front matter under the components key, then adding sibling entries that share the base name plus a state suffix (e.g., -hover or -active) to override specific properties for different UI states.
The google-labs-code/design.md repository provides a specification for managing design tokens in Markdown files. When building reusable UI components, you need to handle multiple interaction states without duplicating entire token sets. The component variant system allows you to define base styles and layer state-specific overrides efficiently.
Understanding the Component Token Structure
According to the official specification in docs/spec.md (lines 305-317), component tokens follow a specific naming convention that enables automatic merging by consumers.
Base Component Definition
Every variant starts with a base component that defines the default styling. In the YAML front matter of your DESIGN.md, create an entry under the components map:
components:
button-primary:
backgroundColor: "{colors.primary}"
textColor: "{colors.on-primary}"
typography: "{typography.label-md}"
rounded: "{rounded.lg}"
padding: 12px
height: 48px
This base entry establishes the complete token set for the component's default state.
Variant Naming Convention
Variants are defined as separate entries that share the base component's prefix and append a state suffix. The specification recognizes states such as active, hover, and pressed. For example, if your base is button-primary, valid variants include:
button-primary-hoverbutton-primary-activebutton-primary-disabled
This naming pattern signals to consumers that these entries should be merged with the base component when the corresponding UI state is active.
How to Define Component Tokens with Variants
Follow this pattern to implement variants in your DESIGN.md file:
- Open the YAML front matter of your
DESIGN.md(the section between---delimiters). - Ensure the
componentsmap exists at the root level of the front matter. - Define your base component with all default properties.
- Create variant entries as siblings to the base, using the
{base-name}-{state}format. - Include only overriding properties in variants—values that differ from the base.
A real-world example from examples/totality-festival/DESIGN.md (lines 101-110) demonstrates this pattern:
---
# …other token sections (colors, typography, etc.) …
components:
# Base component – default styling for the primary button
button-primary:
backgroundColor: "{colors.primary}"
textColor: "{colors.on-primary}"
typography: "{typography.label-md}"
rounded: "{rounded.lg}"
padding: 12px
height: 48px
# Hover variant – only the properties that change are listed
button-primary-hover:
backgroundColor: "{colors.primary-fixed}"
# Active (pressed) variant – another override
button-primary-active:
backgroundColor: "{colors.primary-80}"
textColor: "{colors.on-primary-80}"
Another implementation example exists in examples/paws-and-paths/DESIGN.md, showing how the same pattern applies across different project types.
Referencing and Inheriting Tokens
The variant system relies on token references and selective overrides. Use the {path.to.token} syntax to reference global tokens (like colors.primary or typography.label-md) and keep your definitions DRY.
When a variant omits a property, the consumer merges the base component with the variant, preserving base values for any properties not explicitly overridden. This means:
- Full replacement: A variant can redefine any token, like
backgroundColorin the hover state. - Inheritance: Unspecified properties automatically fall back to the base component values.
The consumer (CLI tool, AI agent, or build system) handles this merge at runtime or build time.
Consuming Component Variants in Code
Applications consuming these tokens implement logic to merge base and variant tokens based on UI state. As shown in the repository's README.md, tokens are accessed via dot-notation paths like "components.button-primary".
A TypeScript implementation demonstrates the merge strategy:
function getButtonTokens(state: 'default' | 'hover' | 'active') {
const base = tokens.components['button-primary'];
const variant = tokens.components[`button-primary-${state}`] ?? {};
return { ...base, ...variant };
}
The spread operator performs a shallow merge, where variant properties override base properties when present. This yields a complete token set appropriate for the current component state.
Summary
- Component tokens live in the YAML front matter of
DESIGN.mdunder thecomponentskey. - Variants follow the naming pattern
{base-name}-{state}(e.g.,button-primary-hover). - Define only changed properties in variants; consumers merge them with the base component.
- Reference global tokens using
{path.to.token}syntax to maintain consistency. - The specification is documented in
docs/spec.md(lines 305-317) with examples inexamples/totality-festival/DESIGN.mdandexamples/paws-and-paths/DESIGN.md.
Frequently Asked Questions
What is the difference between a base component and a variant token?
A base component defines the complete set of design tokens for a UI element's default state, including properties like backgroundColor, textColor, and padding. A variant token is a partial definition that shares the base name plus a state suffix (like -hover or -active) and contains only the properties that change for that specific state. Consumers merge the base with the variant to generate the final styles.
How do I reference other tokens inside component definitions?
Use the curly brace syntax {path.to.token} to reference tokens defined elsewhere in the YAML front matter. For example, backgroundColor: "{colors.primary}" references the primary color token from the colors section. This keeps component definitions synchronized with your global design system and avoids hardcoded values.
Can a component have multiple variants for different states?
Yes, a single base component can have any number of variant siblings. The specification supports states like active, hover, pressed, and disabled. Each variant is defined as a separate entry in the components map (e.g., button-primary-hover, button-primary-active, button-primary-disabled), allowing granular control over styling across all interaction states.
Where does the merging of base and variant tokens happen?
The merge logic is implemented by the consumer—whether that's a CLI tool, AI agent, or build system processing the DESIGN.md file. The specification in docs/spec.md defines the expected behavior, but the actual JavaScript/TypeScript merge (typically using object spread like { ...base, ...variant }) occurs in the consuming application or build pipeline.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →