Code blocks get syntax highlighting for the first time — and it reads in the house palette, not GitHub's
Every code block on every guide had been rendering as unstyled plain text, because Astro's built-in Shiki never sees content that LFM renders through a custom component. Wired up a highlighter whose theme is made of CSS variables, so the three modes re-point tokens instead of shipping three themes.
Code blocks get syntax highlighting for the first time
Why Care?
Open /guides/terminal-setup-ghostty-oh-my-posh before today and all fifteen code blocks were flat grey text. Not badly themed — not highlighted at all. Zero occurrences of shiki in the built HTML.
The cause is architectural rather than configuration, which is why it went unnoticed: Astro’s built-in Shiki only highlights markdown that Astro’s own pipeline renders to HTML. Our content path is LFM → MDAST → AstroMarkdown.astro → CodeBlock.astro, all custom. Astro’s highlighter never gets a look. Any site copying our LFM rendering pattern has inherited the same silence.
What’s New?
A highlighter whose theme is CSS variables. src/lib/code-highlight.ts builds one Shiki instance with a TextMate theme whose token colors are literally var(--code-keyword), var(--code-string), and so on. Shiki passes those straight into the inline styles it emits, which means one highlighter serves light, dark, and vibrant — the modes just re-point tokens in theme.css, exactly like every other color in the system. Re-theming code is a token edit, not a theme swap.
Twenty grammars preload; an alias map folds sh/zsh/terminal onto bash and text/txt onto plaintext; anything unrecognized degrades to escaped plain text rather than failing a build.
A palette, not a borrowed one. Dark and vibrant run the house neons at full strength — violet-electric keywords, lime-terminal strings, cyan-vapor functions, amber-flare numbers. Light mode mixes each toward ink until it carries on bone. Vibrant swaps keywords to magenta-fuse so the block doesn’t dissolve into the violet chrome around it.
Result on that one guide: 979 colored spans across 15 blocks, where there had been none.
Three bugs found along the way
CodeBlock.astrowas entirely off the token system — hardcoded#0f172abackground and#e2e8f0text, plus slate greys for the chrome. Every color now comes from a token.- Its border read
var(--border), a token that does not exist in this system. It had been silently falling back tocurrentColorfor as long as the component has existed. .docs-prose codewas styling block code as if it were inline — border, background, padding and--color-primaryapplied to the<code>inside every<pre>, drawing a second bordered box inside the code block..changelog-detail__body,.webinar-detail__body, and.book-reader__bodyall carry apre codereset for exactly this;docs-prose.csswas the one that never got one. Now guarded with:not(pre) > code.
Chrome, corrected
The language label and Copy button were absolutely positioned over the top-right of the code, crowding the first line and the right edge. They’re now a proper header row — flex, space-between, its own background and bottom border, label left and Copy right. The button went transparent with a border that appears on hover, since a filled background would vanish into the bar it now sits on; :focus-visible joined :hover, which had been mouse-only.
One self-inflicted regression worth recording: the first pass set display: block on Shiki’s .line spans, which double-spaced every block. Shiki separates those spans with a literal newline, and inside a <pre> that newline already breaks the line. They must stay inline. Horizontal scroll was never the reason to touch them — overflow-x: auto on the <pre> already handles it.
What’s Next?
- The pattern source is behind.
packages/lfm-astro/components/CodeBlock.astrois the canonical copy-from reference for every Astro Knots site, and it still has the unhighlighted version, the hardcoded slate, and the phantomvar(--border). Separate repo, separate commit — but any site copying it today inherits all three. - Light mode’s mix ratios (
lime-terminal42%,cyan-vapor38%,amber-flare55%) were set by judgment, not measured against a contrast target. Worth a real check. CodeBlock.astroaccepts ametaprop and drops it. Fence meta is wheretitle="~/.config/ghostty/config"belongs, and the terminal guide writes to three distinct paths that currently live only in the surrounding prose.- Fence audit came back clean — all 23 blocks across the content tree are correctly labeled (14
bash, 3yaml, 3text, and one each ofts,ini,json). No backfill needed.