Lumis Docs
Integrations

Vite

Highlight code in static Vite HTML entry points with Lumis.

Full example on GitHub

Use @lumis-sh/vite for HTML entry points that Vite processes directly. During transformIndexHtml the plugin parses the document to locate its code blocks, runs rehype-lumis over each one, and splices the result back into the source.

Install

npm install -D @lumis-sh/vite
npm install @lumis-sh/lumis @lumis-sh/themes @lumis-sh/wasm-html @lumis-sh/wasm-javascript

Configure Vite

vite.config.js
import {defineConfig} from 'vite'
import lumis from '@lumis-sh/vite'
import {htmlMultiThemes} from '@lumis-sh/lumis/formatters'
import html from '@lumis-sh/wasm-html'
import javascript from '@lumis-sh/wasm-javascript'
import githubDark from '@lumis-sh/themes/github_dark'
import githubLight from '@lumis-sh/themes/github_light'

export default defineConfig({
  plugins: [
    lumis({
      languages: [html, javascript],
      formatter: (language) =>
        htmlMultiThemes({
          language,
          themes: {light: githubLight, dark: githubDark},
          defaultTheme: 'light-dark()',
        }),
    }),
  ],
})

The plugin creates one highlighter per Vite plugin instance. It initializes that highlighter at build or dev-server startup and reuses it for every HTML transform. Put languages in languages to load their parsers during that startup. A language injected into another loads from its own package, so install the packages for the languages your HTML can inject too.

Mark code in HTML

Name a language to highlight a block:

<pre><code class="language-javascript">const answer = 42</code></pre>

Or put data-language on <pre>:

<pre data-language="javascript"><code>const answer = 42</code></pre>

Blocks without a language are left untouched. For plain text with Lumis styling, use language-plaintext or data-language="plaintext".

To format all unlabeled blocks, set defaultLanguage to a language name:

lumis({
  defaultLanguage: 'plaintext',
  formatter: (language) => htmlMultiThemes({
    language,
    themes: {light: githubLight, dark: githubDark},
    defaultTheme: 'light-dark()',
  }),
})

The default is false, which skips unlabeled blocks. An omitted or undefined value uses that default.

Add nohighlight or no-highlight to either <pre> or <code> to leave that block untouched, even when it names a language or defaultLanguage is set:

<pre><code class="language-javascript nohighlight">filled after load</code></pre>

Skipped blocks keep their original source byte for byte. A block that names an unavailable language still renders as plain text with the configured formatter.

<code> has to be the first child of <pre>. The HTML parser drops a single newline directly after <pre>, so writing the <code> on the next line still matches, but indenting it does not: the indentation is a text node, and the block is left alone.

<pre data-language="javascript">
  <code>never highlighted</code>
</pre>

Because the document is parsed as HTML before highlighting, entity-encoded source is decoded before Lumis sees it:

<pre data-language="html"><code>&lt;button&gt;Save&lt;/button&gt;</code></pre>

Authored attributes on both <pre> and <code> survive highlighting. Classes are combined, authored styles are appended to Lumis styles, and authored values win for attributes such as id, role, aria-*, data-*, translate, and tabindex.

Everything outside a highlighted block is returned byte for byte. The plugin parses the document to locate the blocks, then splices the highlighted HTML into the original source at their offsets instead of reserializing the tree, the same way Vite applies its own HTML edits. So your doctype spelling, unquoted attributes, character references, and any <%= … %> left for a templating plugin to fill in all reach the next plugin untouched.

Framework projects

This package is for Vite's HTML entry-point pipeline. Frameworks that handle their own entry documents may not run transformIndexHtml; use the framework's Lumis integration instead, such as Astro, Nuxt, or rehype-lumis.

On this page