How to Create Component Variants (Hover and Active States) in DESIGN.md
To create component variants in DESIGN.md, define a base component and add state-specific variants using the naming pattern <component-name>-<state-suffix>, such as button-primary-hover or card-active, which the parser automatically merges with the base definition.
The google-labs-code/design.md repository defines a design system specification that uses plain-text markdown with optional YAML frontmatter for design tokens and component definitions. Creating component variants for interactive states like hover and active follows a specific naming convention that allows the CLI linter to detect and resolve state-specific styling without duplicating entire component definitions.
Understanding the Variant Naming Convention
DESIGN.md recognizes variants through a strict key-naming pattern. When the parser encounters a component key matching <component-name>-<state-suffix>, it treats this as a variant of the base component.
According to the specification in docs/spec.md (line 305), valid variant names follow this pattern:
button-primary(base component)button-primary-hover(hover state)button-primary-active(active/pressed state)
The parser implementation in packages/cli/src/linter/spec-config.ts detects these variant keys and merges their token values with the base component definition. This merging happens at the data structure level defined in packages/cli/src/linter/model/spec.ts, ensuring that variants inherit all base properties while overriding only specific values.
Defining Hover and Active States
Variant definitions only need to specify properties that differ from the base component. This prevents duplication and keeps the design system maintainable.
Basic Component with Hover Variant
Define the base component with full styling, then create a variant entry that overrides specific properties for the hover state:
---
components:
button-primary:
background: "{colors.primary}"
border: "2px solid {colors.primary}"
color: "{colors.on-primary}"
padding: "12px 24px"
rounded: "{rounded.md}"
button-primary-hover:
background: "{colors.primary-variant}"
border: "2px solid {colors.primary-variant}"
color: "{colors.on-primary}"
---
The button-primary-hover variant inherits the padding and rounded values from button-primary while specifically overriding background and border colors for the hover interaction.
Multiple State Variants
You can define several states for a single component by appending different suffixes:
---
components:
card:
background: "{colors.surface}"
border: "1px solid {colors.outline}"
padding: "{spacing.md}"
rounded: "{rounded.lg}"
card-hover:
background: "{colors.surface-variant}"
card-active:
background: "{colors.surface-active}"
---
This approach creates three distinct states—default, hover, and active—while minimizing repetition. The card-hover and card-active variants each change only the background property, inheriting border, padding, and radius from the base card definition.
Referencing Design Tokens in Variants
Variants can reference design tokens using the {token.path} syntax, enabling consistent color palettes and spacing across all component states. The parser resolves these references at build time, allowing variants to use semantic color names like {colors.primary-variant} instead of hardcoded values.
When defining hover states, reference lighter or darker color variants from your token palette:
components:
button-primary-hover:
background: "{colors.primary-variant}" # Lighter shade for hover
This ensures that state changes remain consistent with your overall design system theme.
Real-World Examples from the Repository
The examples/totality-festival/DESIGN.md file demonstrates practical variant usage for badge components:
---
components:
badge:
background: "{colors.secondary}"
color: "{colors.on-secondary}"
badge-info:
background: "{colors.info}"
color: "{colors.on-info}"
badge-success:
background: "{colors.success}"
color: "{colors.on-success}"
---
Each variant (badge-info, badge-success) follows the naming convention and overrides only the colors that differ from the base badge component. The examples/paws-and-paths/DESIGN.md file provides additional illustrations of variant token usage for interactive elements.
Summary
- Use the naming pattern
<component-name>-<state-suffix>(e.g.,button-primary-hover) to define variants for hover, active, and other states. - Define only differing properties in variant entries; the parser merges them with the base component automatically.
- Reference design tokens using
{token.path}syntax to maintain consistency across states. - Locate the core logic in
packages/cli/src/linter/spec-config.tsandpackages/cli/src/linter/model/spec.ts. - Consult the specification in
docs/spec.md(line 305) for the official variant definition.
Frequently Asked Questions
What is the exact naming pattern for component variants in DESIGN.md?
Variants must follow the pattern <component-name>-<state-suffix>, where the suffix describes the UI state such as hover, active, or pressed. For example, button-primary-hover is recognized as a hover variant of the button-primary component. The parser in packages/cli/src/linter/spec-config.ts identifies these keys by detecting the hyphen-separated suffix.
How does the DESIGN.md parser handle variant inheritance?
The parser treats variant entries as partial definitions that extend their base components. When processing button-primary-hover, the system starts with all properties defined in button-primary, then applies any overrides specified in the variant entry. This merging occurs at the data structure level in packages/cli/src/linter/model/spec.ts, ensuring variants inherit base values without requiring explicit duplication.
Can variants reference other tokens or only literal values?
Variants can reference design tokens using the {category.name} syntax (e.g., {colors.primary-variant}). The parser resolves these references during the build process, allowing variants to use semantic tokens from your design system rather than hardcoded color values or measurements. This supports dynamic theming and ensures consistency across hover, active, and default states.
Where is the variant specification documented in the source code?
The official variant specification resides in docs/spec.md at line 305, which states: "A component may have a variant for different UI states such as active, hover, pressed, etc." The implementation logic that enforces this specification lives in packages/cli/src/linter/spec-config.ts, which handles the detection and merging of variant keys during the linting process.
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 →