How to Apply Dark Mode for Code Blocks in Next.js with Highlight.js and Tailwind CSS

We can use highlight.js to highlight code blocks in Next.js. However, it doesn't support dark mode by default, i.e. the code blocks will not change style when we switch to dark mode. My friend Songkeys provided a very elegant solution for this. In this article, I will show you how to apply dark mode for code blocks in Next.js with Tailwind CSS.

Install highlight.js and import code style

First, we need to install highlight.js.

1npm install highlight.js

After you install it, you can import any code style from highlight.js/styles/ to your project. For example, if you want to use the github-dark style

You can import it in some tsx file:

1import "highlight.js/styles/github-dark.css";

You can also import it in global.css, if you turn on postcss-import in your postcss.config.js:

postcss.config.js
1module.exports = {
2 plugins: {
3 "postcss-import": {},
4 tailwindcss: {},
5 autoprefixer: {},
6 },
7};
global.css
1@import "highlight.js/styles/github-dark.css";

What is the problem for dark mode?

If you use github-dark style, you will find that the code blocks will not change style when you switch to differnt modes (light or dark). Because you only import the github-dark style.

Let's say what you want is to import github-dark.css for dark mode and github.css for light mode. How can you do that?

I used next-theme with attribute="class" to switch between light and dark mode, which basically add a dark or light class to the html tag when you switch to dark or light mode.

Therefore, to dynamically import css files for code blocks, you may considering about code like this:

global.css
1@import "highlight.js/styles/github.css";
2.dark {
3 @import "highlight.js/styles/github-dark.css";
4}

However, this is not valid CSS. You can't use @import inside a selector. From the MDN:

The @import CSS at-rule is used to import style rules from other validstylesheets. An @import rule must be defined at the top of the stylesheet,before any other at-rule (except @charset and @layer) and styledeclarations, or it will be ignored.

Solution

For websites only support auto mode

If your website only support auto mode, which means the mode depends on the system preference, and visitors cannot switch between light and dark mode manually, you can use the following easy solution. (Notice to turn on postcss-import in your postcss.config.js)

global.css
1@import url("highlight.js/styles/github-dark.css") screen and
2 (prefers-color-scheme: dark);
3@import url("highlight.js/styles/github.css") screen and
4 (prefers-color-scheme: light);

This solution is not valid for website support light, dark and auto mode. For example, visitors want dark mode and manually switch to it when their systems are in light mode, i.e. prefers-color-scheme: light. Then the code block style will still be in light mode.

For websites support light, dark and auto mode

We can use SASS/SCSS to solve this problem. First, we need to install sass in Next.js.

1npm install sass

Then, we change the global.css to global.scss and use the following code:

global.scss
1@use "sass:meta";
2
3@import "highlight.js/styles/github.css";
4.dark {
5 @include meta.load-css("highlight.js/styles/github-dark");
6}

This code import github.css by default. When you switch to dark mode, it will import github-dark.css and override the style of github.css. This is valid because SASS support @include inside a selector to import a css file.

Now, the code blocks will change style when you switch to dark mode. My website support light, dark and auto mode, it works very well.