LumisDocs
Themes

CSS Theme Files

Use Lumis CSS theme files with htmlLinked output in JavaScript / TypeScript, Elixir, and CDN workflows.

The htmlLinked formatter emits CSS class names instead of inline styles. You provide a theme stylesheet.

JavaScript / TypeScript package

Import a stylesheet from @lumis-sh/themes/css/<theme>.css.

import '@lumis-sh/themes/css/catppuccin_frappe.css'
import {highlight} from '@lumis-sh/lumis'
import {htmlLinked} from '@lumis-sh/lumis/formatters'
import javascript from '@lumis-sh/wasm-javascript'

const html = await highlight(
  'const x = 1',
  htmlLinked({language: javascript}),
)

CDN

Load the compiled CSS file directly:

<link
  rel="stylesheet"
  href="https://unpkg.com/@lumis-sh/themes/dist/css/catppuccin_frappe.css"
>

Available distribution folders:

  • CSS: https://unpkg.com/browse/@lumis-sh/themes/dist/css/
  • JSON: https://unpkg.com/browse/@lumis-sh/themes/dist/json/

Elixir / Phoenix

Lumis ships CSS files under priv/static/css/.

endpoint.ex
plug Plug.Static,
  at: "/themes",
  from: {:lumis, "priv/static/css/"},
  only: ["catppuccin_frappe.css"]
layout
<link
  phx-track-static
  rel="stylesheet"
  href={~p"/themes/catppuccin_frappe.css"}
/>

Repository-generated CSS

Inside this repo, CSS theme files are generated into multiple targets:

  • css/*.css
  • packages/elixir/lumis/priv/static/css/
  • packages/javascript/themes/dist/css/

That is why the same theme can be consumed from Rust assets, Elixir static files, and the npm themes package.

When to use CSS theme files

  • many code blocks on one page
  • existing app stylesheet pipeline
  • cacheable stylesheets
  • smaller HTML output than inline styles

If you want the block to be self-contained instead, use htmlInline.

Token classes

Token classes use the l- prefix. Dots in Tree-sitter highlight scopes become dashes and the rest of the scope is left alone, so comment becomes .l-comment, tag.attribute becomes .l-tag-attribute, and attribute.c_sharp becomes .l-attribute-c_sharp.

The gutter is not a Tree-sitter scope and does not follow that rule: LineNr is .l-line-number and CursorLineNr is .l-line-number-highlighted, which a highlighted gutter carries alongside .l-line-number. Theme CSS applies CursorLineNr without merging in LineNr, and a theme without CursorLineNr falls back to LineNr.

The block and language classes stay bare: .lumis and language-*.

.l-comment {
  font-style: normal;
}

If the pre-built files do not fit, generate a stylesheet at runtime with the CSS Builder. Use it for parent selectors, custom block selectors, or a background that comes from your app CSS.

Line layout

Each theme file ends with a small @layer lumis block that lays out the lines. The CSS that CssBuilder, build_css and buildCss generate ends with the same block by default. To generate theme colors without these rules, set layout: false or use lumis themes build-css <theme> --no-layout:

@layer lumis {
  pre.lumis > code {
    display: block;
    width: fit-content;
    min-width: 100%;
    overflow-wrap: anywhere;
  }
  pre.lumis > code > span.l-line {
    display: inline-block;
    width: 100%;
    min-height: 1lh;
    vertical-align: top;
  }
  .l-line-number {
    -webkit-user-select: none;
    user-select: none;
  }
}

<code> grows to its longest line, so a highlighted line covers the whole scrolled width, and a block wrapped with white-space: pre-wrap still wraps at its container. min-height keeps an empty line one row tall, and vertical-align: top stops it from sitting on the baseline and stretching the row. Line numbers stay out of selection copy.

If your stylesheet targets lines:

  • Select lines with .l-line; each line is a <span>.
  • Keep lines inline or inline-block. A display: block line plus the newline after it renders an extra blank row, and code { display: grid } makes some engines leave empty lines out of copied text.
  • Add box-sizing: border-box when a line has horizontal padding or a border, so it still fits in width: 100%.

Your own CSS overrides the block as long as it is not in a layer itself. If it is, as with Tailwind v4, declare lumis before your layers; see Tailwind CSS.

If your styles target div.l-line, follow the HTML line markup migration.

On this page