LumisDocs
Recipes

Line Numbers

Number the lines Lumis renders, in HTML and in the terminal.

line_numbers numbers the lines a formatter renders. The three HTML formatters and terminal take it; bbcode_scoped has nothing to render a number into and does not.

import {highlight} from '@lumis-sh/lumis';
import {htmlInline} from '@lumis-sh/lumis/formatters';
import javascript from '@lumis-sh/wasm-javascript';
import frappe from '@lumis-sh/themes/catppuccin_frappe';

const html = await highlight(
  'line 1\nline 2\nline 3',
  htmlInline({
    language: javascript,
    theme: frappe,
    lineNumbers: true,
  })
);

Line numbers correspond to HTML's data-line attribute and the numbers accepted by highlight_lines.

What each formatter renders

FormatterGutter
HTML Inline<span class="l-line-number"> inside the line's <span>
HTML Linkedthe same
HTML Multi-Themesthe same
Terminalthe number, right-aligned to the widest one and followed by a space
BBCode Scopednot available

HTML

The gutter is the first child of the line, inside <code>:

<span class="l-line" data-line="1"><span class="l-line-number" style="-webkit-user-select: none; user-select: none; color: #51576d;" aria-hidden="true">1</span><span style="color: #ca9ee6;">fn</span></span>

It is a real element rather than content: attr(data-line) so the number is in the document a reader can inspect, and aria-hidden so a screen reader is not read a number before every line. Its appearance comes from the theme's LineNr style. On a highlighted line it also has the l-line-number-highlighted class and uses CursorLineNr, falling back to LineNr when that style is absent.

HTML Inline writes the style directly, HTML Multi-Themes writes the corresponding theme variables, and HTML Linked uses the .l-line-number and .l-line-number-highlighted rules in generated theme CSS. All three keep the gutter out of selection copy with user-select: none (and its WebKit prefix). Theme CSS does not choose a gutter width, so give it a column:

.l-line-number {
  display: inline-block;
  width: 3ch;
  margin-right: 1ch;
  text-align: right;
}

Those are layout properties, which theme CSS does not set, so they apply as written. Recoloring the gutter is different. Theme CSS writes .l-line-number:not(.l-line-number-highlighted) to keep LineNr off a CursorLineNr gutter, and two classes beat the one in a bare .l-line-number, which therefore loses however late it is loaded. Scope the rule under the block instead, and load it after the theme stylesheet:

.lumis .l-line-number {
  color: #888;
}

That is two classes as well, so it wins on order rather than specificity, and it recolors the highlighted gutter along with the rest. To recolor only the lines that are not highlighted, and to win regardless of order, add the same :not() the theme rule carries.

With line_numbers disabled, HTML contains no gutter elements. Each line has a data-line attribute, which you can use to display numbers with CSS:

.l-line::before { content: attr(data-line); }

innerText and textContent include the gutter's text; user-select affects selection only. For a copy button, use the original source rather than either property. That also preserves CRLF and final newlines exactly. Chromium may omit trailing blank rows from selection copy when they contain only gutters.

Terminal

The number is padded to the widest one the render will show, so the code stays aligned past line 9.

Neovim draws the number column with LineNr, and with CursorLineNr on the cursor line, so CursorLine never reaches it. Lumis follows that: a highlighted line's background starts at its text, not at its gutter. The gutter uses every text attribute in the corresponding theme style, including foreground, bold, italic, and decorations. When a theme has no CursorLineNr style, a highlighted gutter falls back to its LineNr style.

A final newline terminates the last line; it does not add an extra line number in either terminal or HTML output.

lumis highlight -f terminal -t catppuccin_frappe -n main.rs
 8 fn main() {
 9     println!("hello");
10 }

On this page