hugoblog@terminal — Hugo TUI

Building a Terminal UI in the Browser

2026-08-10 #tui #frontend #javascript · projects · 3 min read

The Idea

There is something deeply satisfying about terminal interfaces. They are minimal, keyboard-driven, and free from the visual noise that plagues modern web design. I wanted to bring that experience to a blog — a place where you type commands to navigate, read, and explore content.

The result is a single-page application that looks and feels like a terminal emulator, built entirely with Hugo and vanilla JavaScript.

Architecture

The core architecture is simple but effective. Hugo generates two things at build time:

  1. A JSON index of all posts embedded directly in the HTML via a <script type="application/json"> tag
  2. A template pool of all post content embedded via <template> elements

This means the entire blog — metadata and full content — lives in a single HTML file. No API calls, no network requests, no loading spinners. Everything is available instantly.

Command System

The command bar at the bottom of the screen accepts slash commands:

  • /blog — list all posts with keyboard navigation
  • /archive — posts grouped by year
  • /tags — browse by tag
  • /random — open a random post
  • /about — read the about page
  • /help — show all available commands
  • /clear — return to the welcome screen

Each command is registered in a central command registry, making it easy to add new ones.

Keyboard Navigation

One of the defining features of a TUI is keyboard-driven interaction. The implementation handles:

  • Arrow keys — navigate up and down through lists
  • Enter — open the selected item
  • Tab — autocomplete commands
  • Ctrl+key shortcuts for quick access
  • Page Up/Down — scroll through long articles

The key handler follows a priority chain: Ctrl shortcuts take precedence, then option list navigation, then article scrolling.

Theme System

The terminal supports multiple color themes through CSS custom properties. Each theme defines a palette of colors — background, foreground, accent, muted, error, and success. Themes are stored in the Hugo configuration and applied at build time via a CSS variables stylesheet.

Users can switch themes at runtime, and the selection persists in localStorage. Available themes include Claude (warm orange on dark), Matrix (green on black), Gruvbox (retro warm), and Mono (pure grayscale).

Challenges

The biggest challenge was handling Chinese text. Hugo’s built-in .Summary function uses word-boundary splitting, which breaks on CJK characters that have no spaces between words. The solution was to use plainify combined with truncate for a character-count-based summary instead.

Another tricky aspect was file:// compatibility. Hugo generates absolute paths starting with /, but when opening the HTML file directly from the filesystem, these paths break. A toRelative() utility strips the leading slash to make everything work without a server.

Performance

Since all content is embedded at build time, the initial page load is the only network request. The total size depends on the number and length of posts, but for a typical blog with 10-20 posts, the HTML file stays well under 500KB. Subsequent navigation is instant — no round trips, no rendering delays.

The keyboard handler is designed to be as responsive as possible. Event listeners are attached once at the document level, and the handler checks the current state to determine what action to take. This avoids the overhead of adding and removing listeners as views change.

What I Learned

Building a TUI in the browser taught me a lot about state management without frameworks, keyboard accessibility, and the power of Hugo’s build-time data embedding. It is a reminder that you do not always need React or Vue to build an interactive experience — sometimes vanilla JavaScript is all you need.

The project lives on as a template for future Hugo blogs. If you enjoy keyboard-driven interfaces and minimal design, give it a try.

Type /blog for the post list, or /clear to return home.

/blog /archive /about /help
Type /help for commands · Tab complete · ↑↓ history · Ctrl+L clear [theme: claude]