# Desktop UI Color System

> Use whenever choosing a color palette, defining semantic colors, deciding how stacked/layered surfaces should read, styling active/selected/hover states, or reviewing an app for color inconsistency or the "AI purple gradient" look. Depends on desktop-ui-foundations. Trigger on "color palette," "dark mode," "this looks too purple/generic," "active state color," "elevation," or "stacked components."

- Skill: `simply-ehis/desktop-ui-color-system` (Agent Skill)
- Install (CLI): `npx skillmds@latest add simply-ehis/desktop-ui-color-system`
- Raw SKILL.md: https://api.skillmd.com/api/skills/simply-ehis/desktop-ui-color-system/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Design & Media
- Author: simply-ehis (https://skillmd.com/u/simply-ehis)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/simply-ehis/desktop-ui-color-system

---


# Color Theory (Desktop)

## Build semantic roles, not a swatch list

Don't start from "here are our 8 brand colors." Start from the roles the UI actually needs, then assign colors to roles:

| Role | Purpose |
|---|---|
| `surface-base` | The window's base background |
| `surface-raised` | Cards, panels, sidebars — one step above base |
| `surface-overlay` | Modals, popovers, menus — floats above everything |
| `border-subtle` / `border-strong` | Hairline dividers vs. emphasized separators |
| `text-primary` / `text-secondary` / `text-tertiary` | Full-contrast body text, muted supporting text, disabled/placeholder text |
| `accent` | The single brand action color — used for primary buttons, active states, links |
| `success` / `warning` / `danger` | Status colors, used consistently everywhere they appear |

Every other color decision in the app should trace back to one of these roles. If you can't name which role a color is serving, it probably shouldn't be there.

## The 60-30-10 balance

Roughly 60% of visible surface should be `surface-base`/neutral, 30% secondary surfaces and text, and only about 10% should be the `accent` color and status colors combined. Apps that break this — heavy accent color everywhere, saturated backgrounds — read as loud rather than confident. The accent color has more impact precisely because it's used sparingly.

## Avoid the AI-purple trap

Purple/violet-to-pink gradients as a primary or accent color are the strongest single "this is an AI-generated app" visual signal that currently exists. This doesn't mean purple is banned — it means:

- If the brand color happens to be purple, use it as a **flat, specific hue** (not a gradient sweep) with a name and a hex value, not "purple-ish gradient."
- Don't reach for purple-to-pink by default when no brand color has been specified — that's the generator's laziest option, not a neutral choice.
- Prefer a color story that's actually tied to the product (an existing brand motif, logo color, or the mood of what the app does) over a generic "trustworthy tech" gradient.

## Stacked / layered components — elevation via more than just shadow

When components sit on top of other components (a card inside a panel inside the window, a dropdown above a card, a modal above everything), each layer needs to read as clearly above or below the one beneath it. Use two or three of these together, not shadow alone:

1. **A one-step surface color shift** (surface-base → surface-raised → surface-overlay), even a very subtle one (a few percent lightness difference is enough in most themes).
2. **A cheap box-shadow**, sized to the elevation level from foundations (`raised` gets a small tight shadow, `floating`/`modal` get a larger, softer one) — never rely on `backdrop-blur` alone for this, it's expensive and doesn't read as elevation on its own.
3. **A hairline border** at low opacity, especially useful in dark mode where shadows read less clearly against dark backgrounds.

Never stack more than 3 visually distinct elevation levels on screen at once — beyond that, users lose track of what's "above" what, and it usually means the layout needs restructuring rather than a fourth shadow tier.

## Active / selected / hover state color logic

Use a consistent, predictable transformation from rest → hover → active/selected across every component, rather than inventing a new treatment per component:

| State | Typical treatment |
|---|---|
| Rest | Base surface/text color for that role |
| Hover | Small background tint shift toward the accent color (5-10% mix), or a subtle surface-level lightening |
| Active / pressed | Slightly stronger tint than hover, plus the scale-down motion from `desktop-ui-motion` |
| Selected (persists after click — e.g. active sidebar item, chosen tab) | A clear, ongoing marker: accent-colored background tint AND either a left-edge bar, an underline, or a filled icon state — never color alone, since color-only selected states are easy to miss and fail accessibility contrast checks for some users |

## Dark mode is not "invert the colors"

Build dark mode as its own set of role assignments, not a CSS filter. In particular:

- Shadows read poorly on dark backgrounds — lean more on the surface-color-shift and border techniques above.
- Pure black (`#000`) backgrounds create harsh contrast against white text and cause halation/glow on OLED and some LCD panels — prefer a very dark neutral gray (e.g. `#0a0a0c`–`#121214` range) as `surface-base`.
- Saturated accent colors often need to be slightly desaturated or lightened in dark mode to avoid vibrating against the dark background.

## Accessibility baseline

Body text against its background should meet at least a 4.5:1 contrast ratio; large text (18px+/bold 14px+) can go as low as 3:1. Never use color as the *only* signal for an important state (error, success, selected) — always pair it with an icon, text, or shape change.

## Review format

| Before | After | Why |
|---|---|---|
| Purple-to-pink gradient as primary accent | Flat, specific brand hue | Gradients-as-accent are the top AI-generated-app tell |
| Selected sidebar item shown only via a lighter background | Background tint + left-edge accent bar | Color-only state markers are easy to miss and fail some contrast checks |
| Every card the exact same background as the window | Cards one step up the surface scale (`surface-raised`) | Without a surface shift, layering has to be carried entirely by shadow, which reads weaker |

