Component variants beat a pile of CSS variables

I once took over a WooCommerce project for a high-traffic luxury brand, and it was a mess. The previous dev and designer had no shared language. The design files did not really contain buttons. They contained about 20 different “rectangles with text” that looked identical but each had slightly different padding. Every time the client asked for a small tweak, say a different border-radius, it turned into a three-hour hunt through CSS selectors. We needed a way to enforce consistency, which is where Component Variants come in.

My first instinct was to throw a pile of CSS variables at it and call it done. Define --primary-button-bg and everything falls into line, right? It worked for about five minutes. Then we needed a disabled state in the brand’s muted purple, and my tidy variable system turned into spaghetti. The fix was not the tokens on their own. It was grouping the logic with Component Variants in a tool like Penpot.

What component variants actually give you

In Penpot, component variants let you take one base object and define several states or types without creating whole new components. If you have ever built a sustainable design system, you know the goal is to update in one place and see it everywhere. What I like about Penpot’s version is that it is open source and uses code-friendly structures like the W3C DTCG format for design tokens.

This is how Brad Frost’s Atomic Design was meant to work. You do not just have an atom, you have an atom that understands its context. When you build these in Penpot you are writing the blueprint for your theme.json or your CSS architecture. Twenty minutes setting up variants now saves you twenty hours of debugging later.

Step 1: get your tokens in order

Before you touch a variant you need tokens. In Penpot you create a main component, but the useful part is aliasing your colors. Instead of a raw hex code you use a token like color.brand.default. I have seen teams skip this and hand-style every variant, which lands them right back where they started. Here is roughly how I structure the naming so dev handoff stays clean.

/* 
 * Example token structure for a bbioon_system setup.
 * This mirrors how Penpot exports tokens to JSON.
 */

{
  "button": {
    "primary": {
      "default": { "value": "{color.brand.main}" },
      "hover": { "value": "{color.brand.lighter}" },
      "disabled": { "value": "{color.brand.muted}" }
    }
  }
}

Step 2: building and scaling the variants

With your tokens set, select the main component and click Create Variant. That gives you a linked copy. The part people miss: you are not changing the color, you are changing the property value. I name my properties plainly, things like State, Size or Icon Position. If you have read my post on taming border radii with Theme.json, this workflow will feel familiar.

Penpot also reads a forward slash naming convention, so Button/Primary/Default gets grouped for you. Later, when you drop an instance of that button on the canvas, you get a dropdown to switch between default, hover and disabled instead of hunting through a list of 50 separate components.

What this bought us

Component variants are a communication tool as much as a design feature. They close the gap between what the designer sees and what we have to code. Grouping the variations under one main component cuts technical debt and keeps the UI from falling apart the next time the brand guidelines change. Once we moved the luxury brand’s site to this system, our maintenance time dropped by nearly 60%.

Design systems get complicated fast. If you are tired of debugging someone else’s mess and just want a clean, scalable architecture, drop me a line. I have probably seen your exact problem before.

How are you handling component states right now? Still doing it by hand, or have you moved to a variant-first workflow?

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.