Open-source project
nuxt-modules/color-mode avatar
nuxt-modules/color-mode

color-mode: dark and light themes for a Nuxt application

Dark and Light mode with auto detection made easy with Nuxt 🌗

1,228 stars124 forksTypeScriptMIT

At a glance

What is it?
The @nuxtjs/color-mode module adds a light or dark class to the html element, detects the system preference automatically, and works with both client-side and universal rendering. It suits a Nuxt team that wants theming in one place instead of a hand-rolled cookie and a hydration guard, and its 4.x line targets Nuxt 3 and later.
Who is it for?
Adopt @nuxtjs/color-mode if you run Nuxt and want a single class on the html element to drive your CSS, with system preference detection and a per-page override. Skip it if you are on Nuxt 2, because version 4 of the module is compatible with Nuxt 3 and later and the previous version has its own documentation.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the module does

The module is described as dark and light mode with auto detection made easy with Nuxt, and the feature list is four items. It adds a `.${color}` class to `<html>` for easy CSS theming, it can force a page to a specific color mode which the documentation calls perfect for incremental development, it works with client-side and universal rendering, and it auto-detects the system color-mode. The class-on-html approach is the design decision and it is a good one, because it means the theme is a CSS concern rather than a JavaScript one. Your stylesheet keys off `html.dark` and the module handles the awkward part, which is that the user's preference has to be known before the first paint or the page flashes the wrong theme, and the server needs to know it too or the rendered HTML and the client state disagree. The primary language is TypeScript, the licence is MIT, the last push was on 2026-10-19, and the releases are v4.0.1 on 2026-06-09, v4.0.0 on 2025-11-11 and v3.5.2 on 2024-10-24.

Why the class approach beats a runtime toggle

The alternative to this module is a composable that holds a boolean and a function to flip it, with a cookie or localStorage read on mount to restore the user's choice. That approach has three problems the module removes. The first is the flash, because the theme is only known in the browser after hydration, so the page paints in the default theme first. The second is hydration mismatch, because the server rendered one theme and the client thinks it should be another, which Nuxt will complain about. The third is that every component has to read the state rather than the DOM, so the theme lives in JavaScript and the CSS is a consequence of it. With a class on the html element, the CSS is the source of truth, the server can emit the class directly, and the user preference is read once by the module and applied as an attribute. The class also gives you free specificity, since `html.dark` beats a bare selector without any extra CSS gymnastics.

Forcing a mode on one page

The second feature is the one that earns the module its place in a real application. A page can be forced to a specific color mode, and the documentation describes this as perfect for incremental development. The practical case is a page that is not finished, a marketing page designed only for light, or a dark-only media viewer, and the alternative is adding a wrapper class on that page and then losing to specificity conflicts with the global theme. With the module, the page declares its mode and the class on the html element reflects it, so the whole page including any portalled components and modals inherits it rather than fighting it. That is the difference between a mode that applies to a route and a mode that applies to a subtree, and the second is the one that produces visual bugs in a modal or a tooltip that renders at the root of the application.

The universal rendering problem, and what the module does about it

Working with client-side and universal rendering is listed as a feature, and it is the hardest part of the problem. Under universal rendering, the server produces HTML before any JavaScript runs, so it does not know the user's preference unless the preference travels with the request. The module reads the stored preference and can have the server apply the right class, so the HTML that arrives already has `dark` or `light` on the html element and the browser does not repaint it. Under client-side rendering the same module code reads the preference on start. The consequence for an application is that the theme is correct in both modes, which removes the class of bug where a site looks right when navigated to and wrong when loaded fresh. The system detection feature completes the picture, since a visitor who has never expressed a preference gets the one their operating system asks for, and the module treats the system value as the default rather than as an explicit choice.

The v4 line, and what changed in the major bump

