Color in User Interfaces

This chapter covers practical application of color in UI design - where to use color, how much, and how to create visual hierarchy.

The Role of Color in UI

Color in interfaces serves specific functions:

FunctionExample
Brand identityPrimary colors, logo, headers
Visual hierarchyDrawing attention to important elements
FeedbackSuccess, error, warning states
AffordanceIndicating interactive elements
GroupingRelating similar elements
Mood/EmotionSetting the tone of the experience

Color Hierarchy Principle

Less color = More impact. The more restraint you show, the more power your color choices have.

┌─────────────────────────────────────────────────────┐
│                                                     │
│  ┌──────────────────────────────────────────────┐  │
│  │  Most of your interface                       │  │
│  │  should be neutral (gray, white, black)       │  │
│  │                                               │  │
│  │     ┌────────────────────────────────┐       │  │
│  │     │  Primary brand color           │       │  │
│  │     │  for key interactive elements  │       │  │
│  │     │                                │       │  │
│  │     │   ┌──────────────────────┐    │       │  │
│  │     │   │  Accent color        │    │       │  │
│  │     │   │  sparingly for       │    │       │  │
│  │     │   │  maximum impact      │    │       │  │
│  │     │   └──────────────────────┘    │       │  │
│  │     └────────────────────────────────┘       │  │
│  └──────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
     Neutrals (70-80%)   Primary (15-25%)   Accent (5-10%)

Core UI Color Categories

1. Neutral Colors (The Foundation)

Most of your UI should be neutral:

:root {
  /* Gray scale - tinted with brand hue for cohesion */
  --gray-50:  hsl(220, 15%, 98%);
  --gray-100: hsl(220, 15%, 95%);
  --gray-200: hsl(220, 12%, 88%);
  --gray-300: hsl(220, 10%, 75%);
  --gray-400: hsl(220, 8%, 60%);
  --gray-500: hsl(220, 8%, 45%);
  --gray-600: hsl(220, 10%, 35%);
  --gray-700: hsl(220, 12%, 25%);
  --gray-800: hsl(220, 15%, 17%);
  --gray-900: hsl(220, 18%, 10%);
}

Usage:

  • Backgrounds: 50-200
  • Borders: 200-300
  • Secondary text: 500-600
  • Primary text: 700-900

2. Primary Color (Brand)

Your main brand color for key interactive elements:

:root {
  --primary-50:  hsl(220, 80%, 97%);
  --primary-100: hsl(220, 75%, 92%);
  --primary-200: hsl(220, 70%, 82%);
  --primary-300: hsl(220, 65%, 68%);
  --primary-400: hsl(220, 70%, 55%);
  --primary-500: hsl(220, 80%, 50%);  /* Main brand color */
  --primary-600: hsl(220, 85%, 42%);
  --primary-700: hsl(220, 90%, 35%);
  --primary-800: hsl(220, 90%, 28%);
  --primary-900: hsl(220, 90%, 20%);
}

Usage:

  • Primary buttons
  • Links
  • Active states
  • Selected items
  • Progress indicators

3. Semantic Colors (Feedback)

Standard colors for communicating status:

:root {
  /* Success - Green */
  --success-light: hsl(142, 70%, 90%);
  --success-base: hsl(142, 70%, 45%);
  --success-dark: hsl(142, 80%, 30%);

  /* Warning - Yellow/Orange */
  --warning-light: hsl(45, 90%, 88%);
  --warning-base: hsl(45, 90%, 50%);
  --warning-dark: hsl(38, 90%, 40%);

  /* Error - Red */
  --error-light: hsl(0, 80%, 92%);
  --error-base: hsl(0, 75%, 55%);
  --error-dark: hsl(0, 80%, 40%);

  /* Info - Blue (or use primary) */
  --info-light: hsl(210, 80%, 92%);
  --info-base: hsl(210, 80%, 50%);
  --info-dark: hsl(210, 85%, 38%);
}

4. Accent Color (Optional)

A contrasting color for special emphasis:

