# CSS Builder (/themes/css-builder)

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.

| Option | Default | Use 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_style` | none | Add 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. |
| `layout` | `true` | Include the built-in line layout rules in `@layer lumis`. Set to `false` when your app supplies its own layout. |

`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.

## Colors without layout

Set `layout` to `false` to omit the entire `@layer lumis` block. Container,
token, and gutter theme styles stay, including italic, bold, and decoration
styles. Any explicit `containerStyle` / `container_style` declarations stay too.
Prebuilt CSS files always include layout.

### JavaScript / TypeScript

```js
const css = buildCss(frappe, {layout: false})
```

### Rust

```rust
let css = themes::CssBuilder::new(&theme).layout(false).build();
```

### Elixir

```elixir
css = Lumis.Theme.build_css!("catppuccin_frappe", layout: false)
```

### CLI

```sh
lumis themes build-css catppuccin_frappe --no-layout > theme.css
```


Use your own rules for [line layout](/themes/css-files#line-layout) when disabling it.

## Combine scoping and container styles

### JavaScript / TypeScript

```js
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'],
  ],
  layout: false,
})
```

### Rust

```rust
use lumis::themes;

let theme = themes::get("catppuccin_frappe").unwrap();

let css = themes::CssBuilder::new(&theme)
    .scope(".docs-theme")
    .container_selector(".code-block")
    .container_style([
        ("background-color", "var(--code-background)"),
        ("border-radius", "0.5rem"),
        ("padding", "1rem"),
    ])
    .layout(false)
    .build();
```

### Elixir

```elixir
css =
  Lumis.Theme.build_css!("catppuccin_frappe",
    scope: ".docs-theme",
    container_selector: ".code-block",
    container_style: [
      {"background-color", "var(--code-background)"},
      {"border-radius", "0.5rem"},
      {"padding", "1rem"}
    ],
    layout: false
  )
```

### CLI

```sh
lumis themes build-css catppuccin_frappe \
  --scope .docs-theme \
  --container-selector .code-block \
  --container-style 'background-color=var(--code-background)' \
  --container-style 'border-radius=0.5rem' \
  --container-style 'padding=1rem' \
  --no-layout > theme.css
```


The output starts like this:

```css
/* catppuccin_frappe
 * revision: e068ab5f8261f23f6f71ffd8791ae40315b77b9c
 * license: MIT
 */
.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

### JavaScript / TypeScript

```js
import {buildCss} from '@lumis-sh/themes'
import frappe from '@lumis-sh/themes/catppuccin_frappe'

const css = buildCss(frappe)
```

### Rust

```rust
use lumis::themes;

let theme = themes::get("catppuccin_frappe").unwrap();
let css = themes::CssBuilder::new(&theme).build();
```

### Elixir

```elixir
css = Lumis.Theme.build_css!("catppuccin_frappe")
```

### CLI

```sh
lumis themes build-css catppuccin_frappe > theme.css
```


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

```css
/* catppuccin_frappe
 * revision: e068ab5f8261f23f6f71ffd8791ae40315b77b9c
 * license: MIT
 */
.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.

### JavaScript / TypeScript

```js
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'],
  ],
})
```

### Rust

```rust
use lumis::themes;

let theme = themes::get("catppuccin_frappe").unwrap();

let css = themes::CssBuilder::new(&theme)
    .scope("html[data-theme=\"dark\"]")
    .container_style([
        ("background-color", "var(--code-background)"),
        ("border-radius", "0.375rem"),
        ("padding", "1rem"),
    ])
    .build();
```

### Elixir

```elixir
css =
  Lumis.Theme.build_css!("catppuccin_frappe",
    scope: ~s(html[data-theme="dark"]),
    container_style: [
      {"background-color", "var(--code-background)"},
      {"border-radius", "0.375rem"},
      {"padding", "1rem"}
    ]
  )
```

### CLI

```sh
lumis themes build-css catppuccin_frappe \
  --scope 'html[data-theme="dark"]' \
  --container-style 'background-color=var(--code-background)' \
  --container-style 'border-radius=0.375rem' \
  --container-style 'padding=1rem' > theme.css
```


That produces selectors like this:

```css
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.

### JavaScript / TypeScript

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

### Rust

```rust
let css = themes::CssBuilder::new(&theme)
    .container_selector(".code-block")
    .build();
```

### Elixir

```elixir
css =
  Lumis.Theme.build_css!("catppuccin_frappe",
    container_selector: ".code-block"
  )
```

### CLI

```sh
lumis themes build-css catppuccin_frappe \
  --container-selector .code-block > theme.css
```


That changes only the container rule selector:

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

## Other runtimes

Java does not expose the CSS builder yet.