Dark mode with Shiki and Code Hike
Sebastian Sdorra
Posted on January 29, 2023
Warning: This article refers to versions of Code Hike before 0.8.0. Since version 0.8.0, Code Hike uses lighter instead of Shiki. The approach presented in this article uses shiki's color replacement method, which no longer works with lighter.
I'm using Code Hike a lot, including for the code snippets on my blog, and I love this library.
It has many nice features, like tabs or focus.
But it is lacking support for switching themes at runtime, which I want to use for a dark mode implementation.
https://github.com/code-hike/codehike/issues/271
That is really a pity.
But why actually?
Code Hike uses Shiki and Shiki supports changing themes via CSS variables.
We should try that out?
Shiki
In order to switch Shiki colors at runtime,
we have to load a special theme which uses CSS variables instead of hard coded colors.
This should look like this:
{
options: {
remarkPlugins: [
[remarkCodeHike, { theme: "css-variables" }]
],
},
}
After that we can define our colors for the theme with css variables:
:root {
--shiki-color-text: #24292f;
--shiki-color-background: #ffffff;
--shiki-token-constant: #0550ae;
--shiki-token-string: #24292f;
--shiki-token-comment: #6e7781;
--shiki-token-keyword: #cf222e;
--shiki-token-parameter: #24292f;
--shiki-token-function: #8250df;
--shiki-token-string-expression: #0a3069;
--shiki-token-punctuation: #24292f;
--shiki-token-link: #000012;
}
And we can override them for the dark mode:
@media (prefers-color-scheme: dark) {
:root {
--shiki-color-text: #c9d1d9;
--shiki-color-background: #0d1117;
--shiki-token-constant: #79c0ff;
--shiki-token-string: #a5d6ff;
--shiki-token-comment: #8b949e;
--shiki-token-keyword: #ff7b72;
--shiki-token-parameter: #c9d1d9;
--shiki-token-function: #d2a8ff;
--shiki-token-string-expression: #a5d6ff;
--shiki-token-punctuation: #c9d1d9;
--shiki-token-link: #000012;
}
}
Tailwind CSS
If you use Tailwind CSS and use the dark mode with the class strategy instead of media query,
you can override the variables like this:
html.dark {
--shiki-color-text: #c9d1d9;
--shiki-color-background: #0d1117;
--shiki-token-constant: #79c0ff;
--shiki-token-string: #a5d6ff;
--shiki-token-comment: #8b949e;
--shiki-token-keyword: #ff7b72;
--shiki-token-parameter: #c9d1d9;
--shiki-token-function: #d2a8ff;
--shiki-token-string-expression: #a5d6ff;
--shiki-token-punctuation: #c9d1d9;
--shiki-token-link: #000012;
}
Bonus:
You can use colors from Tailwind CSS by using the theme function:
:root {
--shiki-color-text: theme("colors.zinc.700");
--shiki-color-background: theme("colors.white");
--shiki-token-constant: theme("colors.emerald.700");
--shiki-token-string: theme("colors.emerald.700");
--shiki-token-comment: theme("colors.zinc.500");
--shiki-token-keyword: theme("colors.cyan.700");
--shiki-token-parameter: theme("colors.pink.700");
--shiki-token-function: theme("colors.violet.700");
--shiki-token-string-expression: theme("colors.emerald.700");
--shiki-token-punctuation: theme("colors.zinc.800");
--shiki-token-link: theme("colors.zinc.700");
}
Code Hike
Ok, now we have everything together let's test it.
Most of the colors are now loaded from our variables, but not all.
The background and the default text color are always very dark.
Why is that?
After some time of searching in the source code of Code Hike, I found the answer.
It reads some color information from the theme itself (theme.ts).
But shouldn't it then get variables instead of hard coded blackish color codes?
After a look into the Shiki theme,
I have to realize that there are no variables defined.
After another research phase, I found out that Shiki does some weird color code to variable replacement.
https://github.com/shikijs/shiki/pull/314
Shiki does this because the underlying library vscode-textmate or vscode-oniguruma returns black if the value is not a valid color.
This explains why Code Hike uses blackish color codes instead of variables.
But what if we write a variable directly into the theme instead of the color code.
Custom theme
At first we copy the css-variables theme from Shiki and
modify the values of editor.foreground
and editor.background
from the blackish color codes to variables.
{
"name": "css-variables",
"type": "css",
"colors": {
"editor.foreground": "var(--shiki-color-text)",
"editor.background": "var(--shiki-color-background)",
},
...
}
Now we can load our custom theme.
import { remarkCodeHike } from "@code-hike/mdx";
import theme from "./lib/ch-theme.json" assert { type: "json" };
const options = {
remarkPlugins: [[remarkCodeHike, { theme }]],
};
// configure mdx ...
Great, that seems to work.
But what about tabs, if we use CH.Code
?
After some research in the source code,
we add some more variables to our theme and to our css:
"colors": {
"editor.foreground": "var(--shiki-color-text)",
"editor.background": "var(--shiki-color-background)",
"editorGroupHeader.tabsBackground": "var(--ch-tabs-bg)",
"tab.activeBackground": "var(--ch-tab-active-bg)",
"tab.inactiveForeground": "var(--ch-tab-inactive-color)",
"tab.inactiveBackground": "var(--ch-tab-inactive-bg)",
"icon.foreground": "var(--ch-icon-text)",
"tab.border": "var(--ch-tab-border)",
"tab.activeBorder": "var(--ch-tab-active-border)",
},
:root {
--ch-icon-text: #3f3f46;
--ch-tabs-bg: #e4e4e7;
--ch-tab-border: #d4d4d8;
--ch-tab-active-border: #ffffff;
--ch-tab-active-bg: #ffffff;
--ch-tab-active-color: #3f3f46;
--ch-tab-inactive-color: #71717a;
--ch-tab-inactive-bg: #e4e4e7;
}
But the color definition for tab.activeForeground
caused problems.
Code Hike tries to convert the color from hex to rgba in order to add opacity to the color (transparent).
But our color definition (var(--ch-tab-active-color
) is not a hex color code and the function throws an error.
With a bit of css we are able to workaround the problem:
div.ch-editor-tab-active {
color: var(--ch-tab-active-color);
}
That's it, we can now use CSS variables for the style of our syntax highlighting
and we can use different themes for light and dark mode.
Warning: I've tested not all feature of Code Hike. It is very likely that there will still be problems at one point or another.
Existing Themes
That is all nice, but how can we use existing VS Code themes with this approach (Shiki and Code Hike are using VS Code themes).
We have to read the theme and copy the color codes from the theme to our css variables.
But this is a very tedious work, so I have written a small tool that can take this task from us.
$ npx convert-sh-theme https://raw.githubusercontent.com/shikijs/shiki/main/packages/shiki/themes/dracula.json
style.css
=======================================
:root {
--shiki-color-text: #F8F8F2;
--shiki-color-background: #282A36;
--shiki-token-constant: #BD93F9;
--shiki-token-string: #8BE9FD;
--shiki-token-comment: #6272A4;
--shiki-token-keyword: #FF79C6;
--shiki-token-parameter: #F8F8F2;
--shiki-token-function: #8BE9FD;
--shiki-token-string-expression: #FF79C6;
--shiki-token-punctuation: #F8F8F2;
--shiki-token-link: #8BE9FD;
}
To create styles and the theme for Code Hike:
$ pnpx convert-sh-theme --code-hike --out output https://raw.githubusercontent.com/shikijs/shiki/main/packages/shiki/themes/dracula.json
... write file style.css
... write file theme.json
For more information about the converter tool, have a look at convert-sh-theme.
Posted on January 29, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.