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:

  1. No colored background blocks for decorative arrows — separators should be colored text glyphs, not filled powerline segments
  2. IBM Oxocarbon palette only — consistent Carbon Design System colors for both dark and light modes
  3. 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: autodarklight → 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.

typescript
// 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:

css
.nf {
  font-family: 'Symbols Nerd Font Mono', monospace;
  font-size: 1em;
  line-height: 1;
  display: inline-block;
}

Then in templates: <span class="nf">&#xF07C;</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.