October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
App Router

Next.js Dark Mode: System Preference, Theme Toggle, and Hydration-Safe App Router Patterns

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use CSS prefers-color-scheme when your interface should follow the device automatically. Add a root .dark class or data-theme="dark" attribute when users need a manual choice, and resolve that choice before theme-dependent UI renders. In the App Router, the key is coordinating the selector, persistence, server-rendered HTML, and client hydration rather than simply inverting a few colors.

Choose the theme model first

Your requirements determine the implementation:

Requirement Recommended pattern What it implies
Only follow the operating-system setting CSS @media (prefers-color-scheme: dark) No theme state or toggle is needed.
Let a user force light or dark Root class or data attribute Store the explicit choice and update the root selector.
Offer Light, Dark, and System Three-way selector with a system fallback “System” must track changes to the OS preference.
Theme-dependent controls in server-rendered markup Resolve the theme before rendering, or hide the control until mounted Prevents hydration mismatches.

The App Router is file-system based and uses React Server Components, Suspense, and Server Functions. That means your layout can be a Server Component while a provider and toggle run on the client.

System-preference-only dark mode with CSS

When no manual override is required, keep the solution entirely in CSS. This avoids client-side state just to change colors.

:root {
  color-scheme: light;
  --background: #ffffff;
  --foreground: #171717;
  --muted: #5f6368;
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --background: #0b0d10;
    --foreground: #f5f7fa;
    --muted: #aab2bd;
  }
}

html, body {
  background: var(--background);
  color: var(--foreground);
}

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

Import this stylesheet from app/layout.tsx (for example, through app/globals.css). Use semantic variables rather than scattering literal colors through components. The color-scheme property also lets browser controls such as form fields and scrollbars use an appropriate palette.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
Philips 24 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 241V8LB
  • CRISP CLARITY: This 23.8″ Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
  • INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
  • THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors
  • WORK SEAMLESSLY: This sleek monitor is virtually bezel-free on three sides, so the screen looks even bigger for the viewer. This minimalistic design also allows for seamless multi-monitor setups that enhance your workflow and boost productivity
  • A BETTER READING EXPERIENCE: For busy office workers, EasyRead mode provides a more paper-like experience for when viewing lengthy documents

Theme-specific images without JavaScript

If an illustration differs between themes, use a picture element:

<picture>
  <source media="(prefers-color-scheme: dark)" srcSet="/hero-dark.webp" />
  <img src="/hero-light.webp" alt="Product dashboard" />
</picture>

For Next.js Image components, a CSS media-query approach can show two variants. Lazy loading normally fetches only the visible variant; making both variants eager can fetch both files. For a high-priority image, set the appropriate fetchPriority rather than eagerly loading every theme asset.

Manual light/dark mode with a root selector

A manual theme needs one authoritative selector near the document root. A class is conventional:

