Numbered Code Block Lines in Eleventy with Shiki Twoslash

Last Updated

Bringing in a third-party library for easy, reliable line numbering
A bull, with parts of the body indicated and <span class='font-bold'>numbered</span>: the numbers forming a border around the image. Woodcut, 17--
A bull, with parts of the body indicated and numbered: the numbers forming a border around the image. Woodcut, 17--. Public Domain Mark. Source: Wellcome Collection.

Markdown Code Block Styling, Part 1

This article is part of a series on styling markdown code blocks.

This first installment covers numbering lines. The second will cover highlighting lines. The third will cover added lines, deleted lines, and focused lines, in combination with numbered and/or highlighted lines.

The second, Highlight Code Block Lines In Eleventy with Shiki Twoslash, covers line highlighting.

The third will cover styling added lines, deleted lines, and focused lines, in combination with numbered and/or highlighted lines.

There’s no way to number lines in the code block HTML generated by Eleventy’s default Markdown library, markdown-it, which turns Markdown like this

md
```md
first line
second
```

into HTML like this

html
<pre><code class="language-md">first line
second
</code></pre>

The Prism-based first party syntax highlighting plugin, eleventy-plugin-syntaxhighlight, doesn’t pick up on Prism’s line number support. You can try to hack it by misusing Prism’s line highlighting, setting eleventy-plugin-syntaxhighlight’s undocumented alwaysWrapLineHighlights option to true, but ⚠️ there are multiple reports of that being glitchy — 11ty/eleventy-plugin-syntaxhighlight#10.

A solution

remark-shiki-twoslash, on the other hand, wraps each code block line in a div. For Eleventy v2 there’s the remark-shiki-twoslash plugin eleventy-plugin-shiki-twoslash. For Eleventy v3 you can use remark-shiki-twoslash with a small wrapper in your .eleventy.js file; see shikijs/twoslash#193 for details.

Shiki Twoslash, not to be confused with Shiki Twoslash

As of this writing, there are two Shiki Twoslashes, one under the shikijs org and one under the twoslashes org. remark-shiki-twoslash uses shikijs/twoslash.

Not just for Eleventy

There are remark-shiki-twoslash plugins for Markdown-It, Docusaurus, Eleventy, Gatsby, Hexo, and VuePress. Learn more at https://github.com/shikijs/twoslash.

This Markdown

md
```md
first line
second
```

becomes markup similar to this

html
<pre class="shiki">
  <div class="language-id">markdown</div>

  <div class="code-container">
    <code>
      <div class="line">first line</div>

      <div class="line">second</div>
    </code>
  </div>
</pre>

A CSS counter can be used to style those .lines. Something like

css
pre.shiki {
  counter-reset: codeblock-line;

  .line {
    counter-increment: codeblock-line;

    &::before {
      content: counter(codeblock-line);
    }
  }
}

Opting out of line numbers

With remark-shiki-twoslash, classes can be added to code blocks by including class attribute markup on the Markdown code block’s opening fence if a language is also specified on the opening fence.

This

md
````md
```md class="my-class"

```
text

renders as

```html class="codeblock-no-line-numbers codeblock-no-copy-button codeblock-delete-0 codeblock-add-1"
<pre class="shiki"></pre>
```

Use that to support opting out of line numbers:

```css class="codeblock-no-line-numbers codeblock-no-copy-button codeblock-delete-0 codeblock-add-1"
pre.shiki {
pre.shiki:not(.codeblock-no-line-numbers) {
  /* … */
}
```



  <div class="gap-4 grid [&>br]:hidden lg:grid-cols-2 my-4">
    <div class="flex flex-col gap-1 codeblock-no-language codeblock-no-copy-button [&>*]:my-0 [&>br]:hidden [&>p]:hidden">
      <div class="pb-2 text-sm">Markdown Source</div>

````md class="codeblock-no-line-numbers"

```text
has
line
numbers
```



Rendered Result

text
has
line
numbers
Markdown Source
md

```text class=&quot;codeblock-no-line-numbers&quot;
doesn&#39;t
have
line
numbers
```



Rendered Result

text
doesn&#39;t
have
line
numbers