Why Your Design System Colors Aren't Generating Correctly

Hướng dẫn chi tiết về Why Your Design System Colors Aren't Generating Correctly trong Vibe Coding dành cho None.

Why Your Design System Colors Aren’t Generating Correctly

In the era of Vibe Coding, where we describe our vision and watch the AI manifest it into reality, there is a recurring “ghost in the machine” that haunts every developer: the hallucinated palette. You prompt for a “modern, professional SaaS aesthetic with deep indigo accents,” and the AI gives you a landing page that looks decent at a glance. But then you look closer. The primary button is #3b82f6 (standard Tailwind blue), the hover state is a random slate gray, the borders are a different shade of navy that doesn’t exist in your CSS variables, and the text contrast fails every accessibility check in the book.

This isn’t just a minor annoyance; it’s a systemic failure that breaks the fundamental promise of a design system. A design system is supposed to provide consistency, but when the AI “guesses” colors instead of “deriving” them, it introduces technical debt that you have to manually clean up. This article explores why this happens in AI-assisted workflows—specifically within the Vibe Coding ecosystem using tools like Pencil and Stitch—and how to fix your generation pipeline so your colors are mathematically perfect every time.

The Core Problem: The Semantic Gap in Color Representation

The primary reason AI fails to generate design system colors correctly is the Semantic Gap. Most developers prompt for colors using qualitative labels: “warm red,” “muted forest green,” or “professional navy.” To an LLM, these are linguistic tokens, not design tokens.

When you ask for “professional navy,” the LLM pulls from its training data, which includes millions of lines of inconsistent CSS from the last decade. It might give you a hex code that worked for a WordPress theme in 2018, but it has no context for how that navy should interact with your specific background-alt-gray or your success-state-green.

In Vibe Coding, we need to bridge this gap by moving away from “Literals” (fixed hex codes) and toward “Logic” (dynamic tokens).

1. The Trap of Hex Codes

LLMs treat hex codes as strings. To a human, #1A202C is a specific dark slate. To an AI, it’s just a six-character sequence. The AI doesn’t “see” the relationship between #1A202C and its 10% lighter variant. If you ask the AI to “make the background slightly lighter,” it might hallucinate #2A303C, which could be a completely different hue depending on the original saturation.

2. Context Window Drift

When using MCP tools like Pencil to design screens, the “context window” is your most precious resource. If your .pen file or DESIGN.md doesn’t explicitly define a color scale, the AI has to reinvent the wheel every time it creates a new component. This leads to “Color Drift,” where the navbar indigo on page one is #4338ca and the button indigo on page two is #4f46e5.


How It Works: The “Token-First” Architecture

To fix this, we must adopt a Token-First approach. Instead of telling the AI what color to use, we define the rules of the color system and force the AI to select from a predefined list of variables. This is where the Pencil MCP becomes essential.

Using HSL Over Hex

The first step in any robust Vibe Coding design system is switching from Hex to HSL (Hue, Saturation, Lightness).

  • Hue: Defines the identity (e.g., 220 is Blue).
  • Saturation: Defines the intensity.
  • Lightness: Defines the depth.

LLMs are significantly better at manipulating HSL values because they are numeric and predictable. If you tell an AI, “The primary brand hue is 220, generate a scale from 50 to 950 lightness,” the AI can mathematically calculate the increments. This prevents the “muddy” or “neon” colors that often result from hex hallucinations.

The Role of the DESIGN.md

In the Cody Master workflow, the DESIGN.md acts as the “source of truth.” When the AI generates a new screen, it should first call get_variables from the Pencil tool to see what exists. If you haven’t defined your variables, the AI will default to its own training bias. By populating your .pen file with a structured variable set, you create a “sandbox” that the AI cannot escape.


Practical Example: Refactoring a Hallucinated Palette

Let’s look at a real-world scenario. Suppose you have a generated dashboard where the colors are inconsistent.

The Broken “Hallucinated” CSS:

/* AI-generated mess */
.sidebar { background-color: #1e293b; }
.active-item { color: #38bdf8; }
.border-line { border-color: #334155; }
.button-primary { background-color: #0284c7; } /* Where did this blue come from? */

The Vibe Coding Fix (The “Pencil” Method): Instead of fixing the CSS, we fix the system using the set_variables tool. We define a semantic scale based on a single brand hue.

Step 1: Define the Base Tokens in your .pen file You would prompt the AI: “Using the Pencil tool, set variables for a Slate-Indigo theme. Brand hue is 230. Create a background-foreground scale and a primary brand scale using HSL.”

The resulting set_variables call might look like this:

{
  "theme": {
    "colors": {
      "brand": {
        "50": "hsl(230, 80%, 96%)",
        "500": "hsl(230, 66%, 45%)",
        "900": "hsl(230, 80%, 15%)"
      },
      "neutral": {
        "100": "hsl(230, 10%, 95%)",
        "800": "hsl(230, 15%, 12%)",
        "950": "hsl(230, 20%, 6%)"
      }
    }
  }
}

Step 2: Force Semantic Mapping Now, when you use the batch_design tool to update your UI, you don’t pass hex codes. You pass variable references:

U("sidebar-node-id", { "fill": "var(--neutral-950)" })
U("active-item-id", { "color": "var(--brand-500)" })
U("border-node-id", { "stroke": "var(--neutral-800)" })

By using the var() syntax within the Pencil tool, you ensure that if you ever change the brand hue from 230 (Indigo) to 160 (Emerald), the entire UI updates instantly and correctly. This is the difference between “drawing” and “architecting.”


Best Practices & Tips for Perfect Generation

To ensure your design system colors generate correctly every time, follow these intermediate-level strategies:

1. Implementation of “Contrast Constraints”

AI often ignores accessibility because it “visualizes” based on aesthetics, not math. You should include a “Constraint Clause” in your design prompts:

“All text generated must maintain a WCAG AA contrast ratio of at least 4.5:1 against its background. Use the defined --neutral-100 for text on --neutral-950 backgrounds.”

2. The “Atomic Hue” Strategy

Avoid defining separate hues for Primary, Secondary, and Accent colors unless strictly necessary. Instead, use a single hue and vary the Saturation and Lightness. For example:

  • Primary: hsl(220, 70%, 50%)
  • Muted/Secondary: hsl(220, 20%, 50%)
  • Surface: hsl(220, 10%, 98%) This ensures that the “undertone” of your app is perfectly consistent, creating a professional, cohesive look that feels “designed” rather than “assembled.”

3. Use snapshot_layout to Validate Visual Weight

Color isn’t just about the hue; it’s about the visual weight. A dark sidebar and a dark header shouldn’t always be the same color. Use the snapshot_layout tool to check the hierarchy. If the AI is making the “Search Input” the same color as the “Sidebar Background,” it will disappear. Explicitly prompt: “The input surface must be 2 steps lighter than the parent container in the neutral scale.”

4. Semantic Naming over Functional Naming

Don’t name a variable --blue-500. Name it --button-background. Why? Because if the AI decides a page needs a “High Contrast Mode,” it might change the “Blue” to “Orange.” If the variable is named --blue-500, the code becomes confusing. If it’s --button-background, the system remains logical regardless of the underlying color value.


Interactive Example: The “Theme Mirror” Test

One of the most effective ways to see if your design system is working correctly in a Vibe Coding environment is the Theme Mirror Test.

  1. Generate a complex dashboard component (e.g., a data table with status badges).
  2. Using the Pencil tool, change the brand-hue variable in your .pen file.
  3. Observe the output.

If your system is correct: Every badge, link, button, and highlight updates to the new hue perfectly. If your system is broken: You will see “residual colors”—hex codes that the AI hardcoded because it wasn’t strictly following your variables.

To fix “residual colors,” use the replace_all_matching_properties tool. This is a powerful Pencil tool that lets you sweep the entire node tree and swap hardcoded hex strings for your variables:

// Systematic cleanup
replace_all_matching_properties({
  "pattern": "#0284c7",
  "replacement": "var(--brand-500)",
  "parentId": "root"
})

Conclusion: Mastering the Vibe

Generating design system colors correctly isn’t about giving the AI better “taste.” It’s about giving the AI better infrastructure.

The AI is a “vibe” engine, but it requires a “logical” rail to run on. By defining your color system using HSL tokens, enforcing semantic naming through the Pencil MCP, and using validation tools like get_variables and snapshot_layout, you transform the AI from a chaotic artist into a disciplined design engineer.

In your next project, don’t start by prompting for “a cool blue app.” Start by defining your DESIGN.md tokens. Once the math is right, the “Vibe” will take care of itself. This is how we move from “fixing generated code” to “directing generated systems.”