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.
- It reads the user’s preferred color scheme, not a site setting by itself.
- It supports the values light and dark.
- It works inside normal CSS media queries.
- It is often paired with the color-scheme property for better built-in form and UI control styling.
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:
- It reduces eye strain in low-light environments.
- It makes your site feel native on modern devices.
- It helps browser-built controls blend into your design.
- It gives you a standards-based way to adapt styles without JavaScript.
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:
- Marketing sites that should match the visitor’s device theme.
- Documentation pages that need readable dark mode for long-form text.
- Dashboards where charts, panels, and cards need a dark alternative.
- Product landing pages with logos or illustrations that need theme-specific assets.
- Forms and account pages where native controls should feel consistent with the chosen theme.
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
- prefers-color-scheme reflects user preference, not always a site-specific theme choice.
- Browser support is strong in modern browsers, but older browsers may ignore the media query and stay on the default theme.
- The color-scheme property affects browser-native UI more than your custom CSS.
- Dark mode can still look wrong if an image, video thumbnail, or brand asset was created only for light backgrounds.
- Some controls and embedded content from third parties may not fully follow your theme.
- If you use CSS custom properties, remember that inherited values can affect nested components in ways you might not expect.
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
- prefers-color-scheme lets CSS react to the user’s preferred light or dark theme.
- Use it with a default theme and dark overrides, not as your only styling rule.
- color-scheme helps browser UI elements match your supported themes.
- Custom properties make theme maintenance simpler and more consistent.
- Dark mode must still be designed carefully for contrast, icons, images, and forms.
11. Practice Exercise
- Create a simple page with a heading, paragraph, link, and button.
- Set a light theme as the default.
- Add a dark theme using @media (prefers-color-scheme: dark).
- Use CSS custom properties for background, text, border, and accent colors.
- Make sure the button and link remain readable in both themes.
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.