Skip to content

<Showcase />

What Lumis can do.

From built-in languages and themes to custom formatters and framework integrations, on every runtime.

Dual themes

htmlMultiThemesTypeScript

Two themes in one render, switched by the reader's colour scheme rather than by JavaScript.

The code
import { createHighlighter } from '@lumis-sh/lumis'
import { htmlMultiThemes } from '@lumis-sh/lumis/formatters'
import typescript from '@lumis-sh/wasm-typescript'
import latte from '@lumis-sh/themes/catppuccin_latte'
import frappe from '@lumis-sh/themes/catppuccin_frappe'

const hl = await createHighlighter({ languages: [typescript] })

const html = hl.highlight(source, htmlMultiThemes({
  language: typescript,
  themes: { light: latte, dark: frappe },
  defaultTheme: 'light-dark()',
}))

Fetching parser…

Injected languages

ElixirHEExMarkdown

A HEEx template in a sigil and Markdown in a doc string, each parsed by its own grammar.

The code
import elixir from '@lumis-sh/wasm-elixir'
import heex from '@lumis-sh/wasm-heex'
import markdown from '@lumis-sh/wasm-markdown'

const hl = await createHighlighter({
  languages: [elixir, heex, markdown],
})

const html = hl.highlight(source, htmlMultiThemes({
  language: elixir,
  themes: { light: latte, dark: frappe },
  defaultTheme: 'light-dark()',
}))

Fetching parser…

Markdown code fences

MarkdownRustJavaScript

Fenced blocks are highlighted in the language the fence names, at any depth.

The code
const hl = await createHighlighter({
  languages: [markdown, rust, javascript],
})

const html = hl.highlight(readme, htmlMultiThemes({
  language: markdown,
  themes: { light: latte, dark: frappe },
  defaultTheme: 'light-dark()',
}))

Fetching parser…

Template languages

ERBRubyHTML

ERB, EEx, HEEx, Vue and Svelte carry two grammars at once, and both get highlighted.

The code
import { erb } from '@lumis-sh/wasm-embedded-template'
import html from '@lumis-sh/wasm-html'
import ruby from '@lumis-sh/wasm-ruby'

const hl = await createHighlighter({ languages: [erb, html, ruby] })

const output = hl.highlight(template, htmlMultiThemes({
  language: erb,
  themes: { light: latte, dark: frappe },
  defaultTheme: 'light-dark()',
}))

Fetching parser…

Highlight lines

highlightLinesRust

Mark line ranges with a CSS string, a class of your own, or the theme's highlight colour.

The code
const html = hl.highlight(source, htmlMultiThemes({
  language: rust,
  themes: { light: latte, dark: frappe },
  defaultTheme: 'light-dark()',
  highlightLines: {
    lines: [[3, 6]],
    style: 'background: light-dark(#dce0e8, #414559)',
  },
}))

Fetching parser…

Rainbow brackets

rainbowBracketsJavaScript

Bracket pairs coloured by depth, taken from each language's brackets query rather than a fixed list.

The code
const html = hl.highlight(source, htmlMultiThemes({
  language: javascript,
  themes: { light: latte, dark: frappe },
  defaultTheme: 'light-dark()',
}), { rainbowBrackets: true })

Fetching parser…

250+ Neovim themes

htmlInlineElixir

Each theme is generated from the colorscheme itself, so it looks like the editor it came from.

The code
import dracula from '@lumis-sh/themes/dracula'
import nord from '@lumis-sh/themes/nord'
import githubLight from '@lumis-sh/themes/github_light'
import tokyonightMoon from '@lumis-sh/themes/tokyonight_moon'

for (const theme of [dracula, nord, githubLight, tokyonightMoon]) {
  render(hl.highlight(source, htmlInline({ language: elixir, theme })))
}

Fetching parser…

CSS classes instead of inline styles

htmlLinkedCSS

Tokens get scope class names, and the colours come from a stylesheet the browser can cache.

The code
<link rel="stylesheet" media="(prefers-color-scheme: light)"
      href="https://cdn.jsdelivr.net/npm/@lumis-sh/themes/dist/css/catppuccin_latte.css">
<link rel="stylesheet" media="(prefers-color-scheme: dark)"
      href="https://cdn.jsdelivr.net/npm/@lumis-sh/themes/dist/css/catppuccin_frappe.css">

<script type="module">
  const html = hl.highlight(source, htmlLinked({ language: css }))
</script>

Fetching parser…

File header and copy button

headerTypeScript

The header option wraps the output in your own markup, so a code block can carry a toolbar.

The code
const html = hl.highlight(source, htmlMultiThemes({
  language: typescript,
  themes: { light: latte, dark: frappe },
  defaultTheme: 'light-dark()',
  header: {
    openTag: '<figure><figcaption>formatPrice.ts</figcaption>',
    closeTag: '</figure>',
  },
}))

Fetching parser…

Custom formatter

FormatterEvents

A formatter is an object with a render method that receives the highlight events, so any output format is a few lines away.

The code
const tokenTable = {
  language: javascript,
  render(source, events) {
    const bytes = new TextEncoder().encode(source)
    const scopes = []
    const rows = []
    for (const event of events) {
      if (event.type === 'start') scopes.push(event.scope)
      if (event.type === 'end') scopes.pop()
      if (event.type !== 'source') continue
      const text = new TextDecoder().decode(bytes.subarray(event.start, event.end))
      const scope = scopes.at(-1)
      if (scope && text.trim()) rows.push(scope.padEnd(24) + text)
    }
    return rows.join('\n')
  },
}

const table = hl.highlight(source, tokenTable)

Fetching parser…

Terminal output

terminalJSON

The same themes as ANSI escape codes, which is what the CLI prints.

The code
import { terminal } from '@lumis-sh/lumis/formatters'

const ansi = hl.highlight(source, terminal({ language: json, theme: frappe }))
process.stdout.write(ansi)

Fetching parser…

BBCode output

bbcodeScopedJSON

Scope names as BBCode tags, for forums and anywhere else that is not a browser.

The code
import { bbcodeScoped } from '@lumis-sh/lumis/formatters'

const bbcode = hl.highlight(source, bbcodeScoped({ language: json }))

Fetching parser…

Examples you can clone and run.

Frameworks, editors and terminals cannot be embedded in a page. Each of these is a directory in the repository.