How Component Variants (Hover States) Are Defined in DESIGN.md
Component variants in DESIGN.md are defined as separate YAML entries using state-specific suffixes like -hover or -active, where each variant inherits base component properties and overrides only the values that change for that specific UI state.
The google-labs-code/design.md specification treats interactive UI states as first-class citizens through its variant system. Rather than embedding state logic within a single component definition, DESIGN.md utilizes a flat key structure that appends state modifiers to base component names. This approach enables precise, per-state styling while maintaining a concise, DRY configuration format.
Variant Naming Convention in DESIGN.md
The design system establishes a strict naming pattern for state-dependent styling. Each variant is identified by a compound key consisting of the base component name followed by a hyphenated state suffix.
State-Specific Suffix Pattern
Valid variant keys append specific UI state identifiers to the base component key. According to docs/spec.md (lines 305-306), supported suffixes include:
-hoverfor mouse-over states-activefor currently selected elements-pressedfor depressed or clicked states
For example, a primary button exists as button-primary, while its hover state counterpart is defined as button-primary-hover. The specification explicitly states: "Those variant components may be defined under a different but related key, for example, button-primary, button-primary-hover, button-primary-active."
Inheritance and Property Override Behavior
Variants operate on a sparse override model. When you define a variant, you only specify properties that differ from the base component; all other values are inherited automatically.
In docs/spec.md (lines 307-316), the specification demonstrates this with a concrete example where button-primary-hover redefines only the backgroundColor property:
components:
button-primary:
backgroundColor: "{colors.primary-60}"
textColor: "{colors.primary-20}"
rounded: "{rounded.md}"
padding: 12px
button-primary-hover:
backgroundColor: "{colors.primary-70}"
The system resolves the final appearance by first loading the base button-primary tokens, then overlaying the button-primary-hover values when the hover state is active. This inheritance mechanism eliminates redundancy while ensuring state-specific fidelity.
Practical Examples of Component Variants
Implementing variants requires minimal YAML configuration. Below are concrete implementations demonstrating the pattern across different component types.
Basic Button Hover State
Define the base button and its hover variant as sibling entries under the components key:
components:
button-primary:
backgroundColor: "{colors.primary-60}"
textColor: "{colors.primary-20}"
rounded: "{rounded.md}"
padding: 12px
button-primary-hover:
backgroundColor: "{colors.primary-70}"
When rendered, the hover state retains the text color, border radius, and padding from button-primary while applying the darker background color.
Card Component with Hover Variant
Interactive surfaces like cards benefit from subtle background shifts. The examples/totality-festival/DESIGN.md file (lines 202-210) demonstrates this pattern:
components:
card:
backgroundColor: "{colors.surface-variant}"
borderColor: "{colors.outline-variant}"
rounded: "{rounded.sm}"
padding: 16px
card-hover:
backgroundColor: "{colors.secondary-70}"
During resolution, the variant merges onto the base card definition, preserving structural properties while updating the background for the hover interaction.
Reusing Variant Styles Across Components
You can reference existing variant definitions using YAML anchors to maintain consistency. For instance, a chip component might reuse a button's hover styling:
components:
chip-primary:
backgroundColor: "{colors.primary-30}"
textColor: "{colors.on-primary}"
rounded: "{rounded.full}"
padding: 8px
chip-primary-hover:
<<: *button-primary-hover
textColor: "{colors.on-primary}"
This technique ensures that hover states remain consistent across related interactive elements without duplicating token values.
Runtime Resolution in the DESIGN.md CLI
The variant system is enforced by the linter and generator tools in the repository. In packages/cli/src/linter/model/spec.ts, the parser materializes component objects by first resolving the base key, then conditionally overlaying any matching variant key based on the current UI state context.
This resolution strategy means consumers (such as build tools or design-to-code generators) receive a fully merged property set for the active state without manually traversing inheritance chains.
Summary
- Component variants in DESIGN.md use hyphenated suffixes (
-hover,-active,-pressed) appended to base component keys. - Variants inherit all properties from their base component and override only specific values for that UI state.
- The specification in
docs/spec.md(lines 291-306) formalizes this pattern as the standard method for handling interactive states. - Real-world implementations appear in
examples/totality-festival/DESIGN.md, demonstrating practical usage of hover variants on card components. - The CLI linter at
packages/cli/src/linter/model/spec.tshandles runtime resolution by overlaying variant properties onto base components.
Frequently Asked Questions
What file defines the variant specification in DESIGN.md?
The authoritative definition resides in docs/spec.md at lines 291-306. This section establishes the naming convention, inheritance model, and valid state suffixes for component variants such as hover and active states.
How does a variant inherit properties from its base component?
The system uses a sparse override mechanism. When resolving a component, the parser first loads all properties from the base key (e.g., button-primary), then overlays only the properties defined in the variant key (e.g., button-primary-hover). Unspecified properties in the variant retain their base values automatically.
Can I use YAML anchors to reuse variant styles across different components?
Yes. The specification supports standard YAML merge keys (<<: *anchor-name) to reference variant definitions from other components. This allows you to share hover state configurations across disparate elements like buttons and chips while maintaining a single source of truth for the token values.
What UI states are supported for component variants?
The specification explicitly mentions active, hover, and pressed as standard UI states. You define these by appending the respective suffix to your base component key (e.g., component-name-hover). The system treats any key matching the base name plus suffix pattern as a valid variant regardless of the specific state semantics.
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 →