Light/Dark Dual Themes
Shikiji supports outputting light/dark dual themes. Different from markdown-it-shiki's approach which renders the code twice, Shikiji's dual themes approach uses CSS variables to store the colors on each token. It's more performant with a smaller bundle size.
Change the theme
option in codeToHtml
to options
with light
and dark
keys to generate two themes.
import { getHighlighter } from 'shikiji'
const highlighter = await getHighlighter({
themes: ['nord', 'min-light'],
langs: ['javascript'],
})
const code = highlighter.codeToHtml('console.log("hello")', {
lang: 'javascript',
themes: {
light: 'min-light',
dark: 'nord',
}
})
The following HTML will be generated (demo preview):
<pre
class="shiki shiki-themes min-light nord"
style="background-color:#ffffff;--shiki-dark-bg:#2e3440ff;color:#24292eff;--shiki-dark:#d8dee9ff"
tabindex="0"
>
<code>
<span class="line">
<span style="color:#1976D2;--shiki-dark:#D8DEE9">console</span>
<span style="color:#6F42C1;--shiki-dark:#ECEFF4">.</span>
<span style="color:#6F42C1;--shiki-dark:#88C0D0">log</span>
<span style="color:#24292EFF;--shiki-dark:#D8DEE9FF">(</span>
<span style="color:#22863A;--shiki-dark:#ECEFF4">"</span>
<span style="color:#22863A;--shiki-dark:#A3BE8C">hello</span>
<span style="color:#22863A;--shiki-dark:#ECEFF4">"</span>
<span style="color:#24292EFF;--shiki-dark:#D8DEE9FF">)</span>
</span>
</code>
</pre>
To make it reactive to your site's theme, you need to add a short CSS snippet:
Query-based Dark Mode
@media (prefers-color-scheme: dark) {
.shiki,
.shiki span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
/* Optional, if you also want font styles */
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
}
Class-based Dark Mode
html.dark .shiki,
html.dark .shiki span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
/* Optional, if you also want font styles */
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
Multiple Themes
It's also possible to support more than two themes. In the themes
object, you can have an arbitrary number of themes, and specify the default theme with defaultColor
option.
const code = highlighter.codeToHtml('console.log("hello")', {
lang: 'javascript',
themes: {
light: 'github-light',
dark: 'github-dark',
dim: 'github-dimmed',
// any number of themes
},
// optional customizations
defaultColor: 'light',
cssVariablePrefix: '--shiki-'
})
A token would be generated like:
<span style="color:#1976D2;--shiki-dark:#D8DEE9;--shiki-dim:#566575">console</span>
Then update your CSS snippet to control when each theme takes effect. Here is an example:
Without Default Color
If you want to take full control of the colors or avoid using !important
to override, you can optionally disable the default color by setting defaultColor
to false
.
const code = highlighter.codeToHtml('console.log("hello")', {
lang: 'javascript',
themes: {
light: 'vitesse-light',
dark: 'vitesse-dark',
},
defaultColor: false, // <--
})
With it, a token would be generated like:
<span style="--shiki-dark:#D8DEE9;--shiki-light:#2E3440">console</span>
In that case, the generated HTML would have no style out of the box, you need to add your own CSS to control the colors.
It's also possible to control the theme in CSS variables. For more, refer to the great research and examples by @mayank99 in this issue #6.
Custom Language Aliases
You can register custom language aliases with the langAlias
option. For example:
import { getHighlighter } from 'shikiji'
const highlighter = await getHighlighter({
langs: ['javascript'],
langAlias: {
mylang: 'javascript',
},
})
const code = highlighter.codeToHtml('const a = 1', {
lang: 'mylang',
theme: 'nord'
})