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:
| Function | Example |
|---|---|
| Brand identity | Primary colors, logo, headers |
| Visual hierarchy | Drawing attention to important elements |
| Feedback | Success, error, warning states |
| Affordance | Indicating interactive elements |
| Grouping | Relating similar elements |
| Mood/Emotion | Setting 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);
}
Links
/* 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:
| State | Color Treatment |
|---|---|
| Default | Base color |
| Hover | Slightly darker/more saturated |
| Active/Pressed | Even darker |
| Focus | Outline or ring in primary color |
| Disabled | Reduced opacity or grayed out |
| Selected | Primary 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
| Principle | Application |
|---|---|
| Less is more | 1-2 hues + neutrals + semantic |
| Hierarchy matters | One primary action, rest secondary |
| Neutrals dominate | 70-80% of interface |
| States are essential | Hover, active, focus, disabled |
| Consistency wins | Same treatment for same elements |
| Context is king | Test colors in actual layouts |
Next: 08-building-palettes.md - Creating complete color systems