CSS Color Schemes and prefers-color-scheme Media Query

CSS color schemes let your site respond to the user’s preferred light or dark appearance. The prefers-color-scheme media query helps you style pages differently when the operating system is set to dark mode or light mode, so your UI can feel more comfortable and more consistent with the rest of the device.

Quick answer: Use @media (prefers-color-scheme: dark) to apply dark-theme styles when the user prefers a dark interface. Add the color-scheme property when you want the browser to also render built-in controls, scrollbars, and form elements in a matching style.

Difficulty: Beginner

You’ll understand this better if you know: basic CSS selectors, how media queries work, and how cascading styles override earlier rules.

1. What Is Color Schemes (prefers-color-scheme)?

The prefers-color-scheme media query is a CSS feature that detects whether the user has asked for a light or dark color theme at the operating-system or browser level. You can then change text colors, backgrounds, borders, images, and other visual details to match that preference.

This topic is about adapting design to system preferences, not about building a manual theme switcher with a button.

2. Why Color Schemes Matter

Users increasingly expect websites to respect their device theme. A page that ignores dark mode can feel harsh, hard to read, or visually inconsistent with the rest of the system.

Color-scheme support matters because it improves comfort, accessibility, and polish:

You should use it when your design has meaningful light and dark variants. If your brand or content is already optimized for a single neutral palette, you may not need separate theme rules.

3. Basic Syntax or Core Idea

The basic pattern is a media query that targets a preferred scheme and overrides your default styles. In many projects, you start with a light theme as the base and add dark-mode rules inside a media query.

Minimal media query example

This example changes the page background and text color when the user prefers dark mode.

body {
  background: #ffffff;
  color: #111111;
}

@media (prefers-color-scheme: dark) {
  body {
    background: #111111;
    color: #f5f5f5;
  }
}

The first rule sets the default look. The media query then replaces those colors only when the user’s preference is dark.

Adding the color-scheme property

The color-scheme property tells the browser which schemes your page supports. This helps built-in controls such as inputs, scrollbars, and dialogs match your styling more closely.

html {
  color-scheme: light dark;
}

This does not replace your theme styles. It informs the browser that both schemes are acceptable.

4. Step-by-Step Examples

Example 1: A simple page that adapts to dark mode

Start with readable light-mode styles, then override only the values that need to change in dark mode.

body {
  font-family: Arial, sans-serif;
  background: #fafafa;
  color: #1a1a1a;
}

@media (prefers-color-scheme: dark) {
  body {
    background: #121212;
    color: #e6e6e6;
  }
}

This pattern is easy to maintain because you only override the theme-specific values.

Example 2: Styling cards and borders for both schemes

Theme changes often need more than just page background and text. Cards, borders, and shadows usually need adjustment too.

.card {
  background: #ffffff;
  border: 1px solid #d9d9d9;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}

@media (prefers-color-scheme: dark) {
  .card {
    background: #1e1e1e;
    border: 1px solid #333333;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
  }
}

In dark mode, the same component stays visible and subtle without using overly bright borders or shadows.

Example 3: Using system colors and matching form controls

Forms are a common place where theme support looks incomplete. The browser may style inputs differently unless you tell it that both schemes are supported.

html {
  color-scheme: light dark;
}

input,
button {
  font: inherit;
  padding: 0.75rem 1rem;
}

@media (prefers-color-scheme: dark) {
  input,
  button {
    background: #2a2a2a;
    color: #f2f2f2;
    border: 1px solid #444444;
  }
}

Here, the browser and your custom styles work together so form controls feel consistent.

Example 4: Showing different images or assets by theme

Some visuals need a different version in dark mode, especially logos or illustrations with transparent backgrounds.

.logo-light {
  display: block;
}

.logo-dark {
  display: none;
}

@media (prefers-color-scheme: dark) {
  .logo-light {
    display: none;
  }

  .logo-dark {
    display: block;
  }
}

This lets you swap assets without changing layout or adding scripting.

5. Practical Use Cases

Use prefers-color-scheme when your project needs automatic theme awareness. Common situations include:

If users must explicitly choose a theme inside your site, you can still use prefers-color-scheme as the default and then layer a user preference on top later.

6. Common Mistakes

Mistake 1: Forgetting the default theme

Some developers write only dark-mode rules and assume the page will look good everywhere. That leaves the default appearance undefined for users whose system is not in dark mode.

Problem: Without a base theme, the page can inherit browser defaults or partially styled elements, which often makes the light experience inconsistent.

@media (prefers-color-scheme: dark) {
  body {
    background: #121212;
    color: #f5f5f5;
  }
}

Fix: Define a complete default theme first, then override what changes in dark mode.

body {
  background: #ffffff;
  color: #1a1a1a;
}

@media (prefers-color-scheme: dark) {
  body {
    background: #121212;
    color: #f5f5f5;
  }
}

The corrected version works because the browser always has a complete base style to fall back to.

Mistake 2: Using only color-scheme and expecting full theme changes

The color-scheme property informs the browser about supported schemes, but it does not automatically rewrite your custom colors.

Problem: If you set only color-scheme, text, backgrounds, and component colors you authored yourself may still remain unreadable in dark mode.

html {
  color-scheme: light dark;
}

body {
  background: #ffffff;
  color: #000000;
}

Fix: Pair color-scheme with actual dark-mode overrides.

html {
  color-scheme: light dark;
}

body {
  background: #ffffff;
  color: #000000;
}

@media (prefers-color-scheme: dark) {
  body {
    background: #111111;
    color: #f2f2f2;
  }
}

The fixed version works because browser UI and author styles both respond to the theme.

Mistake 3: Overriding every value instead of using shared tokens

When a project has many components, copying the same colors into every dark-mode rule creates duplication and inconsistency.

Problem: Repeating hard-coded colors across many selectors makes theme maintenance harder and increases the chance of mismatched shades.

.header {
  background: #ffffff;
  color: #111111;
}

.sidebar {
  background: #ffffff;
  color: #111111;
}

@media (prefers-color-scheme: dark) {
  .header {
    background: #121212;
    color: #f5f5f5;
  }

  .sidebar {
    background: #121212;
    color: #f5f5f5;
  }
}

Fix: Use CSS custom properties so one change updates the whole theme.

:root {
  --page-bg: #ffffff;
  --text-color: #111111;
}

.header,
.sidebar {
  background: var(--page-bg);
  color: var(--text-color);
}

@media (prefers-color-scheme: dark) {
  :root {
    --page-bg: #121212;
    --text-color: #f5f5f5;
  }
}

The corrected version scales better because theme values are defined in one place.

7. Best Practices

Practice 1: Define theme tokens with custom properties

Tokens make it easier to keep the light and dark themes consistent across many components.

:root {
  --bg: #ffffff;
  --fg: #111111;
  --accent: #005fcc;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121212;
    --fg: #f4f4f4;
    --accent: #6ab0ff;
  }
}