:root {
  /* Complementary or split-complementary to primary */
  --accent: hsl(40, 90%, 50%);  /* Orange accent with blue primary */
}

Usage:

  • CTAs that need extra attention
  • Sale badges
  • New/featured items
  • Promotional elements

Applying Color to Common Elements

Buttons

/* Primary action - brand color */
.btn-primary {
  background: var(--primary-500);
  color: white;
}
.btn-primary:hover {
  background: var(--primary-600);
}
.btn-primary:active {
  background: var(--primary-700);
}

/* Secondary action - outlined or muted */
.btn-secondary {
  background: transparent;
  border: 1px solid var(--gray-300);
  color: var(--gray-700);
}
.btn-secondary:hover {
  background: var(--gray-100);
  border-color: var(--gray-400);
}

/* Tertiary/Ghost - minimal */
.btn-ghost {
  background: transparent;
  color: var(--primary-500);
}
.btn-ghost:hover {
  background: var(--primary-50);
}

/* Destructive - red */
.btn-danger {
  background: var(--error-base);
  color: white;
}

Form Elements

/* Input default state */
.input {
  border: 1px solid var(--gray-300);
  background: white;
}

/* Focus state - primary color */
.input:focus {
  border-color: var(--primary-500);
  box-shadow: 0 0 0 3px var(--primary-100);
}

/* Error state */
.input.error {
  border-color: var(--error-base);
}
.input.error:focus {
  box-shadow: 0 0 0 3px var(--error-light);
}

/* Success state (after validation) */
.input.valid {
  border-color: var(--success-base);
}
/* Default link */
a {
  color: var(--primary-500);
  text-decoration: underline;
}

a:hover {
  color: var(--primary-700);
}

a:visited {
  color: var(--primary-600);
}

/* Link within dark backgrounds */
.dark-bg a {
  color: var(--primary-300);
}

Alerts/Notifications

.alert {
  padding: 12px 16px;
  border-radius: 6px;
  border-left: 4px solid;
}

.alert-success {
  background: var(--success-light);
  border-color: var(--success-base);
  color: var(--success-dark);
}

.alert-warning {
  background: var(--warning-light);
  border-color: var(--warning-base);
  color: var(--warning-dark);
}

.alert-error {
  background: var(--error-light);
  border-color: var(--error-base);
  color: var(--error-dark);
}

.alert-info {
  background: var(--info-light);
  border-color: var(--info-base);
  color: var(--info-dark);
}

Badges/Tags

/* Neutral badge */
.badge {
  background: var(--gray-200);
  color: var(--gray-700);
}

/* Status badges */
.badge-success {
  background: var(--success-light);
  color: var(--success-dark);
}

.badge-new {
  background: var(--primary-500);
  color: white;
}

Cards

