All articles
nuxt

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.

Vinayak Kulkarni
1 min read

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.

nuxtshikinuxt-contentdark-mode