Building TUI Themes for VitePress
Terminal UI aesthetics have been having a moment. NeoVim distributions like LazyVim and AstroNvim have popularised the look: dark backgrounds, monospace fonts, nerd font icon glyphs, and status bars packed with information. This post walks through how I brought that aesthetic to a static site.
Design principles
A few rules I set for myself up front:
- No colored background blocks for decorative arrows — separators should be colored text glyphs, not filled powerline segments
- IBM Oxocarbon palette only — consistent Carbon Design System colors for both dark and light modes
- Everything monospace where it makes sense — UI chrome (header, sidebar, statusline) uses IBM Plex Mono; prose uses IBM Plex Sans
Layout
The layout is a fixed-height viewport split into three horizontal zones:
┌──────────────────────────────────────────┐
│ user@terminal:~/blog ❯ blog about │ header (36px)
├────────┬──────────┬───────────────────────┤
│ │ │ │
│ FILE │ POSTS │ content │ body (flex)
│ TREE │ (RSS) │ │
│ │ │ │
├────────┴──────────┴───────────────────────┤
│ NORMAL ❯ ~/blog/post ❮ 45% ❮ 12:34│ statusline (24px)
└──────────────────────────────────────────┘The blog post panel only appears on blog post pages. The blog index is a full-width list view. All other pages show just the sidebar and content.
Color mode
VitePress has a built-in appearance system, but I disabled it (appearance: false) to implement a three-way toggle: auto → dark → light → repeat.
The selection is persisted in localStorage under terminal-color-mode. An inline <script> in the <head> applies the html.dark class before the first paint to prevent flash.
// In config.mts head
['script', {}, `(function(){
try {
var m = localStorage.getItem('terminal-color-mode') || 'auto'
var d = window.matchMedia('(prefers-color-scheme: dark)').matches
if (m === 'dark' || (m === 'auto' && d)) {
document.documentElement.classList.add('dark')
}
} catch(e) {}
})()`]Nerd Fonts
Icons come from the Nerd Fonts Symbols Only font, loaded via jsDelivr. A utility .nf CSS class applies the right font-family:
.nf {
font-family: 'Symbols Nerd Font Mono', monospace;
font-size: 1em;
line-height: 1;
display: inline-block;
}Then in templates: <span class="nf"></span> renders a folder-open glyph.
Syntax highlighting
Shiki handles code blocks with a dual theme — github-light for light mode and github-dark for dark mode. VitePress wires this up through CSS variables (--shiki-light, --shiki-dark) which swap automatically when html.dark is toggled.
INFO
This theme is a work in progress. If you find issues or want to contribute, check the project repository.