Skip to main content
ASoc
Tutorial

Tailwind Disable Dark Mode in v4: Light-Only vs Manual Theme

Learn how to disable Tailwind v4 dark mode without breaking UI: remove dark styles for light-only sites or use a class-based manual theme.

The ASoc Team10 min read

To disable dark mode in Tailwind CSS v4, first decide whether you want a permanently light-only site or simply want to stop following the operating system. For a light-only site, remove dark: utilities, authored dark selectors, and theme-toggle logic. To retain an optional manual dark theme, use a class-based @custom-variant instead of the default system-preference variant.

Three requirements that need different solutions

People searching for “disable dark mode” often mean one of three things. A product designer may want a single brand-approved light theme. A developer may want the page to start light even when the user's operating system is dark. A third person may want the dark-mode switch to work only when clicked. Those requirements should not share one workaround.

GoalTailwind v4 approachWhat to test
Permanently light-onlyRemove dark utilities and dark selectorsOS dark still shows the light design
Light by default, manual dark availableDefine a class-based dark variantRoot class, saved choice, and first paint
Follow the operating systemUse the default dark: behaviorBoth system color schemes
Remove an old toggleRemove UI state and the associated CSSNo stale preference reactivates a theme

Tailwind's official dark-mode documentation describes system-based and selector-based variants. It does not replace the product decision about whether dark styling should exist at all.

This guide focuses on the light-only and manual-control decisions. For a broader framework migration, use Tailwind 3 to 4. For a complete explanation of early theme restoration, use Next.js dark mode without the flash.

Why removing darkMode from a config is not enough

Tailwind 3 projects often configured darkMode in a JavaScript file. Tailwind 4 is CSS-first. By default, its dark: utilities respond to the browser's prefers-color-scheme media feature. A class on the root element does not change that media feature.

This matters because removing a class-based override from a v4 stylesheet can make dark mode more automatic, not less. The default system-based variant becomes active again. A site that should always be light must remove the unwanted dark declarations, not just the configuration line.

Also separate the CSS color-scheme property from Tailwind's dark: variant. color-scheme: light tells the browser how to draw native controls such as form elements and scrollbars. It does not cancel a rule that changes an element's background color. Both need attention.

A real Tailwind v4 codebase to audit

ASoc currently supports a manually selected dark theme. The actual stylesheet begins with these lines:

/* src/app/globals.css — current ASoc source */
@import "tailwindcss";

/* Class-based dark mode (toggled by adding dark to html) */
@custom-variant dark (&:where(.dark, .dark *));

The same file has authored base styles:

/* src/app/globals.css — current ASoc source */
@layer base {
  body {
    @apply bg-white font-sans text-base font-normal text-gray-700;
  }
  .dark body {
    @apply bg-gray-900 text-gray-300;
  }
}

That is an important first-party finding: ASoc has both generated dark: utilities and a handwritten .dark body selector. If you removed the variant but left the base selector, the body could still become dark when a class is present. Searching only for dark: would miss the authored rule.

The root layout, src/app/layout.tsx, also restores a saved dark preference before the first paint. It is deliberately separate from the CSS: one part determines which variant matches, while another determines which state the document starts in. The two must be changed together when a product removes dark mode.

The examples below describe possible changes for a different product requirement. They do not claim ASoc has removed its existing dark theme.

Option 1: make the application light-only

The cleanest light-only implementation removes the unused feature rather than leaving a dormant dark theme in every component.

Audit every source of dark styling. Search the application for dark: utilities, CSS selectors such as .dark, data-theme attributes, and prefers-color-scheme media rules. Include third-party component styles where possible. A dark background may come from authored CSS rather than Tailwind.

Choose explicit light colors. A component with bg-white dark:bg-gray-900 can keep bg-white. A component with text-gray-700 dark:text-gray-300 can keep its light text color. Check borders, placeholders, icons, and focus rings as well. Removing half of a pair is safe only when the remaining color still has adequate contrast.

Remove theme state. A persisted dark preference must no longer be read during page initialization. Remove the obsolete toggle and its associated preference-writing logic. If the application supports server-side theme cookies, review that path too. A light-only UI with an active hidden dark-mode state is difficult to debug and surprising to users.

Set the browser's native color scheme. The following is a minimal light-only illustration, not the current ASoc stylesheet:

@import "tailwindcss";

:root {
  color-scheme: light;
}

@layer base {
  body {
    @apply bg-white text-gray-900;
  }
}

This gives native controls a light preference and establishes a light body. It is not a magic switch that neutralizes other dark CSS. If a vendor stylesheet still uses a media query, that rule must be reviewed separately.

Test with the OS in dark mode. This is the test many teams skip. Open the site with a dark operating-system theme and no saved preference. Check the header, cards, buttons, forms, dialogs, and native controls. Repeat in light OS mode. The two renders should be intentionally identical for a light-only design.

Option 2: ignore the OS but allow a manual dark theme

Sometimes “disable dark mode” means “do not switch automatically.” The product still offers a theme switcher; it simply defaults to light and only changes when the user chooses dark.

Use the documented class-based variant:

@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

The root document gets the dark class only when the chosen theme is dark. The exact mechanism may be local storage, a cookie, or a server-side preference. The key rule is that an operating-system preference must not silently add the class when your product promises light as the default.

