The other day I was working on a JSON Schema Generator, and wanted to display line numbers in a
I did some research, and found multiple approaches:
I did not like any of them! The first one didn't look crisp — and didn't match the styles I already had in place for my
The second one required a bunch of JavaScript to maintain that ordered list: adding/removing
So I ended up creating a hybrid.
It's a dynamically generated SVG, stored as a CSS Custom Property — and used as a background-image, inheriting the styles from it's parent

Let's dive in.
First, the main method:
lineNumbers(element, numLines = 50, inline = false)
element is the
Next, we define a prefix for the custom property:
const prefix = '--linenum-';
Before we continue, we check whether to re-use any existing property:
if (!inline) {
const styleString = document.body.getAttribute('style') || '';
const regex = new RegExp(`${prefix}[^:]*`, 'g');
const match = styleString.match(regex);
if (match) {
element.style.backgroundImage = `var(${match[0]})`;
return;
}
}
Next, we extract styles from element, rendering the SVG with the same font-family, font-size, line-height etc. :
const bgColor = getComputedStyle(element).borderColor; const fillColor = getComputedStyle(element).color; const fontFamily = getComputedStyle(element).fontFamily; const fontSize = parseFloat(getComputedStyle(element).fontSize); const lineHeight = parseFloat(getComputedStyle(element).lineHeight) / fontSize; const paddingTop = parseFloat(getComputedStyle(element).paddingTop) / 2; const translateY = (fontSize * lineHeight).toFixed(2);
We need a random id for our property as well:
const id = `${prefix}${Math.random().toString(36).substr(2, 6)}`;
And now it's time to render the SVG:
const svg = `<svg xmlns="http://www.w3.org/2000/svg">
<style>
svg { background: ${bgColor}; }
text {
fill: hsl(from ${fillColor} h s l / 50%);
font-family: ${fontFamily};
font-size: ${fontSize}px;
line-height: ${lineHeight};
text-anchor: end;
translate: 0 calc((var(--n) * ${translateY}px) + ${paddingTop}px);
}
</style>
${Array.from({ length: numLines }, (_, i) => `<text x="90%" style="--n:${i + 1};">${i + 1}</text>`).join("")}
</svg>`;
Let's break it down:
In the