Vibe Coding Color Checks: Light, Dark, and Interaction States
What you’ll learn
- Why colors need to be checked across light and dark modes combined with hover, selection, and focus
- How to share color criteria and colors defined by purpose, then include them in AI instructions
- How to check the same display states and components whenever reviewing code changes
Color Problems I Missed in the Default View
When I started building this site, I thought choosing colors that made text readable in the default view would be enough. But switching between dark and light modes or interacting with buttons and cards repeatedly caused problems: text became harder to read, or selected items became harder to identify.
Alongside the default view, I needed to check hover (when a pointer is over an item), selected states, and focus (when an item is the target of keyboard or other input). Color accessibility—making text and interaction states easy to distinguish—could not be maintained simply by choosing colors one at a time.
As I continued Vibe Coding, an approach to implementation using natural-language instructions to AI, I realized that combinations of theme and interaction state needed to be included in the rules I gave AI before implementation. I added these checks to my harness, a system of rules, procedures, and verification methods for AI, and separated automated checks from checks of the rendered interface.
This article describes the problems I encountered on this site and the rules I established to reduce their recurrence. By looking at differences between the appearance I expected and the actual display, the conditions that caused problems, and the criteria for the next implementation, you can identify what to check on your own site.
How Theme and Interaction State Change Appearance
At first, I fixed colors individually wherever I found a problem: “this button is too pale” or “this card’s selected state is hard to see.” But investigating the causes showed that the problem was how appearance changed with combinations of theme and interaction state, rather than any one color alone.
For example, text may be readable in the default light-mode view, yet become difficult to read when hovering in dark mode because the text and background colors become too similar. The text on a selected card may remain readable while its focus ring—the outline indicating the target of input—disappears into the background or shadow, making it unclear which card is being operated.
AI can quickly produce a polished first draft. However, that does not mean every theme and interaction state has been checked. A comparison grid needs to cover the default, hover, selected, and focus appearances in both light and dark themes.
The minimum contrast criterion in WCAG 2.2, the international guidelines for web accessibility, requires a contrast ratio of at least 4.5:1 for normal text and at least 3:1 for large text.[1] A contrast ratio expresses the difference in brightness between elements such as text and its background as a number.
A screenshot of the default view alone cannot establish readability during interaction. Alongside theme and interaction state, the background and text size also need to be checked in combination.
Limits of Manual Review and Automated Checks
Checking everything manually means remembering the checks for every implementation. As similar UI elements (interactive parts of a screen), such as article cards, quizzes, sidebars, pagination (controls for moving between pages), and tag links accumulate, the number of states to check grows too.
Automated testing tools help with these checks. However, W3C explains that not every accessibility requirement can be checked automatically and that human review is needed.[2]
On this site, I therefore treat AI instructions, shared rules, colors defined by purpose, diff review (reviewing code changes), and browser checks as one workflow. Installing a tool does not complete the work; I separate what can be checked automatically from what requires human judgment.
Color Rules to Share Before Implementing Components
I first organized the checks for adding or changing components, reusable interface elements such as buttons and cards. On this site, the accessibility rules for components under src/components/ live in shared/rules/component-accessibility.md.
Color choices follow these shared rules instead of being left to AI’s judgment on every implementation. For new components, my policy is to select colors for default, hover, focus, and selected states from existing CSS variables named by purpose. CSS variables let values such as colors and spacing be named and referenced from multiple places.
The preferred CSS variables depend on the UI element’s purpose.
| UI purpose | Preferred CSS variables |
|---|---|
| Links and button-style UI | --btn-ghost-* |
| Choices and numbered badge-style UI | --btn-choice-* |
| Containers around circular icons or numbers | --topic-btn-icon-* |
This rule makes it easier to avoid AI repeatedly using --sl-color-accent directly or creating new state colors with arbitrary color-mix() values, a CSS function for mixing colors.
When existing CSS variables cannot express the colors needed, I first add a CSS variable named for its purpose. Rather than specifying the color only inside one component, I make it available for other implementations to reference.
Sharing Color Fixes Through CSS Variables Named by Purpose
src/styles/custom.css collects the CSS variables used in both light and dark themes for buttons, choices, topic icons, and pagination. Incorporating a component’s color fix there lets later implementations use the same color definitions.
During review, I examine theme, interaction, whether an item is selected, background, and the method of fixing colors separately.
| Area | States or targets to check | Approach in shared rules or implementation |
|---|---|---|
| Theme | Light / dark | Define CSS variables with the same purpose names in both themes |
| Interaction | Default / hover / focus | Review focus-visible and hover colors in the same place |
| Selection | Unselected / selected | Separate selection CSS variables such as --btn-choice-* |
| Background | Card / sidebar / body | Refer to existing components with similar purposes |
| Fix method | Individual color definitions / adding CSS variables | Give reusable colors a shared purpose name |
focus-visible is a CSS selector for applying styles such as outlines when focus needs a visible indication, typically during keyboard interaction.
I use this table for both AI instructions and my own review. Its purpose is to make the review cover the same areas from the same perspectives every time, while retaining human checks.
Specifying Themes and Interaction States in AI Instructions
Previously, I asked AI to “consider accessibility” or “make it readable in dark mode.” Those instructions alone did not adequately communicate which states I wanted checked.
I now specify themes and interaction states explicitly:
Implement a new selectable card component.
Color and state requirements:
- Check both light mode and dark mode
- Define default, hover, focus, and selected states
- Prefer the existing --btn-choice-* CSS variables
- Do not add new color-mix() values directly
- Adjust normal-text colors to meet WCAG AA contrast requirements
- Ensure the focus-visible outline is not hidden by backgrounds or shadows
After implementation, briefly report the CSS variables used for each state and any states that remain unverified.In this example, “default” means the normal view, and “WCAG AA” refers to one of WCAG’s conformance levels. The request asks for text colors to meet the contrast requirements at that level.
I place particular importance on the final instruction to report unverified states. Even when AI reports that work is complete, some states may remain unchecked. I include the reporting format in the harness so that work is not treated as complete while checks remain outstanding.
Color Definitions and State Appearance to Check in Diff Review
After implementation, I review the code changes before checking the appearance in the browser. For color accessibility, I focus on these points:
- Does the component define new state colors directly instead of using existing CSS variables?
- Do the definitions cover only light mode or only dark mode?
- Is
:hoverpresent without:focus-visible? - Is selection conveyed only through background color?
- Does the focus ring disappear into shadows, borders, or background colors?
- Does the appearance match the states indicated by attributes such as
disabled,aria-selected, andaria-current?
In the final item, disabled indicates that a control cannot be operated, aria-selected indicates selection, and aria-current identifies the current item. I check whether the states specified in code agree with their appearance.
These checks are not fully automated in the current harness. npm run harness:check detects blog quality issues, repository-claim problems, duplicate content, and inconsistencies in the shared harness, but it does not judge the readability of every color combination.
I therefore combine shared rules and diff review with checks of the display in the browser. Machine checks detect the problems they can identify, while a person judges the meaning conveyed by colors and their actual appearance.
Summary: Check Colors Across Themes and Display States
Maintaining readable colors in Vibe Coding requires checking combinations of both light and dark themes with default, hover, selected, and focus states. Including these checks in shared rules and AI instructions before implementation, and sharing color definitions through CSS variables named by purpose, can reduce repeated problems.
Diff review and automated checks reduce omissions, while a person checks the meaning conveyed by colors and their actual appearance on screen. Choosing one frequently used UI component and organizing the theme and state combinations to check makes the review target concrete.
References
- W3C, Web Content Accessibility Guidelines (WCAG) 2.2, December 12, 2024
- W3C Web Accessibility Initiative, Selecting Web Accessibility Evaluation Tools, updated May 13, 2024
For the latest releases and updates, check the official website and official documentation.