This approach keeps theme logic separate from component structure.

Practice 2: Keep contrast high in both themes

Dark mode is not just inverted colors. Text, links, borders, and placeholders still need enough contrast to be readable.

a {
  color: #005fcc;
}

@media (prefers-color-scheme: dark) {
  a {
    color: #8ec5ff;
  }
}

This matters because colors that look fine on white can become too dim on a dark background.

Practice 3: Style images and icons for both backgrounds

Some graphics need filters, outlines, or separate assets so they remain visible in both themes.

.icon {
  opacity: 0.9;
}

@media (prefers-color-scheme: dark) {
  .icon {
    filter: invert(1) grayscale(1);
  }
}

This can be useful for simple monochrome icons, but separate assets are often better for logos and illustrations.

8. Limitations and Edge Cases

A common “not working” report is that form inputs still look light in dark mode. In many cases, the fix is to declare supported schemes on the root element and then style the inputs explicitly.

9. Practical Mini Project

Here is a small theme-aware article card. It uses a default light theme, dark-mode overrides, and browser-supported color scheme hints.

html {
  color-scheme: light dark;
}

:root {
  --bg: #f8f8f8;
  --surface: #ffffff;
  --text: #1b1b1b;
  --muted: #666666;
  --border: #dddddd;
  --link: #005fcc;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f1115;
    --surface: #181c23;
    --text: #f2f2f2;
    --muted: #aaaaaa;
    --border: #2c3440;
    --link: #8ec5ff;
  }
}

body {
  margin: 0;
  font-family: system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
}

.article-card {
  max-width: 42rem;
  margin: 2rem auto;
  padding: 1.5rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
}

.article-card a {
  color: var(--link);
}

.meta {
  color: var(--muted);
}

This example is complete enough to reuse: the browser gets a supported color scheme, the theme values stay centralized, and the component adjusts automatically when dark mode is active.

10. Key Points

11. Practice Exercise

Expected output: The page should appear light in a light system theme and switch to a dark, high-contrast palette in dark mode.

Hint: Define the shared colors on :root, then override only the custom properties inside the media query.

Solution:

html {
  color-scheme: light dark;
}

:root {
  --bg: #ffffff;
  --text: #202124;
  --border: #d9d9d9;
  --accent: #0b63ce;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121212;
    --text: #f2f2f2;
    --border: #2d2d2d;
    --accent: #8ec5ff;
  }
}

body {
  margin: 0;
  padding: 2rem;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, sans-serif;
}

.panel {
  max-width: 40rem;
  padding: 1.25rem;
  border: 1px solid var(--border);
  border-radius: 0.75rem;
}

a {
  color: var(--accent);
}

button {
  padding: 0.75rem 1rem;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
}

12. Final Summary

prefers-color-scheme is the CSS media query you use to adapt a page to the user’s light or dark preference. It is one of the cleanest ways to make a website feel modern and comfortable without requiring scripts or custom theme storage.

The best results come from combining three ideas: a solid default theme, dark-mode overrides for meaningful visual differences, and the color-scheme property so browser UI can match your design. When you centralize theme values with CSS custom properties, the whole system becomes easier to read, maintain, and extend.

If you want to go further, the next useful topic is building a manual theme toggle that stores a user’s choice while still respecting prefers-color-scheme as the starting point.