Light/Dark Dual Themes
Shiki supports outputting light/dark dual or multiple themes. Shiki's dual themes approach uses CSS variables to store the colors on each token.
Change the theme
option in codeToHtml
to options
with light
and dark
keys to generate two themes.
import { codeToHtml } from 'shiki'
const code = await 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.
import { codeToHtml } from 'shiki'
const code = await 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-'
})
span
tokens would be generated with respective theme's css variables:
<span style="color:#1976D2;--shiki-dark:#D8DEE9;--shiki-dim:#566575">console</span>
After that, you need to apply theme's css variables on element with shiki
class and tokens under it, for example, based on parent's data-theme
property:
[data-theme='dark'] .shiki,
[data-theme='dark'] .shiki span {
background-color: var(--s-dark-bg) !important;
color: var(--s-dark) !important;
}
[data-theme='dim'] .shiki,
[data-theme='dim'] .shiki span {
background-color: var(--s-dim-bg) !important;
color: var(--s-dim) !important;
}
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
.
import { codeToHtml } from 'shiki'
const code = await 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.