March 20, 2024, 10:42 a.m.

Expressive Code: My go-to code syntax highlighter

A new syntax highlighter, Expressive Code, makes it a no-brainer to highlight code. It has many wanted features by default, like line markers and multi-themes support, and works in any build system.

Baptiste's Web Dev Journey

Hey there!

When writing technical articles, having a good code highlighter to present code snippets is mandatory. I tried a few of them but only recently became fully convinced by a new code highlighter: Expressive Code.

Expressive Code is based on Shiki and extends its capabilities. Unlike other code highlighters, like Prism, Shiki is meant to run at build-time or during server-side rendering to ship zero JavaScript to the browser. Shiki and Expressive Code accept any VS Code theme as a valid theme configuration.

Expressive Code has native Astro support. It is enabled by default on Starlight, the template for documentation websites made by Astro's team. Expressive Code also provides a remark plugin for use in any other environment.

Shiki supports many programming languages, so you should be good to go even with the most esoteric ones.

Line markers

The first characteristic of Expressive Code that differentiates it from many other solutions is that it can highlight lines of code by default.

When defining your code blocks in Markdown, you can specify the lines you want to highlight next to the language of the snippet:

Markdown code block defining highlighted lines

It renders the following code block:

Code block with highlighted lines

It's also possible to mark some lines as insertions or deletions with the ins and del attributes:

Markdown code block defining inserted and deleted lines

It renders the following code snippet:

Code block with inserted and deleted lines

Expressive Code also supports diffs without losing the syntax highlighting of the code block's programming language. I've often tried using a diff code block to highlight changes with other syntax highlighters, but the syntax highlighting was always lost.

Markdown code block defining a diff of JavaScript code

Renders that beautiful diff while preserving JavaScript highlighting:

JavaScript code block with a diff

It's also possible to give labels to the diffs:

Markdown code block defining highlighted, inserted, and deleted lines with labels

It will render the labels in the margin:

Code block defining highlighted, inserted, and deleted lines with labels

Frames

Expressive Code determines the frame to use based on the language of the code block. For shell scripts, the frame looks like a macOS terminal; for source code, it looks like a tab in a code editor.

The frame of a shell script:

Shell code block with a macOS-like window style

The frame can be titled either by providing the title attribute to the code block or by the first line of the code block being a comment:

Two Markdown code blocks, the first one defining the title attribute and the second one defining a comment on the first line of code

Renders the following frames:

Two titled frames

And a shell frame with a title looks like this:

Shell frame with the title "PowerShell terminal example"

Additional plugins

Expressive Code has two optional plugins. The first plugin is to create collapsible sections:

Code block with collapsed lines. The collapsed lines are represented by a button titled "x collapsed lines"

The second plugin is to add line numbers:

Code block with line numbers on the left margin

Copy code blocks

It's important to mention that every code block includes a button to copy its content, which appears when the block is hovered over. I like having this as a default behavior.

Hovered shell code block with a copy button on the right

Dark mode

Expressive Code supports using multiple themes. By default, it has a theme for light mode and another for dark mode.

On XState by Example, it renders like that:

Code blocks on XState by Example use a different theme for light and dark modes

Using multiple themes doesn't dramatically increase the HTML bundle size because it relies on CSS variables to style the code blocks' tokens. Expressive Code doesn't duplicate all the HTML content for each theme—that's smart!

The <Code /> component

There is one last significant benefit of using Expressive Code, and it's a recent addition: the <Code /> component of the Astro's integration.

For XState by Example, I want to display the source code of each demo below the demo. Thanks to the <Code /> component, I can get syntax highlighting for dynamic code snippets.

I get the raw content of the source files and display their content with Expressive Code:

---
import { Code } from "astro-expressive-code/components";

const { default: machineCode } = await import(
  `../examples/${Astro.params.machine}/machine.ts?raw`
);
---

<Code code={machineCode} lang="ts" title="machine.ts" />

Astro doesn't support rendering such dynamic content, so I had to find a horrible escape hatch to make it work before the <Code /> component became a thing.

Wrapping up

I'm delighted to use Expressive Code to highlight the code blocks on my website, XState by Example. I will now use it every time I need to display code snippets on a website.

Expressive Code is perfect and deserves much more love, so let's spread it worldwide!

Best,
Baptiste

You just read issue #3 of Baptiste's Web Dev Journey.

Share on Twitter Share on LinkedIn
Blog GitHub YouTube X
This email brought to you by Buttondown, the easiest way to start and grow your newsletter.