.card {
  background: white;
  border: 1px solid var(--gray-200);
  /* OR use shadow instead of border */
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.card:hover {
  border-color: var(--gray-300);
  /* OR */
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* Selected card */
.card.selected {
  border-color: var(--primary-500);
  box-shadow: 0 0 0 3px var(--primary-100);
}

Visual Hierarchy with Color

Text Hierarchy

:root {
  --text-primary: var(--gray-900);    /* Headings, important text */
  --text-secondary: var(--gray-600);  /* Body text, descriptions */
  --text-muted: var(--gray-500);      /* Captions, timestamps */
  --text-disabled: var(--gray-400);   /* Disabled labels */
}
<article>
  <h1 style="color: var(--text-primary)">Main Heading</h1>
  <p style="color: var(--text-secondary)">Body paragraph with regular content...</p>
  <span style="color: var(--text-muted)">Posted 2 hours ago</span>
</article>

Background Hierarchy

/* Layer backgrounds to create depth */
.page {
  background: var(--gray-100);
}

.sidebar {
  background: var(--gray-50);
}

.card {
  background: white;
}

.modal-overlay {
  background: rgba(0, 0, 0, 0.5);
}

.modal {
  background: white;
}

Using Color for Focus

Draw attention to the most important element:

/* Everything muted except the CTA */
.pricing-card {
  background: white;
  border: 1px solid var(--gray-200);
}

.pricing-card.featured {
  border: 2px solid var(--primary-500);
  box-shadow: 0 4px 20px rgba(var(--primary-500), 0.2);
}

/* The primary action stands out */
.cta {
  background: var(--primary-500);
  color: white;
}

/* Secondary actions recede */
.secondary-link {
  color: var(--gray-600);
}

Interactive States

Every interactive element needs clear state changes:

StateColor Treatment
DefaultBase color
HoverSlightly darker/more saturated
Active/PressedEven darker
FocusOutline or ring in primary color
DisabledReduced opacity or grayed out
SelectedPrimary color background or border
.interactive {
  /* Default */
  background: var(--primary-500);

  /* Hover - darken */
  &:hover {
    background: var(--primary-600);
  }

  /* Active - darker still */
  &:active {
    background: var(--primary-700);
  }

  /* Focus - visible ring */
  &:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 2px;
  }

  /* Disabled - muted */
  &:disabled {
    background: var(--gray-300);
    color: var(--gray-500);
    cursor: not-allowed;
  }
}

Common Color Mistakes in UI

1. Too Many Colors

Problem: Using 5+ distinct hues makes interfaces feel chaotic.

Solution: Limit to 1-2 hues plus neutrals and semantic colors.

2. Equal Emphasis

Problem: Everything is the same color weight - nothing stands out.

Solution: Create clear hierarchy. One element is primary, others are secondary.

3. Saturated Backgrounds

Problem: Large areas of vivid color cause eye strain.

/* Bad */
.sidebar {
  background: hsl(220, 80%, 50%);  /* Too intense for large areas */
}

/* Better */
.sidebar {
  background: hsl(220, 30%, 95%);  /* Subtle tint */
}

4. Pure Black and White

Problem: Pure black (#000) on pure white (#FFF) can be harsh.

/* Harsh */
.text {
  color: #000000;
  background: #FFFFFF;
}

/* Softer */
.text {
  color: hsl(220, 15%, 15%);
  background: hsl(220, 15%, 99%);
}

5. Inconsistent Hover States

Problem: Different buttons have different hover behaviors.

Solution: Systematize your state changes:

:root {
  --hover-darken: 8%;   /* Consistent darkening */
  --active-darken: 15%;
}

Practical Example: Dashboard Color System

:root {
  /* Neutrals */
  --bg-page: hsl(220, 15%, 97%);
  --bg-card: hsl(0, 0%, 100%);
  --bg-elevated: hsl(0, 0%, 100%);

  --border-light: hsl(220, 15%, 90%);
  --border-default: hsl(220, 12%, 82%);

  --text-primary: hsl(220, 20%, 15%);
  --text-secondary: hsl(220, 10%, 45%);
  --text-muted: hsl(220, 10%, 60%);

  /* Primary (Blue) */
  --primary-50: hsl(220, 80%, 97%);
  --primary-100: hsl(220, 75%, 92%);
  --primary-500: hsl(220, 80%, 50%);
  --primary-600: hsl(220, 85%, 42%);
  --primary-700: hsl(220, 90%, 35%);

  /* Semantic */
  --success: hsl(142, 70%, 45%);
  --warning: hsl(45, 90%, 50%);
  --error: hsl(0, 75%, 55%);

  /* Data visualization (distinct, accessible) */
  --chart-1: hsl(220, 70%, 50%);
  --chart-2: hsl(160, 60%, 45%);
  --chart-3: hsl(280, 60%, 55%);
  --chart-4: hsl(30, 80%, 55%);
}

Summary

PrincipleApplication
Less is more1-2 hues + neutrals + semantic
Hierarchy mattersOne primary action, rest secondary
Neutrals dominate70-80% of interface
States are essentialHover, active, focus, disabled
Consistency winsSame treatment for same elements
Context is kingTest colors in actual layouts

Next: 08-building-palettes.md - Creating complete color systems