/* globals.css */
:root { --background: #fff; --foreground: #171717; }
.dark { --background: #0b0d10; --foreground: #f5f7fa; }

body { background: var(--background); color: var(--foreground); }

Or use an attribute, which is useful when you want selectors such as [data-theme="dark"]:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
[data-theme="light"] { --background: #fff; --foreground: #171717; }
[data-theme="dark"] { --background: #0b0d10; --foreground: #f5f7fa; }

Put the selector on <html>, not on an arbitrary page wrapper, so portals, dialogs, and the entire viewport inherit the same theme.

Rank #2
Philips 22 Inch Computer Monitor FHD 100Hz VA VESA Flicker-Free, 221V8LB
  • CRISP CLARITY: This 22 inch class (21.5″ viewable) Philips V line monitor delivers crisp Full HD 1920x1080 visuals. Enjoy movies, shows and videos with remarkable detail
  • 100HZ FAST REFRESH RATE: 100Hz brings your favorite movies and video games to life. Stream, binge, and play effortlessly
  • SMOOTH ACTION WITH ADAPTIVE-SYNC: Adaptive-Sync technology ensures fluid action sequences and rapid response time. Every frame will be rendered smoothly with crystal clarity and without stutter
  • INCREDIBLE CONTRAST: The VA panel produces brighter whites and deeper blacks. You get true-to-life images and more gradients with 16.7 million colors
  • THE PERFECT VIEW: The 178/178 degree extra wide viewing angle prevents the shifting of colors when viewed from an offset angle, so you always get consistent colors

Tailwind CSS selector mode

Tailwind’s dark utilities follow prefers-color-scheme by default. For a manual mode, configure the dark variant to respond to your root selector (for example, .dark or [data-theme=dark]), then use classes such as bg-white dark:bg-slate-950. Keep the selector convention consistent with your JavaScript; mixing a class-based configuration with an attribute-based script leaves utilities stuck in one mode.

App Router implementation with next-themes

next-themes supplies a provider, persistence, system-mode handling, and root-attribute updates. Create a client component:

// app/theme-provider.tsx
'use client'

import { ThemeProvider } from 'next-themes'
import type { ReactNode } from 'react'

export function AppThemeProvider({ children }: { children: ReactNode }) {
  return (
    <ThemeProvider
      attribute="class"
      defaultTheme="system"
      enableSystem
      disableTransitionOnChange
    >
      {children}
    </ThemeProvider>
  )
}

Wrap your application below the root document tags:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// app/layout.tsx
import './globals.css'
import { AppThemeProvider } from './theme-provider'

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <AppThemeProvider>{children}</AppThemeProvider>
      </body>
    </html>
  )
}

The provider modifies the html element, so suppressHydrationWarning belongs there. It suppresses the expected attribute difference; it does not repair arbitrary mismatches elsewhere.

Hydration-safe toggle

The current theme is unavailable during the server render. Do not render a theme-dependent button label until the component has mounted:

Rank #3
Sale
Dell 24 Monitor - SE2426H - 23.8-inch FHD (1920x1080) 144Hz 1ms Display, in-Plane Switching (IPS) Technology, AMD FreeSync™, TÜV 3-Star 2X HDMI, Tilt
  • Clear visuals. Fluid motion: A 144Hz refresh rate and 1ms MPRT deliver smooth, tear‑free motion across work, gaming, and streaming for clearer, more fluid viewing.
  • Eye comfort: TÜV Rheinland 3‑star* certification reduces harmful blue light while preserving stunning color quality without compromise. *TÜV Rheinland 3-star eye comfort certification.
  • Wide viewing angle: Get consistent views across a wide 178° /178° viewing angle.
  • In-Plane Switching (IPS): See excellent color accuracy and consistency across wide viewing angles with In-plane Switching (IPS) technology.
  • Ultra-thin bezels: Maximize your viewing experience with thin bezels.
// app/theme-toggle.tsx
'use client'

import { useEffect, useState } from 'react'
import { useTheme } from 'next-themes'

export function ThemeToggle() {
  const [mounted, setMounted] = useState(false)
  const { theme, setTheme, resolvedTheme } = useTheme()

  useEffect(() => setMounted(true), [])

  if (!mounted) {
    return <button type="button" aria-label="Choose theme" disabled>Theme</button>
  }

  const effective = theme === 'system' ? resolvedTheme : theme
  return (
    <button
      type="button"
      onClick={() => setTheme(effective === 'dark' ? 'light' : 'dark')}
      aria-label={`Switch to ${effective === 'dark' ? 'light' : 'dark'} mode`}
    >
      {effective === 'dark' ? 'Use light mode' : 'Use dark mode'}
    </button>
  )
}

For a three-way control, expose three values and call setTheme('light'), setTheme('dark'), or setTheme('system'). “System” should remain a real stored choice, not a one-time conversion to the current color, so later operating-system changes are honored.

Handling the first visit and persistence

On an initial visit with no saved choice, a system-based setup should read prefers-color-scheme. A provider can do this in the browser and persist the user’s explicit selection (commonly in local storage). The server cannot read that browser preference unless you introduce a separate cookie or other request signal, so avoid rendering different text or icons on the server based on an unknown theme.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Use neutral, theme-independent markup for the initial server render.
  • Apply the root selector as early as possible to reduce a flash of the wrong colors.
  • Delay labels, icons, and menus that depend on the resolved theme until mount.
  • Test a fresh profile, a previously saved choice, and an operating-system switch while “System” is selected.

Theme-specific images with Next.js

Use CSS media queries or picture when the asset should follow the system. If the image follows a manual class or attribute, render both variants and hide one with CSS, or choose the asset in a mounted client component. Consider bandwidth: two eager Image components can download both files, while lazy loading ordinarily limits the request to the visible variant. Set fetchPriority for the single above-the-fold asset that genuinely needs priority.

CSS-in-JS in the App Router

CSS-in-JS can work with dark mode, but the library must support Server Components and streaming. Follow its App Router integration pattern: create a style registry, collect styles with useServerInsertedHTML, and place the registry in a Client Component wrapper. Ensure the generated theme selector is present consistently in server and client output; otherwise streaming can expose unstyled content or hydration warnings. If your library lacks documented support for these React features, plain CSS variables or a supported utility framework is the safer path.

Common failures and fixes

Flash of light theme

Cause: the dark selector is added only after hydration. Fix: use a provider that applies the attribute early, keep the initial markup neutral, and avoid client code that waits several seconds before setting the theme.

Rank #4
Samsung 27" Essential S3 (S36GD) Series FHD 1800R Curved Computer Monitor
  • CURVED FOR ENHANCED ENGAGEMENT: An immersive viewing experience with a curved monitor that wraps more closely around your field of vision; It creates a wider view, enhancing depth perception and minimizing peripheral distraction
  • SMOOTH PERFORMANCE FOR SEAMLESS CONTENT: Stay in the action when playing games, watching videos, or working on creative projects; The 100Hz refresh rate reduces lag and motion blur so you don't miss a thing in fast-paced moments¹
  • MORE GAMING POWER: Gain the edge with optimizable game settings; Color and image contrast can be adjusted to see scenes more vividly and spot enemies hiding in the dark; Game Mode adjusts any game to fill the screen so you can view every detail²
  • KEEP IT EASY ON THE EYES: Care for your eyes and stay comfortable, even during long sessions; Advanced eye comfort technology certified by TÜV reduces eye strain by minimizing blue light and reducing irritating screen flicker²
  • INCREASED VERSATILITY: Connect to more; Plug devices straight into your monitor for increased flexibility, making your computing environment even more convenient

Hydration mismatch warning

Cause: a server render reads theme, resolvedTheme, or browser storage before those values exist. Fix: gate the dependent UI behind a mounted flag and put suppressHydrationWarning on the html element when a provider changes it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Tailwind dark: classes do nothing

Cause: Tailwind is still configured for system media queries while your script sets a class or attribute. Fix: switch the dark variant to the exact selector your script writes and verify that it is on html.

System mode stops following OS changes

Cause: “system” was converted to a fixed light/dark value. Fix: retain system as the selected mode and let the provider or a matchMedia listener resolve it whenever the preference changes.

Both image variants download

Cause: both images were marked eager or high priority. Fix: allow lazy loading for noncritical variants and assign priority only to the asset that is visible and above the fold.

Styles disappear during streaming

Cause: a CSS-in-JS registry is not inserted with the App Router’s streaming lifecycle. Fix: use the library’s documented style registry and useServerInsertedHTML integration, and confirm the library supports current React Server Component behavior.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
Sale
Sceptre New 22-Inch Gaming Monitor, FHD 1080p, Up to 144Hz, HDMI, DisplayPort, Built-in Speakers, Machine Black (E225W-FW144 Series, 2026)
  • 【INTEGRATED SPEAKERS】Whether you're at work or in the midst of an intense gaming session, our built-in speakers provide rich and seamless audio, all while keeping your desk clutter-free.
  • 【EASY ON THE EYES】 Protect your eyes and enhance your comfort with Blue-Light Shift technology. This feature reduces harmful blue light emissions from your screen, helping to alleviate eye strain during long hours of use and promoting healthier viewing habits.
  • 【WIDEN YOUR PERSPECTIVE】Our sleek minimal bezel design ensures undivided attention. The nearly bezel-free display seamlessly connects in a dual monitor arrangement, delivering an unobstructed view that lets you focus on more at once, completely distraction-free.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Testing checklist

  • Fresh browser profile with OS light and dark preferences.
  • Saved light, dark, and system choices across a full reload and a second route.
  • OS preference changed while the app remains open in system mode.
  • JavaScript disabled or delayed, checking that base colors remain usable.
  • Keyboard focus, form controls, dialogs, charts, code blocks, and images in both palettes.
  • Slow network, streaming navigation, and production builds rather than only development mode.

Or skip the browser setup

If your goal is a clean screenshot of a themed page rather than implementing the theme, ScreenshotNeo returns PNG, JPEG, WebP, or PDF from one request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server gives Claude, Cursor, and other MCP clients take_screenshot, get_page_info, and capture_pdf tools.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

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)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the complete parameter reference in the ScreenshotNeo documentation. Options include full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets or custom viewports, retina scale, PDF paper settings and page ranges, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage data, and an OpenAPI specification. Existing parameter names from other screenshot APIs are accepted to ease migration.

Plans include 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000. Every feature is on every plan, and yearly billing provides two months free. Create a free ScreenshotNeo account to start.

Frequently Asked Questions

Can a Server Component read the user’s dark-mode preference?

Not directly from the browser’s media-query preference. Use neutral server markup and resolve the preference in the client, or pass an explicitly stored cookie when your architecture requires server-side variation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I use a class or a data attribute?

Either works. Choose the selector your CSS or Tailwind configuration supports, apply it to the root html element, and use the same convention everywhere.

Is a dark-mode toggle required for accessibility?

No single toggle is mandatory, but maintain readable contrast, visible focus indicators, and usable controls in both palettes. Respecting the system preference is a good default.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.