Lumis Docs
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/lumis/langs/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})
);

The number is the one every line already carries: HTML's data-line and highlight_lines name the same line.

What each formatter renders

FormatterGutter
HTML Inline<span class="l-line-number"> inside the line's <div>
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>:

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

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. Theme CSS does not lay out the gutter, so give it a column and keep it out of a copied selection:

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

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.

Without line_numbers, nothing is added: data-line is on every line as it always has been, and the CSS-only approach still works.

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

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.

The number is written with the line's text, so the empty line a trailing newline opens carries none: a terminal writes nothing for that line, and a bare number after the output would be all you saw of it. HTML numbers it, because HTML emits a <div> for it.

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

On this page