RSS Amplifier

daverupert.com · Aug 19, 2026

Introducing Microlighter

0
Sign in to vote or save

Dave Rupert · Dave Rupert

I made a smol client-side syntax highlighter that uses the CSS Custom Highlights API called MicroLighter. I’ll talk about why I made it in a bit but first I wanted to… ahem… highlight… some of the features.

At some point I broke syntax highlighting on my Jekyll blog. I’ve used a handful of syntax highlighters over the years (Highlight.js, PrismJS, Rouge, Shiki, etc, etc) and I’ve felt the trade-offs between different client-side and server-side implementations. Faced with picking another, I knew I wanted to explore Bramus’s technique of syntax highlighting with the CSS Custom Highlights API.

There’s some limitations with the CSS ::highlight() pseudo; no italics, no bold, no font swapping – but otherwise it’s pretty cool syntax to idiomatically express “I want to highlight this token” via CSS instead of injecting spans everywhere. Using the Highlight API means I avoid any DOM mutation and the scope of the library shrinks down to: scan code blocks using regex patterns and send CSS.highlights.set(category, textRanges) to highlight the code blocks.

I don’t need much syntax highlighting on this site. Not all posts have code and my code samples are a whole fifteen lines long at best. My struggle is that I swap languages often. I’ll do HTML, CSS, and JavaScript all in the same post. A little bash here, a little ruby there, some markdown as a treat. Using so all those languages grew the complexity beyond the limits of my regex-fu, so I decided to lean on Textmate’s established collections of patterns used by VS Code. And before I knew it, my little highlighter could do almost any language.

Knowing I use different languages often, one principle I established was that all language grammars should be auto-loaded on-demand to reduce configuration and bundle size. That way you only pay for what you use.

Inspired by PrismJS’s simplified token categories I flattened down Textmate’s granular token categories to a more human-friendly set, making it easier to style. In addition to that, one enormous nit-pick I have with codeblock styling is that light and dark themes are separate entities, so I merged them into one theme using light-dark().

The last big opinion I baked in was that I wanted the syntax highlighter to do one job: infer language and highlight code in that language. With that a guideline, I moved all extra functionality (like line-numbers, etc) over to a web component. The vanilla web component adds about ~1 KiB in size, but co-locating UI into a UI primitive like native custom elements feels right and the ShadowDOM encapsulation makes it easy to separate the code from the presentational UI.

Obviously, I’m a web component pervert but it feels like a great separation of concerns versus trying to cram everything into core library.

Try it out

To get started on your site, I’d use the self-initializing minified bundle, but I’m also shipping ESM and a web component.

npm install microlighter
<script type="module" src="path/to/microlighter/microlighter.min.js"></script>

Like I said above, not all my posts have syntax highlighting, so I even wait to import the script unless I know there’s a page with code on it.

if(document.querySelector('pre>code').length) {
	import('path/to/microlighter/microlighter.min.js');
}

You can use the ESM version if you’re going to do something fancy yourself:

import { highlightAll } from 'microlighter'
highlightAll({
	selector: 'pre.onlyTheseGetHighlights'
})

And you can also use the web component if you want those extra features I talked about:

<micro-lighter data-syntax-theme="github" line-numbers controls="copy">
  <pre><code>Code goes here</code></pre>
</micro-lighter>

Web component classes are pretty extendible too so if I don’t support something you need, you can “fork” it by extending the base class and adding your own features.

Lastly, you can use one of the pre-supplied themes or roll your own. The basic structure is:

/**
 * Setup semantic `--syntax-*` tokens
 * @value background | foreground | comment | keyword |
 * operator |string | constant | function | type | variable |
 * property | tag | selector | inserted | deleted
 */
[data-syntax-theme="my-theme-name"] {
  color-scheme: light dark;
  /* Code block tokens */
  --syntax-background: light-dark(#f8f8f8, #3a3a3a);
  --syntax-foreground: light-dark(#3a3a3a, #f8f8f8);
  /* Highlight tokens */
  --syntax-comment: light-dark(#6e7781, #8b949e);
	--syntax-function: light-dark(#8250df, #d2a8ff);
  /* ...etc... */
}
[data-syntax-theme="my-theme-name"] pre:has(code) {
	background-color:var(--syntax-background);
	color:var(--syntax-foreground)
}
::highlight(comment) { color: var(--syntax-comment) }
::highlight(function) { color: var(--syntax-function) }
/* ...etc... */

And that’s MicroLighter. If you end up using it and trying it out, let me know what you think.

Read the original on daverupert.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.