How to Add Custom Component Variants Beyond Hover States in DESIGN.md

To add custom component variants beyond hover states in DESIGN.md, create a new key following the naming convention <base-component>-<variant> and define only the token overrides for that specific state; the system automatically merges these with the base component tokens.

The DESIGN.md specification from google-labs-code/design.md treats UI components as maps of design tokens that support multiple interaction states. While hover states are common, you often need additional variants like pressed, active, or disabled-dark to capture complex UI behaviors. Adding these custom variants requires no core configuration changes—just a specific naming convention that the parser automatically recognizes.

Understanding the DESIGN.md Variant System

According to the Variants section in docs/spec.md, components can define multiple states that represent different interaction modes. The specification defines these variants as separate keys that the agent merges with the base component during rendering. This architecture allows you to extend any component with domain-specific states without duplicating the entire token set.

When a DESIGN.md consumer processes a file, it first retrieves the base component tokens, then checks for a variant key matching the pattern <base-name>-<variant>. If found, the consumer overlays the variant tokens on top of the base values, with missing fields inheriting from the parent component.

Step-by-Step Guide to Adding Custom Component Variants

Follow these steps to implement custom variants such as pressed, focused, or disabled-dark:

  1. Identify the base component you want to extend (for example, button-primary).
  2. Create a new key using the format <base-component>-<variant-name> (for example, button-primary-pressed).
  3. Define only the token changes for that specific state. You do not need to repeat every token from the base component.
  4. Save the changes in your DESIGN.md file, whether in the project-wide spec, an example file, or a component library file.

The system automatically resolves this hierarchy, making the variant available to CLI tools, linters, and code generators.

Practical Examples

Adding a Pressed State to a Primary Button

In docs/spec.md, the primary button base definition includes tokens for background color, text color, rounding, and padding. To add a pressed variant:


# docs/spec.md (excerpt)

components:
  button-primary:
    backgroundColor: "{colors.primary-60}"
    textColor: "{colors.primary-20}"
    rounded: "{rounded.md}"
    padding: 12px
  # Custom variant definition

  button-primary-pressed:
    backgroundColor: "{colors.primary-80}"
    textColor: "{colors.on-primary}"

Notice that button-primary-pressed only overrides the backgroundColor and textColor tokens. The rounded and padding values inherit automatically from the base button-primary definition.

Project-Specific Variant Definitions

You can also define variants in project-specific DESIGN.md files. For example, in examples/totality-festival/DESIGN.md:


# examples/totality-festival/DESIGN.md

components:
  button-primary:
    backgroundColor: "{colors.primary-60}"
    textColor: "{colors.primary-20}"
  button-primary-pressed:
    backgroundColor: "{colors.primary-80}"
    textColor: "{colors.on-primary}"

This approach keeps your design tokens organized while allowing per-project customization of component states.

Accessing Variants in Generated Code

When the CLI exporter or code generator processes these definitions, it uses merge logic similar to this TypeScript implementation:

function getComponentTokens(name: string, state?: string) {
  const base = design.components[name];
  if (!state) return base;
  const variant = design.components[`${name}-${state}`];
  return { ...base, ...variant };
}

// Retrieve tokens for a pressed primary button
const tokens = getComponentTokens('button-primary', 'pressed');

The function retrieves the base button-primary tokens, then overlays the button-primary-pressed variants, creating the complete token set for that interaction state.

Key Files and Validation

When working with custom variants, reference these essential files in the google-labs-code/design.md repository:

  • docs/spec.md: Contains the official DESIGN.md specification, including the Variants rule that defines how state-based keys are processed.
  • examples/totality-festival/DESIGN.md: Demonstrates practical implementation of component tokens and variants in a real project context.
  • packages/cli/src/linter/fixtures/*.md: Contains fixture files used by the CLI linter to validate variant handling, useful for testing your new variant definitions.

Summary

  • Custom variants in DESIGN.md follow the naming convention <base-component>-<variant-name>.
  • Define only the token overrides for each variant; the system automatically inherits unspecified tokens from the base component.
  • No additional configuration is required—the CLI linter, code generators, and theming tools automatically resolve and merge variant hierarchies.
  • Reference docs/spec.md for the official specification and examples/totality-festival/DESIGN.md for concrete implementation patterns.

Frequently Asked Questions

Can I create variants for any component type, or only buttons?

You can create variants for any component defined in your DESIGN.md file. The variant system is not limited to buttons—it works for cards, inputs, navigation elements, or any custom component following the base specification in docs/spec.md.

Do I need to redefine all design tokens for each variant?

No. You should define only the tokens that change for that specific state. The DESIGN.md consumer automatically merges variant tokens with the base component, so undefined fields inherit values from the parent component.

How do I validate that my custom variant syntax is correct?

Run the CLI linter against your DESIGN.md file. The linter uses fixture files from packages/cli/src/linter/fixtures/*.md to validate variant handling and will flag any keys that don't follow the established naming conventions or reference undefined tokens.

Can I use nested variants like button-primary-pressed-hover?

The specification supports single-level variant naming following the <base>-<variant> pattern. For complex state combinations, define explicit combined states (such as button-primary-pressed) rather than chained suffixes, ensuring the parser can correctly resolve the token hierarchy.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →