As of
Est. 

Dark Mode for VitePress 0.x ​

 ☀ī¸đŸŒ™Â 

When I started this site, VitePress v0.x did not yet have support for a switchable dark mode. But even in v0.x you can define your theme colors, which also allows for dark backgrounds. The VitePress default theme defines CSS variables that can be redefined to change the general color scheme. You can simply override them in your custom theme. § curl -s https://vitepress.dev/guide/custom-theme.html | pup "head > title text{}" Using a Custom Theme | VitePress

This already gives you much of the effect of a dark mode. Since not all color styling depends on these variables, you also need to override some styles, see below. I also wanted to provide a switch that allows the user to manually choose between the light and dark theme.

Dark Mode and Tailwind ​

My dark mode extension works with and without Tailwind. I use Tailwind CSS and don't want to break it or develop an incompatible approach. So it makes sense to learn what mechanisms Tailwind provides for dark mode styling and how to switch between light and dark.

To specify dark mode styling, you can simply add the dark: prefix to any Tailwind utility class, e.g. dark:bg-slate-800. Utility classes with this prefix are only active in dark mode.

How does Tailwind Know It's Dark? ​

Tailwind supports two mechanisms for detecting dark mode: the default configuration, darkMode: "media" automatically detects the browser's settings via the following media query

js
window.matchMedia('(prefers-color-scheme: dark)').matches
window.matchMedia('(prefers-color-scheme: dark)').matches

This works out of the box, but can only be switched globally with the color scheme of the browser or operating system. The alternative is darkMode: "class". Here, Tailwind's dark: prefix is activated when a CSS class name dark is set in a surrounding HTMLElement, preferably in the :root element of the page[1].

Since I want to provide a dark mode toggle on my page, I decided to use the "class" alternative. Class mode is enabled in ./tailwind.config.js as follows:

js
module.exports = {
  content: ["./docs/**/*.{html,vue,js,ts,md}"],
  darkMode: "class",
};
module.exports = {
  content: ["./docs/**/*.{html,vue,js,ts,md}"],
  darkMode: "class",
};

Styling Beyond Tailwind ​

For CSS selectors, it's easy to determine if the class dark is set on an outer HTML element. When I define <styles> independently of Tailwind, I simply add a .dark to the beginning of the selector, e.g.

css
.dark .highlight-lines .highlighted {
  background-color: rgba(100, 100, 100, 0.66);
}
.dark .highlight-lines .highlighted {
  background-color: rgba(100, 100, 100, 0.66);
}

Defining Dark Mode Colors ​

I have defined a dark_mode.css file to override the theme colors in dark mode. I import it into my customized VitePress theme at ./docs/.vitepress/theme/index.ts.

css
:root {
  --vp-c-text-dark-1: #dccea0;
  --vp-c-text-dark-2: #b79572;
  --vp-c-text-dark-3: #805447;
}
:root.dark {
  --vp-c-bg: var(--vp-c-black);
  --vp-c-bg-accent: var(--vp-c-black-light);
  --vp-c-divider: var(--vp-c-divider-dark);
  --vp-c-text: var(--vp-c-text-dark-1);
:root {
  --vp-c-text-dark-1: #dccea0;
  --vp-c-text-dark-2: #b79572;
  --vp-c-text-dark-3: #805447;
}
:root.dark {
  --vp-c-bg: var(--vp-c-black);
  --vp-c-bg-accent: var(--vp-c-black-light);
  --vp-c-divider: var(--vp-c-divider-dark);
  --vp-c-text: var(--vp-c-text-dark-1);

Some stylings come from sources outside the VitePress theme. Such sources of course do not know the theme color variables of VitePress and therefore do not use them. An example is the styling for some Markdown-it plugins.

css
.dark .custom-block.tip {
  color: var(--vp-c-text);
  background-color: #333;
}
.dark .custom-block.tip {
  color: var(--vp-c-text);
  background-color: #333;
}

Fenced Code Colors ​

VitePress v0.x uses Prism to colorize the code blocks in markup. The stylesheet included in the default VitePress theme only supports a dark background. So I downloaded the stylesheets of the dark Prism theme "Tomorrow Night" and the light prism theme "Coy" and prefixed the CSS rules accordingly. Partially, I had trouble overriding the stylesheet built into VitePress. It would be nice if the default VitePress theme provided an easier way to opt out of the built in stylesheet for Prism. đŸĻ§

VitePress 1.0 switched from Prism to Shiki. It can be configured to use different themes for light mode and dark mode.

The Toggle ​

To support switching between dark mode and light mode, I added a simple Vue component called DarkMode to the VitePress navigation bar. This is done in my local ./docs/.vitepress/theme/Layout.vue by using the search slot of the default theme's layout component.

vue
<script setup lang="ts">
import DefaultTheme from "vitepress/theme";
const { Layout } = DefaultTheme;
</script>
<template>
  <Layout>
    <template #navbar-search>...<DarkMode />...</template>
<script setup lang="ts">
import DefaultTheme from "vitepress/theme";
const { Layout } = DefaultTheme;
</script>
<template>
  <Layout>
    <template #navbar-search>...<DarkMode />...</template>

It sets the CSS class dark (or light) in the <html> element of the current page. The component has a simple state. At the beginning, it queries the browser's preferred color scheme. After that, it switches between dark and light when toggled.

Finally, I added a checkbox in the navigation bar whose appearance also depends on the dark mode classes:

css
.light #dark-mode-toggle + label::before {
  content: "☀ī¸";
}
.dark #dark-mode-toggle + label::before {
  content: "🌙";
}
.light #dark-mode-toggle + label::before {
  content: "☀ī¸";
}
.dark #dark-mode-toggle + label::before {
  content: "🌙";
}

  1. The :root CSS pseudo-class selects the <html> tag of an HTML page. ↩ī¸Ž