Nightwind
An automatic, customisable, overridable Tailwind dark mode plugin
Install / Use
/learn @jacopo-eth/NightwindREADME

A Tailwind CSS plugin that gives you an out-of-the-box, customisable, overridable dark mode.
Nightwind uses the existing Tailwind color palette and your own custom colors to automatically generate the dark mode version of the Tailwind color classes you use.
For example, whenever you use a class like bg-red-600 it gets automatically switched to bg-red-300 in dark mode.
You can see it in action on https://nightwindcss.com
Installation
npm install nightwind
Enable the Dark class variant in your tailwind.config.js file.
// tailwind.config.js - Tailwind ^2.0
module.exports = {
darkMode: "class",
// ...
plugins: [require("nightwind")],
}
In older Tailwind versions (< 2.0)
// tailwind.config.js
module.exports = {
experimental: {
darkModeVariant: true,
applyComplexClasses: true,
},
dark: "class",
// ...
plugins: [require("nightwind")],
}
Helper functions
Nightwind relies on a fixed 'nightwind' class to manage transitions, and a toggled 'dark' class applied on a top level element in the DOM, typically the root element.
You can define your own functions to manage the dark mode (or check the examples below), or use the helper functions included in 'nightwind/helper.js' to get started right away.
By default, the helper functions prevent the dreaded flicker of light mode and allow the chosen color mode to persist on update.
Initialization
To initialize nightwind, add the following script tag to the head element of your pages.
// React Example
import nightwind from "nightwind/helper"
export default function Layout() {
return (
<>
<Head>
<script dangerouslySetInnerHTML={{ __html: nightwind.init() }} />
</Head>
// ...
</>
)
}
Toggle
Similarly, you can use the toggle function to switch between dark and light mode.
// React Example
import nightwind from "nightwind/helper"
export default function Navbar() {
return (
// ...
<button onClick={() => nightwind.toggle()}></button>
// ...
)
}
Enable mode
If you need to selectively choose between light/dark mode, you can use the enable function. It accepts a boolean argument to enable/disable dark mode.
// React Example
import nightwind from "nightwind/helper"
export default function Navbar() {
return (
// ...
<button onClick={() => nightwind.enable(true)}></button>
// ...
)
}
BeforeTransition
Nightwind also exports a beforeTransition function that you can leverage in case you prefer to build your own toggle functions. It prevents unwanted transitions as a side-effect of having nightwind class in the html tag.
Check out the toggle function in the Nextjs example below for an example of how this could be implemented.
Examples
See examples of implementation (click to expand):
<details> <summary>Next.js (using the <a href="https://github.com/pacocoursey/next-themes">next-themes</a> library)</summary>_app.js
Add ThemeProvider using the following configuration
import { ThemeProvider } from "next-themes"
function MyApp({ Component, pageProps }) {
return (
<ThemeProvider
attribute="class"
storageKey="nightwind-mode"
defaultTheme="system" // default "light"
>
<Component {...pageProps} />
</ThemeProvider>
)
}
export default MyApp
Toggle
Set it up using the useTheme hook
import { useTheme } from "next-themes"
import nightwind from "nightwind/helper"
export default function Toggle(props) {
const { theme, setTheme } = useTheme()
const toggle = () => {
nightwind.beforeTransition()
if (theme !== "dark") {
setTheme("dark")
} else {
setTheme("light")
}
}
return <button onClick={toggle}>Toggle</button>
}
</details>
<details>
<summary>Create React App (using the <a href="https://github.com/nfl/react-helmet">react-helmet</a> library)</summary>
index.jsx
Add Helmet using the following configuration
import React from "react"
import ReactDOM from "react-dom"
import { Helmet } from "react-helmet"
import nightwind from "nightwind/helper"
import App from "./App"
import "./index.css"
ReactDOM.render(
<React.StrictMode>
<Helmet>
<script>{nightwind.init()}</script>
</Helmet>
<App />
</React.StrictMode>,
document.getElementById("root")
)
Toggle
Set it up using the default example
import nightwind from "nightwind/helper"
export default function Navbar() {
return (
// ...
<button onClick={() => nightwind.toggle()}></button>
// ...
)
}
</details>
<details>
<summary>Pure JavaScript or Alpine.js</summary>
The whole idea is to deconstruct helper.js, converting it from a module to a var. And unpacking the 'init' function from within helper to be its own script body to execut at DOM render. Here is the code for that at the time of writing (Oct 14th 2021). As long as the classes made it to tailwind.css (did you configure the plugins right?) then this will enable nightwind.toggle() and nightwind.enable()
<script>
var nightwind = {
beforeTransition: () => {
const doc = document.documentElement;
const onTransitionDone = () => {
doc.classList.remove('nightwind');
doc.removeEventListener('transitionend', onTransitionDone);
}
doc.addEventListener('transitionend', onTransitionDone);
if (!doc.classList.contains('nightwind')) {
doc.classList.add('nightwind');
}
},
toggle: () => {
nightwind.beforeTransition();
if (!document.documentElement.classList.contains('dark')) {
document.documentElement.classList.add('dark');
window.localStorage.setItem('nightwind-mode', 'dark');
} else {
document.documentElement.classList.remove('dark');
window.localStorage.setItem('nightwind-mode', 'light');
}
},
enable: (dark) => {
const mode = dark ? "dark" : "light";
const opposite = dark ? "light" : "dark";
nightwind.beforeTransition();
if (document.documentElement.classList.contains(opposite)) {
document.documentElement.classList.remove(opposite);
}
document.documentElement.classList.add(mode);
window.localStorage.setItem('nightwind-mode', mode);
},
}
</script>
<script>
(function() {
function getInitialColorMode() {
const persistedColorPreference = window.localStorage.getItem('nightwind-mode');
const hasPersistedPreference = typeof persistedColorPreference === 'string';
if (hasPersistedPreference) {
return persistedColorPreference;
}
const mql = window.matchMedia('(prefers-color-scheme: dark)');
const hasMediaQueryPreference = typeof mql.matches === 'boolean';
if (hasMediaQueryPreference) {
return mql.matches ? 'dark' : 'light';
}
return 'light';
}
getInitialColorMode() == 'light' ? document.documentElement.classList.remove('dark') : document.documentElement.classList.add('dark');
})()
</script>
</details>
Getting started
This is some examples of what Nightwind does by default:
- 'bg-white' in dark mode becomes 'bg-black'
- 'bg-red-50' in dark mode becomes 'bg-red-900'
- 'ring-amber-100' in dark mode becomes 'ring-amber-800'
- 'placeholder-gray-200' in dark mode becomes 'placeholder-gray-700'
- 'hover:text-indigo-300' in dark mode becomes 'hover:text-indigo-600'
- 'sm:border-lightBlue-400' in dark mode becomes 'sm:border-lightBlue-500'
- 'xl:hover:bg-purple-500' in dark mode becomes 'xl:hover:bg-purple-400'
Supported classes
Due to file size considerations, Nightwind is enabled by default only on the 'text', 'bg' and 'border' color classes, as well as their 'hover' variants.
You can also extend Nightwind to other classes and variants:
- Color classes: 'placeholder', 'ring', 'ring-offset', 'divide', 'gradient'
- Variants: all Tailwind variants are supported
Configuration
Colors
Nightwind switches between opposite color weights when switching to dark mode. So a -50 color gets switched with a -900 color, -100 with -800 and so forth.
Note: Except for the -50 and -900 weights, the sum of opposite weights is always 900. To customise how Nightwind inverts colors by default, see how to set up a custom color scale
If you add your custom colors in tailwind.config.js using number notation, Nightwind will treat them the same way as Tailwind's colors when switching into dark mode.
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
primary: {
50: "#caf0f8", // becomes primary-900 in dark mode
300: "#90e0ef", // becomes primary-600 in dark mode
600: "#0077b6", // becomes primary-300 in dark mode
900: "#03045e", // becomes primary-50 in dark mode
},
},
},
},
}
Check out color mappings to see how to further customize your dark theme.
Variants and color classes
Variants and other
