Lumis Docs
Themes

CSS Builder

Build a theme stylesheet at runtime in Rust, JavaScript / TypeScript, and Elixir.

Lumis ships one CSS file per theme. The CSS builder lets you scope selectors, change the container selector, and add container styles.

The builder works with the same theme data as the bundled files. With no options, it produces the same CSS shape as @lumis-sh/themes/css/<theme>.css.

Options

The npm API uses camelCase. Rust and Elixir use snake_case.

OptionDefaultUse it for
scope""Parent selector prepended to every generated selector.
containerSelector / container_selector".lumis"Set the selector for the code block container rule.
containerStyle / container_stylenoneAdd declarations to the container selector. Use this for layout-related CSS such as padding, border-radius, or overflow-x. If a property already exists, the new value replaces it.
enableItalic / enable_italictrueDrop italic declarations when set to false.

containerStyle / container_style is a list of (property, value) pairs. For example, background-color: var(--code-background) replaces the theme background, while padding: 1rem adds a new declaration.

Token selectors are fixed as l-* classes, matching the built-in htmlLinked formatter output. Line wrappers use l-line, highlighted lines use l-highlighted by default, and line-number themes use l-line-number plus l-line-number-highlighted on highlighted gutters.

Use all options together

import {buildCss} from '@lumis-sh/themes'
import frappe from '@lumis-sh/themes/catppuccin_frappe'
const css = buildCss(frappe, {
scope: '.docs-theme',
containerSelector: '.code-block',
containerStyle: [
['background-color', 'var(--code-background)'],
['border-radius', '0.5rem'],
['padding', '1rem'],
],
enableItalic: false,
})

The output starts like this:

/* catppuccin_frappe
* revision: e068ab5f8261f23f6f71ffd8791ae40315b77b9c
*/
.docs-theme .code-block {
color: #c6d0f5;
background-color: var(--code-background);
border-radius: 0.5rem;
padding: 1rem;
}
.docs-theme .l-keyword {
color: #ca9ee6;
}

Default output

import {buildCss} from '@lumis-sh/themes'
import frappe from '@lumis-sh/themes/catppuccin_frappe'
const css = buildCss(frappe)

The output starts with the theme name and revision, then the container .lumis rule, then token rules:

/* catppuccin_frappe
* revision: e068ab5f8261f23f6f71ffd8791ae40315b77b9c
*/
.lumis {
color: #c6d0f5;
background-color: #303446;
}
.l-keyword {
color: #ca9ee6;
}
.l-string {
color: #a6d189;
}

Scope a stylesheet

Use scope when the same page needs more than one theme, or when a code block should inherit theme styles only inside part of your app. Do not include a trailing space; Lumis inserts the descendant combinator for you.

import {buildCss} from '@lumis-sh/themes'
import frappe from '@lumis-sh/themes/catppuccin_frappe'
const css = buildCss(frappe, {
scope: 'html[data-theme="dark"]',
containerStyle: [
['background-color', 'var(--code-background)'],
['border-radius', '0.375rem'],
['padding', '1rem'],
],
})

That produces selectors like this:

html[data-theme="dark"] .lumis {
color: #c6d0f5;
background-color: var(--code-background);
border-radius: 0.375rem;
padding: 1rem;
}
html[data-theme="dark"] .l-keyword {
color: #ca9ee6;
}

Change the container selector

The container rule uses .lumis by default. Change it when your rendered HTML uses a different wrapper class.

const css = buildCss(frappe, {
containerSelector: '.code-block',
})

That changes only the container rule selector:

.code-block {
color: #c6d0f5;
background-color: #303446;
}
.l-keyword {
color: #ca9ee6;
}

Other runtimes

Java and the CLI do not expose the CSS builder yet.

On this page