# Themes (/themes)

Lumis themes come from the Neovim ecosystem. You can use the built-in collection, load theme JSON yourself, or generate new themes from a colorscheme repository.

To match an application or terminal background automatically, use
[`closest`](/themes/closest).

## Pick a built-in theme

### JavaScript / TypeScript

```js
import frappe from '@lumis-sh/themes/catppuccin_frappe';

console.log(frappe.name);
console.log(frappe.appearance);
```

### Rust

```rust
use lumis::themes;

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

### Elixir

```elixir
theme = Lumis.Theme.get("catppuccin_latte")
theme.name
```

### Java

```java
import io.roastedroot.lumis4j.core.Theme;

var theme = Theme.CATPPUCCIN_FRAPPE;
```

### CLI

```bash
lumis themes list
```


## Transform theme styles

Use `mapStyles` / `map_styles` to change colors, backgrounds, or text decorations
before rendering. The callback receives each existing scope name and its complete
style once, and returns the style to use instead. It does not merge a partial
patch. Keep the other fields with a spread or struct update.

For a complete example, see [Customize theme styles](/recipes/customize-theme-styles).

### JavaScript / TypeScript

```ts
import githubLight from '@lumis-sh/themes/github_light';
import { mapStyles } from '@lumis-sh/lumis/themes';
import { buildCss } from '@lumis-sh/themes';

const theme = mapStyles(githubLight, (scope, style) => {
  switch (scope) {
    case 'markup.raw.block':
      return { ...style, italic: false };
    case 'normal':
      return { ...style, bg: '#ffffff' };
    case 'highlighted':
      return { ...style, bg: '#fff3bf' };
    default:
      return style;
  }
});

const css = buildCss(theme);
```

The original theme is unchanged. The returned theme has its own style objects,
including for entries whose callback returns the style unchanged.

To disable italics across the whole theme:

```ts
const upright = mapStyles(githubLight, (_scope, style) => ({
  ...style,
  italic: false,
}));
```

### Rust

```rust
use lumis::themes;

let theme = themes::get("github_light").unwrap()
    .map_styles(|scope, mut style| {
        match scope {
            "markup.raw.block" => style.italic = false,
            "normal" => style.bg = Some("#ffffff".into()),
            "highlighted" => style.bg = Some("#fff3bf".into()),
            _ => {}
        }
        style
    });

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

`map_styles` consumes and returns the theme. Call `.clone()` first to retain the
original. The callback accepts owned styles and can capture mutable state.

To disable italics across the whole theme:

```rust
let upright = theme.map_styles(|_, mut style| {
    style.italic = false;
    style
});
```

### Elixir

```elixir
alias Lumis.Theme

theme =
  Theme.get("github_light")
  |> Theme.map_styles(fn
    "markup.raw.block", style -> %{style | italic: false}
    "normal", style -> %{style | bg: "#ffffff"}
    "highlighted", style -> %{style | bg: "#fff3bf"}
    _scope, style -> style
  end)

css = Theme.build_css!(theme)
```

The callback returns a `Lumis.Theme.Style` struct. The original theme is unchanged.

To disable italics across the whole theme:

```elixir
upright = Theme.map_styles(theme, fn _scope, style -> %{style | italic: false} end)
```


Pass the result wherever a theme is accepted: inline HTML, each theme in
multi-theme HTML, terminal output, or custom formatters. For linked HTML, build
CSS from the modified theme and use that stylesheet instead of the prebuilt one.

Transformations preserve scope names, the theme name, appearance, revision, and
license. They visit only existing entries, including language-specific entries,
in an unspecified order. They do not add missing scopes or expand fallback
styles. An exact match for `markup.raw.block` does not also change an existing
`markup.raw.block.markdown` entry. Match those explicitly when needed.

Setting a style's `italic` to `false` removes that scope's contribution; it does
not cancel italics inherited from an enclosing highlight or the surrounding
page. Independently italic comments inside a code fence can remain italic.

You can compose transformations by passing the result to another call. An empty
theme stays empty and never calls the callback. Callback errors propagate to the
caller. The API does not convert colors or turn a dark palette into a light one.

## Load a custom theme from JSON

Generate the file with the [Neovim colorscheme recipe](/recipes/generate-theme-from-neovim-colorscheme),
or write it by hand. Point `$schema` at the theme schema to get completion and
validation in editors that support JSON Schema. [Theme files](/themes/theme-files)
describes every field:

```json
{
  "$schema": "https://lumis.sh/schemas/themes/v1.0.0.json",
  "name": "my_theme",
  "appearance": "dark",
  "revision": "v1",
  "highlights": {
    "keyword": { "fg": "#ff79c6", "bold": true },
    "comment": { "fg": "#6272a4", "italic": true }
  }
}
```

Then load it in your runtime:

### JavaScript / TypeScript

Import your generated JSON as a theme object; no loader helper is needed:

```js
import theme from './my-theme.json' with { type: 'json' };
import { htmlInline } from '@lumis-sh/lumis/formatters';
import javascript from '@lumis-sh/wasm-javascript';

const formatter = htmlInline({ language: javascript, theme });
```

Use Node.js JSON modules or a bundler with JSON import support.

### Rust

```rust
use lumis::themes;

let theme = themes::from_file("my_theme.json").unwrap();
```

### Elixir

```elixir
{:ok, theme} = Lumis.Theme.from_file("/path/to/theme.json")

Lumis.highlight!("const x = 1",
  formatter:
    {:html_inline, language: "javascript", theme: theme}
)
```

### Java

The external [Lumis4J binding](/usage/java) documents enum-based theme selection.
Check its documentation for custom theme support in your version.

### CLI

```bash
lumis highlight index.js --theme ./my-theme.json
lumis themes show ./my-theme.json
lumis themes build-css ./my-theme.json > my-theme.css
```

You can also place the file in the data directory's `themes/` folder and use its
filename stem, not its JSON `name` field. See
[local custom themes](/cli/behavior#local-custom-themes).


## Generate a theme from a Neovim colorscheme

Follow [Generate a theme from a Neovim colorscheme](/recipes/generate-theme-from-neovim-colorscheme)
for prerequisites, a runnable example using a theme outside the bundled collection,
and source-license and redistribution considerations. Generation requires Neovim
with `vim.pack`; it preserves the detected source license rather than relicensing
the theme.

## CSS theme files

If you use `htmlLinked`, pair the HTML output with a stylesheet. See [CSS Theme Files](/themes/css-files) for import paths and runtime-specific setup.

## Theme switching with CSS variables

If you want one block to support multiple themes, use the multi-theme formatter from [Formatters](/formatters).