The version history matters for anyone upgrading, and the README is explicit about it. Version 4 of `@nuxtjs/color-mode` is compatible with Nuxt 3 and later. If you are looking for the previous version of the module, the documentation points at the previous docs site, and there is a separate section describing the differences when migrating to v3. The 4.0.0 release itself landed on 2025-11-11 and the 4.0.1 patch on 2026-06-09, and the runtime dependencies reflect the newer generation, with `@nuxt/kit` at `^4.6.1`. The supporting libraries are also worth noting for anyone extending the module, with `defu` for merging default configuration, `exsolve` for resolving the Nuxt version at runtime, `pathe` for cross-platform paths, `pkg-types` for reading package metadata and `verkit` for version handling. The `type` field in the manifest is `module`, and the package exports a single entry point with a types file and an ESM implementation.

The repository, and the two playgrounds

The tree shows a Nuxt module developed with the current tooling. There is a `playground/` directory and a separate `playground-v5/`, and the dev scripts confirm what they are for, with `dev` running the Nuxt dev server on the playground and `dev:v5` running the one in playground-v5. A second playground is a strong hint that the module is tested against two Nuxt generations at once, which is common where a module supports a range of framework versions. The rest is conventional, with `src/`, `test/`, `docs/`, `build.config.ts` for the module build, `vitest.config.ts` for the tests, `eslint.config.js`, `commitlint.config.cjs` with a `.husky/` directory, `renovate.json` for dependency updates, a `pnpm-workspace.yaml` and a `pnpm-lock.yaml`. The build itself uses `nuxt-module-build` and then minifies the client script with esbuild, and the test script runs vitest with coverage plus a `vue-tsc --noEmit` type check, so types are verified as part of the test run.

Contributing, and the toolchain the module expects

The contributing section is short and offers two routes. You can contribute to the module online with CodeSandbox, which the README links, or you can work locally with three steps: clone the repository, install dependencies using `pnpm install`, and start the development server using `pnpm dev`. The toolchain expectations are recorded in the manifest's `devEngines` block rather than in the usual `engines`, and they are strict. The runtime is node at `^24.21.0` and the package manager is pnpm at `12.11.2`, both with `onFail: download`, so a contributor without those installed has them fetched rather than being told to install them. The release script runs lint, then the tests, then `bumpp`, and the prepack script is what actually builds the module and minifies the client script. The documentation site has its own scripts, with `docs` running the Nuxt dev server in the docs directory and `docs:build` generating it statically.

Editorial conclusion

Adopt @nuxtjs/color-mode if you run Nuxt and want a single class on the html element to drive your CSS, with system preference detection and a per-page override. Skip it if you are on Nuxt 2, because version 4 of the module is compatible with Nuxt 3 and later and the previous version has its own documentation. Before you upgrade, note that v4.0.0 changed the module and there is a documented migration path, that the 4.x line requires a recent Nuxt kit, and that the package uses pnpm with a strict node and pnpm version expectation for contributors.

Frequently asked questions

How does the Nuxt color-mode module apply the theme?

It adds a .${color} class to the html element, so your CSS can key off html.dark or html.light and the theme is a stylesheet concern rather than JavaScript state.

Which Nuxt versions does @nuxtjs/color-mode v4 support?

Version 4 is compatible with Nuxt 3 and later. The previous version of the module has its own documentation, and the docs also describe the differences when migrating to v3.

Can I force a single page to one color mode?

Yes. The module can force a page to a specific color mode, which the documentation describes as perfect for incremental development, and the whole page including portalled components inherits it.

How do I develop the color-mode module locally?

Clone the repository, install dependencies with `pnpm install`, and start the development server with `pnpm dev`, which runs the Nuxt dev server on the playground. There is also a separate playground-v5 for the newer Nuxt generation.

Official sources

  1. License: MIT
  2. nuxt-modules/color-mode on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nuxt-modules-color-mode.svg)](https://hysenlabs.com/projects/nuxt-modules-color-mode)

Was this useful?