The :where(.dark, .dark *) selector matches the element carrying dark and its descendants without adding specificity from the selector itself. That detail matters when a component puts the variant on the root element rather than a child. It is the same selector ASoc uses today.

A useful state model is:

Stored choiceRoot classExpected visual state
No choiceNo dark classLight
LightNo dark classLight
Darkdark class presentDark

If you offer a third “system” choice, that is a separate feature: the application must subscribe to OS preference changes and update the root class accordingly. Do not accidentally implement that behavior in a product that promises a fixed light default.

Keep the initial render consistent

A manual theme switch can produce a flash if the document initially renders light and later restores a dark choice. ASoc's src/app/layout.tsx includes an early head script specifically to restore a stored preference. A React effect runs too late for the first paint.

For a light-only product, remove that restoration logic. For a manual light/dark product, keep it consistent with the selected class-based variant. Do not mix a class-based CSS variant with a script that updates only a data-theme attribute; the selectors would disagree.

Option 3: keep the system preference on purpose

There is also a legitimate case for leaving Tailwind's default behavior alone. A documentation site with no theme switcher may want to follow the OS automatically. That requires no custom variant:

@import "tailwindcss";

A component can then use both light and dark utilities:

// Illustrative component, not copied from ASoc
export function Notice() {
  return (
    <aside className="rounded-lg bg-white p-4 text-gray-900 dark:bg-gray-900 dark:text-gray-100">
      Changes saved.
    </aside>
  );
}

If your product already has a manual toggle, do not switch back to this default without updating the toggle and stored-preference behavior. Otherwise the UI may show “Light” while the OS media query still applies dark colors.

Audit the component, not just the stylesheet

ASoc's src/components/organisms/Header.tsx is a good example of why a light-only conversion needs a component-level review. Its root header has bg-white shadow-sm dark:bg-gray-900. Navigation links also have dark text variants. The mobile menu uses overlays, transitions, and a drawer.

If you remove the dark background but leave a white dark-mode text variant active, links can become unreadable. If you change the theme while the drawer is open, overlay contrast may also change. The safe unit of work is a complete component across its visible states.

The same applies to forms. Check input backgrounds, placeholders, disabled controls, validation messages, and keyboard focus. A theme that looks correct on a screenshot can still be unusable with a keyboard or screen reader.

A simple source search is:

rg -n 'dark:|prefers-color-scheme|data-theme|color-scheme' src

Review each result rather than deleting blindly. For a light-only site, classify it as removable dark styling, a vendor rule requiring a separate decision, or an intentional unrelated use of a dark color.

Verification matrix

ScenarioLight-only productManual light/dark product
OS light, no saved choiceLightLight
OS dark, no saved choiceLightLight
Old saved dark choiceIgnoredDark
Refresh before hydrationLightSaved theme without a flash
Mobile navigationReadableReadable in both themes
Keyboard focusVisibleVisible in both themes
Native form controlsLightMatch selected theme
High-contrast settingsUsableUsable in both themes

Run npm test, npm run lint, npm run format:check, and npm run build after changing theme infrastructure. Those are the real scripts in ASoc's package.json. They can detect broken imports or processing failures; they cannot prove that all screens are readable. Manual or automated browser verification is still necessary.

Troubleshooting

SymptomLikely causeCorrective action
Page becomes dark on a dark OSDefault media variant still appliesRemove dark styling or use manual selector
Removing @custom-variant made dark mode automaticTailwind reverted to its defaultRestore the intended class strategy
Body is dark but cards are lightAuthored .dark body rule remainsAudit regular CSS selectors
Theme toggle does nothingCSS expects media while UI changes a classAlign the variant and state
Inputs are dark on a light pageNative color-scheme is still darkSet native scheme intentionally
Dark preference returns after refreshStartup preference logic still runsRemove it for light-only mode
Text is invisible on a cardOnly one of the paired colors was removedAudit the complete component

Frequently asked questions

Can I use darkMode: false in Tailwind v4? Do not treat a v3 JavaScript setting as the light-only strategy for a CSS-first v4 project. Remove unused dark styles and verify the result.

Does color-scheme: light disable dark: utilities? No. It influences native browser UI; generated Tailwind CSS rules still apply when their variant matches.

How do I keep a dark-mode toggle without following the OS? Define a class-based dark variant and add the root class only for the user's explicit dark choice.

Should I remove every dark class automatically? Only after the product decides to be light-only. Review component contrast, native controls, and authored selectors together.

Sources and templates

This walkthrough uses first-party evidence from src/app/globals.css, src/app/layout.tsx, and src/components/organisms/Header.tsx. Tailwind's dark mode guide and upgrade guide are the authoritative references.

For code-owned landing-page examples where you control the theme implementation, explore ASoc Nexus, ASoc Nimbus, and ASoc Nova. Browse Tailwind landing-page templates or Next.js landing-page templates for the complete collection.

Keep reading

Tutorial7 min read

Tailwind fit-content: When w-fit Is the Right Width

Use Tailwind w-fit for intrinsic controls, not page layout. Learn when it works, how it differs from max-w and w-full, and the flexbox traps to avoid.

Read more