Use CSS prefers-color-scheme when your Next.js site should follow the visitor’s operating-system setting. Add a root .dark class or data-theme="dark" attribute when users need a manual choice, and resolve that choice before rendering theme-dependent UI. In the App Router, this distinction matters because server-rendered HTML, Client Components, and hydration must agree.
Choose the right dark-mode model
Next.js uses a file-system-based App Router built on React features such as Server Components, Suspense, and Server Functions (Next.js App Router documentation). Dark mode is primarily a styling and state decision rather than a router feature.
| Requirement | Recommended approach | State location |
|---|---|---|
| Follow only the operating-system setting | CSS @media (prefers-color-scheme: dark) |
Browser preference |
| Light/dark buttons | Root class or data attribute plus client state | Local storage, cookie, or account setting |
| Light, dark, and system modes | Persist an explicit value and resolve “system” with matchMedia |
Stored preference plus OS preference |
| Different logos or illustrations | CSS media queries or a responsive <picture> |
Asset selection in the browser |
System-preference dark mode with CSS
If there is no manual override, keep the implementation server-safe and dependency-free:
:root {
color-scheme: light;
--background: #ffffff;
--foreground: #171717;
--surface: #f3f4f6;
}
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--background: #111827;
--foreground: #f9fafb;
--surface: #1f2937;
}
}
body {
background: var(--background);
color: var(--foreground);
}
.card {
background: var(--surface);
}
Import this stylesheet from app/layout.tsx (or the global stylesheet configured by your project). The browser evaluates the media query on the initial visit, so no client-side theme state is required. The color-scheme declaration also lets native controls, scrollbars, and form widgets select a compatible palette.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11#1 Best Overall
- Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
- Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
- Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
- Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
- 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.
Reacting when the operating-system setting changes
CSS updates automatically when the OS preference changes. JavaScript is only necessary when application behavior—not just colors—must change. In a Client Component, subscribe to window.matchMedia('(prefers-color-scheme: dark)') and remove the listener in the effect cleanup.
Manual light and dark themes
Selector-driven styling makes the user’s explicit choice authoritative. Tailwind CSS follows prefers-color-scheme by default; its current documentation explains how to override the dark variant with a .dark class or [data-theme=dark] selector (Tailwind dark-mode documentation).
Class-based CSS without a library
:root {
--background: #ffffff;
--foreground: #171717;
}
:root.dark {
--background: #111827;
--foreground: #f9fafb;
}
body { background: var(--background); color: var(--foreground); }
Set the selector on document.documentElement, not on an individual page component, so navigation and shared UI update together.
A three-way toggle
Store one of light, dark, or system. For the first two values, apply the corresponding root class. For system, inspect matchMedia and apply dark only while the OS is dark. Subscribe to media-query changes while system mode is active. Persist the explicit value so a refresh and a cross-page navigation preserve the selection.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #2
- 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
- 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
- Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
- 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
- What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.
'use client'
import { useEffect, useState } from 'react'
type Theme = 'light' | 'dark' | 'system'
export function ThemeToggle() {
const [theme, setTheme] = useState<Theme>('system')
useEffect(() => {
const saved = localStorage.getItem('theme') as Theme | null
if (saved === 'light' || saved === 'dark' || saved === 'system') setTheme(saved)
}, [])
useEffect(() => {
localStorage.setItem('theme', theme)
const media = window.matchMedia('(prefers-color-scheme: dark)')
const apply = () => {
const dark = theme === 'dark' || (theme === 'system' && media.matches)
document.documentElement.classList.toggle('dark', dark)
document.documentElement.dataset.theme = dark ? 'dark' : 'light'
}
apply()
media.addEventListener('change', apply)
return () => media.removeEventListener('change', apply)
}, [theme])
return (
<select value={theme} onChange={e => setTheme(e.target.value as Theme)}>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
)
}
This minimal component intentionally starts in system mode. For a flash-free first paint, use a cookie that the server can read or an inline, CSP-compatible initialization script in the document head; local storage is available only after JavaScript runs.
Using next-themes with the App Router
next-themes supplies a provider and theme state. Its App Router pattern places ThemeProvider beneath the root layout’s <html> and <body>. Add suppressHydrationWarning to <html> because the provider modifies that element.
// app/providers.tsx
'use client'
import { ThemeProvider } from 'next-themes'
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
)
}
// app/layout.tsx
import { Providers } from './providers'
import './globals.css'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body><Providers>{children}</Providers></body>
</html>
)
}
Preventing hydration mismatches
Do not render a button whose label depends on useTheme() until the component is mounted. The server cannot know the browser’s resolved theme, so rendering “Switch to dark” on the server and “Switch to light” in the browser can produce a mismatch. Track mounted in an effect and render a placeholder, disabled control, or nothing until it is true.
Dark and light images
Next.js documents CSS media-query and <picture> approaches for alternate assets (Next.js Image documentation).
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #3
- Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
- Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
- Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
- Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
- What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.
<picture>
<source media="(prefers-color-scheme: dark)" srcSet="/hero-dark.png" />
<img src="/hero-light.png" alt="Product dashboard" />
</picture>
When using two Next.js Image components, ordinary lazy loading generally loads only the visible variant. Setting both to eager can download both files. For an above-the-fold image that must be fetched quickly, use the documented fetchPriority option, while keeping only the needed variant high priority.
Tailwind selector configuration
Tailwind’s default dark variant follows the system preference. To make a class or data attribute control it, configure the variant as described in its dark-mode guide, then apply dark to the root element or use a data-theme selector. A three-way switch still needs JavaScript to persist the selected mode and translate system mode into the active selector.
CSS-in-JS in the App Router
Next.js documents an App Router integration using a style registry, useServerInsertedHTML, and a Client Component wrapper (Next.js CSS-in-JS guide). Confirm that your library supports Server Components and streaming; otherwise styles can arrive late or behave differently between server and client. Prefer the integration supplied by the library’s current documentation rather than copying an older Pages Router example.
Initial-visit behavior and persistence
- No saved choice: use the OS preference for system mode.
- Saved local choice: apply it as early as possible; expect a possible flash unless an initialization script or server-readable cookie sets the root selector before paint.
- Authenticated preference: store the setting with the user profile and render the server response consistently, while still allowing system mode.
- Accessibility: keep contrast compliant, preserve visible focus, and label the toggle with its current mode and action.
Troubleshooting
Hydration warning or text mismatch
Cause: theme-dependent markup rendered before the client resolved the theme. Fix: add suppressHydrationWarning to <html> when using next-themes and delay theme-dependent controls until mounted.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #4
- Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
- Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
- Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
- Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
- Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft
Dark styles never activate
Check that the root class or data attribute matches the selector your CSS or Tailwind configuration expects. Inspect document.documentElement in browser developer tools.
Theme resets on navigation or refresh
Ensure the provider wraps the entire App Router tree and persist the explicit value. A component-level provider is recreated when its segment is replaced.
Both image variants download
Remove unnecessary eager or high-priority settings. Let noncritical images lazy-load, or use a single <picture> source selection.
CSS-in-JS styles flash or disappear
Use the framework integration with a registry and useServerInsertedHTML, and verify streaming support for the exact library version.
Best Value
- 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
- Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
- Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
- HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
- What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.
Or skip the browser setup
If you need screenshots of a dark-mode page for documentation, visual QA, or previews, ScreenshotNeo captures the URL through one request. It can set a viewport, device, color scheme, cookies, custom CSS, JavaScript, delay, selector wait, and full-page mode, so you do not have to maintain Playwright or Puppeteer just for captures.
Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and billing result. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
See the ScreenshotNeo API documentation for all options. A basic request is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Equivalent Python and Node.js calls:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
Free tools Windows power users keep installed
One-click scans. No signup required.
Performance and reliability considerations
- CSS-only system mode adds no theme-state JavaScript.
- Manual persistence improves continuity but can introduce a first-paint flash unless the selector is set before hydration.
- Loading both light and dark assets eagerly increases transfer and decode work.
- There are no documented benchmark results comparing these approaches; measure your own pages with the production CSS, images, and device targets.
Frequently Asked Questions
Can a Server Component read the browser’s dark-mode preference?
Not directly. The server can use a persisted cookie or account setting; the browser’s live system preference is available through CSS or client-side matchMedia.
Should I use a class or a data attribute?
Either works. Match the selector to your CSS or Tailwind setup and apply it consistently to the root html element.
Why does my toggle flash the wrong theme?
The saved preference is being applied after the first paint. Set the root selector before rendering, or accept the flash and keep theme-dependent controls hidden until mounted.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

