TIL: Shiki multi-theme highlighting does not switch the code-block background
Shiki multi-theme mode swaps token colours per colour scheme but leaves the pre background variable empty, so light-theme text can land on a dark block. You have to theme the container yourself.
I gave the code blocks on this very blog a light and dark theme via Nuxt Content's multi-theme Shiki config:
export default defineNuxtConfig({
content: {
build: {
markdown: {
highlight: {
theme: {
default: 'github-dark',
dark: 'github-dark',
light: 'github-light',
},
},
},
},
},
});
In dark mode the code blocks looked great. In light mode the syntax colours were correct GitHub-light tokens, but they were sitting on a dark background, so the dark text was nearly invisible.
What is actually happening
Multi-theme Shiki emits per-token CSS variables and switches them with the colour-mode class on the <html> element:
html .shiki span {
color: var(--shiki-default);
}
html.dark .shiki span {
color: var(--shiki-dark);
}
html.light .shiki span {
color: var(--shiki-light);
}
That part works. The problem is the container. The <pre> element is styled with background: var(--shiki-default-bg), but the --shiki-light-bg and --shiki-dark-bg variables are never actually defined. So the block's background does not change with the colour mode. In light mode you get light-theme dark tokens on whatever single background the <pre> inherited.
The fix
Stop relying on Shiki for the block background and drive it from your own tokens, one rule per mode:
.prose pre.shiki {
background-color: oklch(0.96 0 0);
color: var(--foreground);
}
.dark .prose pre.shiki {
background-color: oklch(0.205 0 0);
}
This also lets the code block match the rest of your palette instead of GitHub's hardcoded backgrounds. Token colours keep switching via Shiki's variables; you own the container.
The way I caught this was a browser check of the actual computed style: getComputedStyle(pre).backgroundColor. It was reporting the same value in both modes, which told me immediately that the container, not the tokens, was the thing that never switched.