Detecting System Color Scheme
Read the user's OS dark mode preference with prefers-color-scheme and update React state when it changes.
Detecting System Color Scheme is a free React Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the React Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The prefers-color-scheme Media Query
The prefers-color-scheme CSS media query detects whether the user has configured their operating system to use a dark or light color theme. It accepts two values: dark and light. This preference is set at the OS level in system settings, not in the browser.
CSS Dark Mode via Media Query
You can apply dark styles purely in CSS using @media (prefers-color-scheme: dark) { ... }. Inside this block, you override colors, backgrounds, and borders to suit a dark palette. This approach requires no JavaScript and works even before React mounts.
window.matchMedia in JavaScript
In JavaScript, window.matchMedia('(prefers-color-scheme: dark)') returns a MediaQueryList object. You can use this to read the current system preference programmatically inside React hooks or event handlers. This is the bridge between CSS media queries and your JS logic.
Reading the Initial Value
The MediaQueryList object has a matches property that returns a boolean. If matches is true, the system is in dark mode; if false, it is in light mode. You typically read this once on mount to initialize your theme state.
Listening for System Changes
Users can change their OS color scheme at any time. To respond dynamically, call mediaQuery.addEventListener('change', handler) where handler receives the updated MediaQueryListEvent. The event object also has a matches boolean reflecting the new preference.
Building usePrefersDarkMode Hook
A clean approach is to build a custom hook using useSyncExternalStore. The hook subscribes to mediaQuery.addEventListener and provides a snapshot function that returns mediaQuery.matches. This keeps the hook in sync with the external browser API without manual state management.
Unsubscribing on Cleanup
When your component unmounts, you must remove the event listener to avoid memory leaks. In a useEffect cleanup, call mediaQuery.removeEventListener('change', handler). With useSyncExternalStore, the subscribe function must return an unsubscribe function that does the same.
System Preference vs User Override
The system preference is what the OS reports. But users often want to override the app theme independently, for example keeping the app light while the OS is in dark mode. Your architecture should distinguish between the system default and an explicit user override stored in localStorage.
Emulating Color Scheme in DevTools
Chrome DevTools lets you emulate color scheme preferences without changing your OS settings. Open DevTools, go to the Rendering tab, and find the "Emulate CSS media feature prefers-color-scheme" dropdown. This is invaluable for testing both themes quickly during development.
Respecting User Preference by Default
A good dark mode implementation reads the system preference as the default starting point and applies it immediately. This shows respect for the user accessibility setting. Only after the user explicitly toggles the theme in your app should you store and use a different override.
SSR Consideration for System Preference
On the server, window.matchMedia does not exist. If you render on the server, guard with typeof window !== 'undefined' before calling matchMedia. For SSR apps, read the user preference from a cookie or HTTP header instead to avoid hydration mismatches.
matchMedia API Check
What property of a MediaQueryList object tells you whether the media query currently matches?
Lesson Recap: Detecting System Color Scheme
The prefers-color-scheme media query exposes the OS color preference in both CSS and JavaScript. Use window.matchMedia to read and listen for changes, always clean up event listeners, and distinguish system defaults from user overrides. Chrome DevTools emulation speeds up testing both themes.
Frequently asked questions
Is the “Detecting System Color Scheme” lesson free?
Yes — the full text of “Detecting System Color Scheme” is free to read here on the web, and the React Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the React Academy course, upgrade to CoddyKit PRO.
What will I learn in “Detecting System Color Scheme”?
Read the user's OS dark mode preference with prefers-color-scheme and update React state when it changes. You practise React Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start React Academy?
No prior experience is required. React Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Detecting System Color Scheme” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this React Academy lesson?
Yes. Every React Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Detecting System Color Scheme
- CSS Variables for Theme Switching
- React Context for Theme State
- Persisting Theme and Preventing Flash