Integrated Syntax Highlighting

Written by Dominik Pantůček on 2026-06-18

racketwebhtmlcss

Sometimes, if you wait long enough, someone else will solve the open problems you were postponing for so long. And this time, Jens Axel Søgaard, provided a very nice solution to one such problem of ours.


When we migrated from WordPress to our own static webpage generator using Racket and Punct, there were two major external dependencies left. Firstly we used GNU Source-highlight as an external tool for performing syntax highlighting on all the code blocks in the blog and secondly we used TEMML for converting any TeX formulae into HTML markup.

Today, we are switching to a pure Racket syntax highlighter which is part of the scribble-tools package. Although the primary goal of this package is to provide support for syntax highlighting of multiple languages in Scribble, Jens was so kind and included support for plain HTML/SXML rendering outside of Scribble documents.

The integration was absolutely straightforward although we had to opt for generating the HTML code and then re-parsing it into txexpr?, mainly for historical reasons. Any workflow using SXML should use the SXML output directly. We also needed to provide some mapping from the programming language names we use to the canonical names used by the code highlighting procedures provided by the package.

All that taken into account, the following is the actual code we use in production now:

(define (code->highlighted code-lst lang)
  (define lsym0 (string->symbol lang))
  (define lsym
    (case lsym0
      ((scheme) 'racket)
      ((javascript) 'js)
      (else lsym0)))
  (define res
    (string->xexpr
     (code-block->html lsym (string-join code-lst))))
  (define pre (caddr res))
  (define preattrs (cadr pre))
  (define code (caddr pre))
  (define codebody (cddr code))
  `(pre ,preattrs (code ((info ,(format "language-~a" lang))) ,@codebody)))

Basically the code strips any Javascript-related helpers (as we want to keep our website truly static without any client-side code running) and it keeps the language name in the info attribute of the code element - like we did from the start. The supporting CSS code is integrated in our global CSS and therefore no further adjustments are needed.

Perhaps one such adjustment we found to be worthwhile. We extracted all the color definitions into CSS variables and made their values depend on the dark mode setting and now our syntax-highlighted code looks much better in dark mode using the following CSS:

:root {
    --stx-green: #6A9955; 
    --stx-blue: #07A; 
    --stx-darker-blue: #1F5F8B; 
    --stx-emphasized-blue: #2B5F8A; 
    --stx-dark-blue: #262680; 
    --stx-red: #A31515; 
    --stx-purple: #5A3E8E; 
    --stx-vibrant-purple: #6B2F8A; 
    --stx-leather-brown: #7A6A4A; 
    --stx-saddle-brown: #795E26; 
    --stx-brown: #8A4F00; 
    --stx-grey: rgba(90, 90, 90, .72); 
}
@media (prefers-color-scheme: dark) {
    :root {
        --stx-green: #6A9955; 
        --stx-blue: #4FC1FF; 
        --stx-darker-blue: #569CD6; 
        --stx-emphasized-blue: #9CDCFE; 
        --stx-dark-blue: #569CD6; 
        --stx-red: #CE9178; 
        --stx-purple: #C586C0; 
        --stx-vibrant-purple: #D670D6; 
        --stx-leather-brown: #DCDCAA; 
        --stx-saddle-brown: #CE9178; 
        --stx-brown: #D7BA7D; 
        --stx-grey: #808080; 
    }
}

Whether you read our web in dark or light mode, you can toggle the view using the switch in the upper-right corner of the top-menu bar or the same switch in the collapsed menu for smaller devices.

As one unintended side-effect, the syntax highlighting is now faster as well. That was slightly expected as before integrating this change we had to spawn a new process for each and every code block to be highlighted. Using an integrated (and compiled) module is much more efficient, of course.

Hope you like websites being actual documents - like we do. See ya next time for more unconventional approaches to whatever problems we face!