← All posts

Fix Dark Reader: `color-scheme` Fails, Use `darkreader-lock`

Dark Reader double-processes sites with custom dark themes, ignoring `color-scheme`; the solution is the `darkreader-lock` meta tag.

Fix Dark Reader: `color-scheme` Fails, Use `darkreader-lock`

My site ships its own dark theme. Near-black background, warm orange accents, a canvas grain effect I spent time getting right. There is no light mode. So when a friend told me the site "looked broken," I dismissed it — until he sent a screenshot. Colors were inverted, the grain was clashing, the orange had turned into something muddy. The site was correct. His browser extension was not.

The culprit was Dark Reader, a popular extension that forces dark mode on any page it visits. In its default dynamic mode it recolors the entire page on the fly — inverting backgrounds, adjusting text. On a site that already ships a dark theme, that means double-processing an already-dark design. The result looks genuinely broken.

The wrong fix I tried first

My first instinct was color-scheme. The CSS property and its matching meta tag tell the browser "this page is designed for a dark color scheme." In Next.js 15 you set it cleanly via the viewport export:

// app/layout.tsx
export const viewport: Viewport = {
  colorScheme: 'dark',
}

That emits <meta name="color-scheme" content="dark"> and sets the CSS property on :root. It is the right thing to do — it tells the browser to use dark native UI chrome (scrollbars, form inputs, system dialogs). But it does nothing to Dark Reader. Dark Reader does not consult color-scheme. It has its own detection logic, and on its dynamic setting it will still process the page regardless.

I wasted twenty minutes confirming this. The extension kept firing. The site kept looking wrong.

The actual fix: darkreader-lock

Dark Reader ships an official opt-out mechanism: the darkreader-lock meta tag. When the extension sees this tag in the document head, it disables itself entirely on that page. One tag. No JavaScript. No CSS hacks.

In Next.js 15 the cleanest place to put it is the Metadata API's other field in app/layout.tsx:

// app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Kyriakos Kaitezidis',
  description: '...',
  // ... rest of your metadata
  other: {
    'darkreader-lock': 'darkreader-lock',
  },
}

Next.js renders other entries as <meta name="key" content="value"> tags in the document head. The extension sees <meta name="darkreader-lock" content="darkreader-lock"> and stands down. That is the entire fix.

Keep color-scheme anyway

I left the viewport.colorScheme: 'dark' export in place. It does not stop Dark Reader, but it is still correct: it signals the browser's own rendering engine that this page is dark-native, which affects native UI controls, system scrollbars, and how the browser handles unfocused input fields. It is a soft semantic hint to the platform, not a Dark Reader kill-switch. Both can coexist and both serve different purposes.

When NOT to do this

Locking out Dark Reader is the right call here because the site already provides a polished dark experience. If you drop this tag on a light-only site, you are actively preventing users who depend on dark mode from getting it. That is hostile. The darkreader-lock tag is specifically for sites that have done the work — if you have a real dark theme, you have earned the right to protect it from being processed again.

What I shipped

The fix landed in app/layout.tsx as a single field addition. No new dependencies, no runtime cost. Dark Reader users now see the site exactly as designed. The grain effect holds. The orange stays orange.

This is the kind of bug that is easy to dismiss as "the user's problem" — after all, an extension is modifying the page outside my control. But if someone with Dark Reader installed hits the site and it looks broken, that is still my problem. The fix exists and takes thirty seconds to apply, so there is no reason not to.

FAQ

What is the darkreader-lock meta tag? It is an official opt-out mechanism provided by the Dark Reader browser extension. When Dark Reader finds <meta name="darkreader-lock"> in the document head, it disables its dynamic recoloring on that page entirely.

Does color-scheme: dark stop Dark Reader from processing a page? No. The color-scheme CSS property and its corresponding meta tag are hints to the browser's own rendering engine — they affect native UI controls and system chrome, not third-party extensions. Dark Reader ignores color-scheme and applies its dynamic mode regardless.

How do I add the darkreader-lock tag in Next.js 15 App Router? Add it to the other field of your root export const metadata object in app/layout.tsx: other: { 'darkreader-lock': 'darkreader-lock' }. Next.js emits it as a standard meta tag in the document head.

Should every website use darkreader-lock? No. Only use it if your site already ships a complete dark theme. Blocking Dark Reader on a site without its own dark mode prevents users who depend on the extension from getting a usable dark experience — that is the wrong trade-off.