Something shifted.
It wasn't sudden. More like tectonic plates moving under the industry while everyone watched the AI hype cycle. But the evidence is hard to ignore. Claude Code now authors 4% of all public GitHub commits , 135,000 a day, doubling month over month. 69% of developers keep a terminal open at all times. OpenCode, a terminal-native AI coding agent, hit 95,000 GitHub stars in two weeks. Ghostty, a GPU-accelerated terminal emulator, went nonprofit because its creator believed the terminal mattered enough to protect from acquisition.
The terminal isn't having a nostalgia moment. It's having a platform moment.
And nobody's talking about how to design for it.
⚡ The Three Forces
Three things happened at once, and the compound effect is bigger than any of them alone.
AI agents chose the terminal. Claude Code, Codex CLI, Gemini CLI, OpenCode:
every serious AI coding tool lives in the shell. Not because terminals are trendy, but because the terminal is where execution happens. IDE extensions suggest code. Terminal agents write code, run tests, read logs, fix errors, and commit. The terminal became an AI runtime nobody intentionally designed, and it turns out to be a really good one.
Modern tooling raised the floor. A generation of Rust and Go tools quietly
replaced the Unix standard library with versions that are faster, prettier, and more intuitive. ripgrep over grep. bat over cat. eza over ls. fd over find. yazi over ranger. zoxide over cd. lazygit over raw git. atuin over Ctrl+R. And tools like ChromaCat, which turns any terminal output into animated gradient art with plasma patterns, aurora effects, and 40+ themes, proved that the terminal could be genuinely beautiful, not just functional. The terminal got a glow-up that had nothing to do with AI; it just got better as a daily environment.
Terminal emulators became premium products. Ghostty renders at 500fps with
native GPU acceleration and platform-native UI. Kitty pioneered an inline image protocol that lets terminals show things. WezTerm ships a built-in multiplexer. Rio runs on WebGPU. The modern terminal baseline is true color, font ligatures, Unicode everywhere, and rendering performance that puts some web apps to shame.
Put them together: more developers spending more time in terminals that are more capable than ever, building with frameworks that make terminal UIs genuinely enjoyable to create.
So where's the design language?
📖 The Missing Manual
Web developers have Material Design, Apple's Human Interface Guidelines, WCAG accessibility standards, and a research tradition going back decades. Mobile developers have platform-specific HIG documents, accessibility mandates, and component libraries that enforce consistency.
Terminal developers have... vibes.
I went looking for the equivalent. Here's what I found:
- clig.dev: a solid CLI design guide that explicitly excludes TUIs: "Full-screen terminal programs are niche projects; very few of us will ever be in the position to design one."
- Base16 / Tinted Theming: a color system with 230+ palettes. Covers color only. Nothing on layout, interaction, navigation, or component patterns.
- A 1983 ACM paper on terminal interface design. The last (and essentially only) academic work on the subject.
- awesome-tuis: the most-starred TUI resource list on GitHub. It's a catalog of apps. Zero design resources.
There is no HIG for terminal applications. No accessibility standard. No cross-framework design system. No academic research tradition. The closest thing is library documentation for individual frameworks, useful but framework-specific and focused on how to build, not what to build.
This gap isn't just an oversight. It's a massive opportunity. Terminals are a medium with their own affordances (information density, keyboard-first interaction, spatial memory, graceful degradation across connection quality) and they deserve design thinking that's native to those strengths, not borrowed from the web.
What follows is my attempt to start filling that gap. Not theory: lessons from building five production TUI applications across two frameworks, with a design system that spans all of them.
🏗️ Designing for 80 Columns
The first thing you learn building terminal UIs: every cell matters in a way that pixels don't. A web developer can throw a 32px margin on something and it disappears into the layout. In a terminal, a single wasted column is a percentage of your real estate. The constraint shapes everything.
🧱 Layout as Architecture
Terminal layouts aren't just arrangements; they're architectures that determine how users build mental models of your app. After building five apps and studying 23 exemplar TUIs, I've found that almost every successful terminal app falls into one of seven patterns:
Persistent Multi-Panel: Everything visible at once, panels in fixed
positions. lazygit, btop, and Unifly all use this. The magic is spatial consistency: users learn that "network traffic is top-right" and their eyes go there automatically. You never rearrange panels without explicit user action. The user's spatial memory is the navigation.
Miller Columns: Three columns showing parent, current, and preview. yazi and
ranger use this for file navigation. The insight: hierarchical data has a natural horizontal flow. You see where you came from (left), where you are (center), and where you're going (right). Elegant for anything tree-shaped.
Drill-Down Stack: Browser-like navigation into increasingly specific views.
k9s does this beautifully for Kubernetes (cluster → namespace → deployment → pod → container → logs), with :resource jumps for power users. The pattern for deep hierarchies where showing everything at once would be chaos.
Widget Dashboard: Independent, self-contained widgets in a grid. btop and
bottom take this approach for system monitoring. Each widget owns its own data lifecycle and rendering. Good when the relationship between data is "these are all about the same system" rather than "these are all about the same item."
IDE Three-Panel: Sidebar, main content, and detail/output. Iris Studio,
harlequin, and most development tools use some variant. The layout metaphor is: navigate (left), work (center), inspect (right). Tab bars give the main panel multiple personalities.
Overlay/Popup: Appears over the shell, does one thing, disappears. atuin and
fzf embody this. No state between invocations. The terminal equivalent of a modal dialog, summoned when needed, gone when done, never disrupting your scrollback.
Header + Scrollable List: Fixed header with stats, scrollable data below,
function bar at the bottom. htop and tig. The oldest pattern and still one of the most effective for any "view a list of things with summary stats" use case.
The choice isn't arbitrary. When I built Unifly (a network dashboard), persistent multi-panel was obvious: network state is best understood all at once, with your eyes learning where each metric lives. When I built Iris Studio (an AI git workflow), IDE three-panel was the right call, because you're working on one thing at a time but need navigation and context flanking the main content.
Picking the wrong layout is like picking the wrong data structure. Everything downstream gets harder.
🎯 Seven Principles
I've codified the design patterns that work across all seven layout types into principles. I won't enumerate them as a numbered list; that's not how they work in practice. Instead, they're threads that run through every decision:
Spatial consistency is the foundation. Panels don't move. Tabs stay in
order. The user builds a mental map of your app in the first minute and navigates by location memory after that. Every time you shuffle the layout, you reset their spatial model to zero.
Keyboard-first, mouse-optional means every feature is reachable without a
mouse, but mouse support isn't an afterthought either. The reason: terminal power users are keyboard people, but beginners discovering your app will click. Support both; optimize for keys.
Progressive disclosure is how you avoid the "wall of keyboard shortcuts"
problem. Three tiers: a footer bar showing the 3-5 most important keys (always visible), a ? help overlay with the full keybinding reference (on demand), and complete documentation for everything else. Beginners see the floor. Experts find the ceiling. Nobody reads a manual to get started.
Semantic color means color carries meaning, not decoration. Green means
success. Red means danger. Yellow means caution. If you stripped all color from your app and it became unusable, your design is broken. Color should reinforce information hierarchy that's already established through layout, typography, and symbols. More on this shortly.
Async everything is non-negotiable in 2026. Never freeze the UI. File
operations, network calls, AI generation: all background tasks with progress indicators. The user should always be able to press Esc and get back to a responsive interface. A TUI that hangs is a TUI that gets killed.
Contextual intelligence means your interface adapts to what the user is
doing right now. Keybindings change when focus moves between panels. The status bar reflects current state. Help shows shortcuts that are actually available in this context. The UI earns trust by always being accurate about what's possible.
Design in layers is the principle I wish someone had told me on day one.
Start with monochrome: is the app usable with no color at all? Then add 16 ANSI colors: is the hierarchy readable? Then layer in true color: is it beautiful? Each tier is independent. Your app works on a monochrome SSH session and looks stunning in Ghostty. That's not a tradeoff; it's a design discipline.
⌨️ The Vim Question
One pattern that emerged across every framework and every app I built: vim keybindings are the terminal lingua franca.
Not because every terminal user runs vim. But because j/k for up/down, h/l for left/right, / for search, ? for help, g/G for top/bottom, and Esc to go back is the most information-dense navigation vocabulary ever designed. It's six keystrokes that handle 80% of navigation. And it's muscle memory for exactly the audience that builds and uses TUIs.
I structure keyboard interaction in four layers:
-
L0 (Universal): Arrow keys, Enter, Escape,
qto quit. Shown in the footer. Anyone can use this. -
L1 (Vim motions):
j/k/h/l,/,?,:. Also shown in the footer. Terminal natives expect this. -
L2 (Actions): Single mnemonic keys:
dfor delete,sfor stage,rfor refresh. Discoverable through the?help overlay. - L3 (Power): Composed commands, macros, configuration. Documentation only. The ceiling for experts who've invested the time.
Each layer is invisible until the user reaches for it. That's progressive disclosure applied to keyboard interaction.
🎨 Color as Information Architecture
Color in a terminal is a resource, not a paintbrush. You have a constrained palette compared to the web, a wildly unpredictable rendering environment (users run every terminal emulator and theme combination imaginable), and an audience that may be looking at your app over SSH on a 16-color connection.
🌈 The Three-Tier Model
The golden rule: usable at 16 colors, beautiful at true color.
Your app encounters terminals in three capability tiers:
16 ANSI colors: The foundation. These are the colors the user's terminal
theme controls. When you say "red," the terminal decides what red looks like. This means your reds match their theme. The upside: automatic coherence. The downside: no fine control. Design with named ANSI colors and your app blends into any terminal. This is your SSH-over-a-bad-connection baseline.
256 colors: Extended palette with fixed colors. You gain control but lose
theme coherence. Your specific shade of purple will look the same on every terminal, which means it may clash with their background. Use sparingly for emphasis; don't build your entire palette here.
True color (24-bit): Full control. 16 million colors. This is where you make
it beautiful. But always remember: it's an enhancement layer over a 16-color foundation, not a replacement for one.
Detection is straightforward: check $COLORTERM for truecolor or 24bit. Check $TERM for 256color. Respect $NO_COLOR unconditionally: if it's set, strip all color. This isn't just accessibility; it's professional courtesy.
🏷️ Semantic Color Slots
The insight that changed how I think about terminal color: define colors by function, not appearance.
Instead of "this panel border is #e135ff," it's "focused panel borders use accent.primary." Instead of "errors are #ff6363," it's "errors use status.error." A semantic layer between your code and your colors.
Here's the vocabulary I use across all five apps:
- text.primary: Main body text. Off-white on dark backgrounds.
- text.muted: Secondary information, metadata, timestamps. Noticeably dimmer.
- text.emphasis: Headers, focused items. Bright, bold.
- bg.base → bg.surface → bg.overlay: Three background layers, each ~5-8% lighter. Creates depth without borders.
- accent.primary: Your brand color. Interactive elements, focused borders.
- accent.secondary: Supporting interactions. Secondary highlights.
- status.success / .warning / .error / .info: Exactly what they sound like.
- git.staged / .modified / .untracked: Domain-specific tokens for git apps.
- diff.added / .removed: Domain-specific tokens for diff views.
When your colors have semantic names, your entire app becomes theme-swappable overnight. Change the values behind the names; every screen updates instantly. I proved this across five apps with 20 different themes, same codebase, completely different personalities.
🔧 Theming as Infrastructure
This is where most TUI developers stop: they pick some hex codes, scatter them through the codebase, and ship one look. Changing anything means grepping through 50 files.
I got tired of this after the second app. So I built Opaline, a token-based theme engine for Ratatui that implements the semantic color model as actual infrastructure.
The pipeline:
Palette (raw hex colors) → Tokens (semantic names that reference palette) → Styles (composed foreground + background + modifiers) → Gradients (multi-stop color interpolation)
Each layer references the one below it. Tokens like text.primary resolve to palette entries like gray_50. Styles like keyword compose a foreground token with bold. Gradients interpolate between palette entries for progress bars and visual effects.
The result: 20 builtin themes, including five SilkCircuit variants (Neon, Soft, Glow, Vibrant, Dawn), plus Catppuccin, Dracula, Nord, Rose Pine, Gruvbox, Tokyo Night, and more. Every theme is validated against a contract test suite: 40+ tokens must be defined, 18+ styles must resolve, 5 gradients must interpolate correctly. Users can write their own themes as TOML files. Runtime switching costs nothing.
The bigger lesson isn't about Opaline specifically. It's that theming is infrastructure, the same way a design system is infrastructure for the web. If you want visual consistency across multiple apps, or you want to support user customization without chaos, you need a resolution pipeline with semantic indirection. Hex codes in source files is a phase, not a strategy.
✨ SilkCircuit: A Terminal Design Language
To make the theme system concrete, I designed SilkCircuit as a cohesive visual identity for terminal applications. Not "use my colors," but "here's what a complete terminal design language looks like."
-
Electric Purple (
#e135ff): Brand, emphasis, focus states -
Neon Cyan (
#80ffea): Interaction, file paths, tech elements -
Coral (
#ff6ac1): Accents, hashes, constants -
Electric Yellow (
#f1fa8c): Warnings, timestamps, attention -
Success Green (
#50fa7b): Confirmations, additions, online states -
Error Red (
#ff6363): Danger, deletions, offline states
Five variants prove the system works: Neon is electric and high-contrast. Soft is muted and comfortable. Glow adds bloom-like emphasis. Vibrant is saturated and bold. Dawn is a warm light theme. Same semantic slots, completely different energy. The design language is the mapping from meaning to color, not the colors themselves.
🚀 Five Apps, Two Frameworks
Theory is cheap. Here's what I actually learned by shipping.
📊 Unifly: The Dashboard
Unifly is a real-time network management dashboard for Ubiquiti UniFi controllers. Eight screens of live data: WAN traffic charts, device health, client lists, firewall rules, topology maps, event streams, historical analytics. Built in Rust with Ratatui.
The design lesson: information density is a feature, not a problem. Every
cell on screen earns its place. WAN bandwidth charts use a dual-layer technique : HalfBlock area fills for the smooth body with Braille character line overlays for the crisp edge. Traffic bars use fractional block characters (▏▎▍▌▋▊▉█) for sub-cell precision that makes terminal charts feel surprisingly smooth. Status indicators use semantic symbols: ● online, ○ offline, ◐ transitioning, ◉ pending adoption.
The architecture lesson: never poll. Unifly uses reactive streams,
tokio::watch channels that push data changes to the UI. The TUI doesn't ask "has anything changed?" on a timer. It gets told when something changes. The difference in responsiveness is visceral.
The product lesson: the dual-product pattern. Unifly ships as two binaries
from the same codebase: unifly (CLI for scripting and automation, JSON output, composable with pipes) and unifly-tui (interactive dashboard for humans). One core, two faces. The CLI lets you unifly devices --json | jq '.[] | select(.status == "offline")'. The TUI lets you explore the same data visually, drill into details, restart devices. Neither is better; they serve different workflows.
🤖 Iris Studio: The AI Workflow
Iris Studio is a six-mode AI-powered git workflow tool. Explore code semantically, generate commit messages, run code reviews, draft PR descriptions, create changelogs, write release notes, all from a three-panel TUI with a universal chat interface. Built in Rust with Ratatui.
The design lesson: modes need visual identity. Six modes could easily feel
like six apps wearing a trench coat. Consistent three-panel layout across all modes (navigate left, work center, inspect right) with mode-specific content keeps it unified. Shift+letter shortcuts for mode switching build muscle memory fast.
The architecture lesson: pure reducers make AI UIs predictable. When an AI
agent controls your UI, you need a state model you can reason about. Iris uses a Redux-style pure reducer where every state transition is a function from (state, event) → (new state, side effects). No I/O inside the reducer. Agent responses flow through the same event system as keystrokes. This makes the entire UI testable, debuggable, and auditable.
The interaction lesson: universal chat changes everything. Press / in any
mode and a chat overlay appears. Ask Iris to refine a commit message, explain a security finding in a review, or add detail to release notes, and it updates the content directly through tool calls. The AI isn't in a separate panel; it's accessible from anywhere you're working. Context follows you.
🔹 q: The Minimalist
q is a tiny Claude Code CLI built with TypeScript, Bun, and Ink (React for terminals). One letter, four modes: query (fire-and-forget questions), pipe (Unix pipeline citizen), interactive (full TUI), and agent (tool-using AI).
The design lesson: know when not to be a TUI. q's pipe mode is the
opposite of a rich interface: raw text output, no markdown formatting, no code blocks, no decoration. It's a perfect Unix filter. cat config.yaml | q "convert to json" > config.json. The discipline is in not rendering things when the context doesn't want rendering.
The framework lesson: React's mental model works in terminals. Ink maps
React's component model directly to the terminal. <Box flexDirection="column">, <Text color="cyan">, useState for state, useEffect for side effects. If you know React, you know Ink. The conceptual overhead is near zero. Claude Code itself is built on Ink, and that's not a niche endorsement.
👁️ Vigil: The Agent Orchestrator
Vigil is a PR lifecycle manager that dispatches AI agents to handle mechanical code review tasks. Card-based dashboard showing all your open PRs, six specialized agents (triage, fix, respond, rebase, evidence, learning), and a human-in-the-loop toggle that ranges from "approve every action" to "let agents run."
The design lesson: state machines need visual language. Vigil classifies PRs
into five states: hot (needs attention now), waiting (blocked on something), ready (good to merge), dormant (stale), blocked (can't proceed). Each state maps to a color, an icon, and a card style. The dashboard looks different when things are on fire versus when everything is calm. Color as information, not decoration.
The architecture lesson: the HITL/YOLO spectrum is a design decision.
Sometimes you want an agent to show you what it plans to do and wait for approval. Sometimes you want it to just handle things. The toggle between these modes is a UX feature, not a backend feature. It changes the entire interaction model of the dashboard. Building it taught me that human-AI control boundaries are UI design problems.
💎 Opaline: The Invisible One
Opaline is the theme engine underneath the other four. It doesn't have its own TUI. It is the reason the other four look cohesive.
What it taught: infrastructure is the unglamorous work that makes everything
else possible. Opaline is 20 builtin themes, a four-pass resolution pipeline, contract testing that validates every theme against a strict schema, and a ThemeSelector widget for drop-in theme pickers. Nobody sees it directly. Everyone benefits.
🛠️ Choosing Your Framework
I build in two frameworks, Ratatui (Rust) and Ink (TypeScript/React). Having shipped production apps in both, here's the actual decision guide:
Reach for Ratatui when your app is a dashboard, a monitor, or any data-heavy
view that runs for hours. Immediate-mode rendering means you describe the entire UI every frame and the framework diffs the terminal buffer for you. Zero garbage collection pauses. Runs beautifully over SSH. Netflix, AWS, and OpenAI all ship Ratatui apps in production. It's the right tool for btop-shaped problems.
Reach for Ink when your app is conversational, agent-driven, or benefits
from the npm ecosystem (syntax highlighting, markdown rendering, rich text). React's component model and hooks make state management familiar. Bun gives you fast startup and embedded SQLite. Claude Code is built on Ink. It's the right tool for chat-shaped problems.
What they share is more interesting than how they differ. Both ecosystems
converge on the same design patterns: unidirectional data flow (events → state → render), vim keybindings as the default navigation model, footer key hints with ? help overlays, semantic color systems, and action dispatch architectures. The framework is the least interesting choice you'll make. The design principles travel across both.
The real question isn't "Ratatui or Ink?" It's "what patterns does my app's data flow demand?" If you answer that well, the framework choice falls out naturally.
🎭 Playwright for Terminals
Here's the part nobody else is talking about.
AI coding agents can write TUI code all day. Claude Code, Codex, Gemini CLI: they'll generate Ratatui components, Ink React trees, Bubbletea models without breaking a sweat. But they have a fundamental problem: they're blind.
When Claude Code runs your TUI app, it gets stdout text. It cannot see the layout. It cannot verify that panel borders line up. It cannot check that j/k navigates correctly. It cannot tell if the status bar is rendering in the right color. It's building a visual interface without eyes.
This isn't a theoretical gap. Claude Code's Bash tool doesn't allocate a real PTY. Interactive programs hang. TUI apps corrupt terminal state. Gemini CLI shipped proper PTY support in October 2025; Claude Code still hasn't. The most capable AI coding agent in the world cannot interact with the category of applications we're building.
Web developers solved this problem years ago with Playwright and Cypress. The agent writes code, opens a browser, renders the page, inspects the DOM, takes screenshots, simulates interactions, and iterates. Test-driven development with eyes.
Terminals have nothing equivalent. Until now.
👻 ghostty-automator
I built ghostty-automator, a purpose-built IPC layer for Ghostty that exposes the terminal's actual state to external processes.
Not scraped text. Not regex-parsed ANSI escape sequences. Not tmux pane captures. The terminal emulator itself tells you, through structured data over a Unix socket, exactly what's on screen: every cell's character, foreground color, background color, bold/italic/underline state, and cursor position. The full semantic state of the rendered terminal.
A Python library wraps this with Playwright-style async ergonomics:
-
terminal.send("cargo run"): send a command -
terminal.wait_for_text("Listening on"): wait for specific output -
terminal.screen(): read what's on screen as text -
terminal.cells(): read styled cells with color and formatting -
terminal.screenshot(): capture a PNG -
terminal.press("KeyJ"): send keystrokes -
terminal.click(row=5, col=20): click at a position -
terminal.expect.to_contain("Dashboard"): assert content
An AI agent skill wraps the whole thing so any Claude Code agent can install terminal automation in one command:
npx skills add hyperb1iss/ghostty-automator-python
The agent gets the full API: send commands, read screens, take screenshots, click cells, assert content. No MCP server configuration, no protocol wiring. Just install and go.
🔄 The Loop
Put the whole stack together and something remarkable happens:
The AI agent has design knowledge: a 3,000-line TUI design skill covering layout paradigms, color theory, interaction patterns, accessibility requirements, and anti-patterns ranked by real-world complaint frequency.
It has theming infrastructure: Opaline, so it can work with semantic colors and swap themes without touching the layout code.
It has frameworks: Ratatui and Ink, which it already knows how to use from training data and documentation.
And now it has eyes and hands: ghostty-automator, so it can run the app in a real terminal, see the rendered output, interact with it through keystrokes and mouse events, and verify that what it built matches what it intended.
The loop closes: design → build → run → see → fix → repeat. The same workflow web developers have had for years, finally available for terminal applications.
Most terminal automation approaches parse ANSI byte streams, capture tmux panes, or run headless emulators. ghostty-automator is different: purpose-built IPC where the emulator itself participates. No parsing, no scraping, no guessing. The terminal tells you its state because you asked in its native protocol.
This is Playwright for terminals. And I think it changes what's possible.
🔮 What Comes Next
The terminal stopped being the environment you escaped from. It became the environment you returned to, because it's actually better for how serious work happens now.
AI agents made it the control plane for software development. Modern frameworks made it beautiful. A generation of Rust and Go tooling made it a pleasure to live in. And now the infrastructure exists for those same agents to build, test, and iterate on terminal interfaces autonomously.
We have design principles for a medium that never had them. We have theming systems that bring design-system rigor to the terminal. We have frameworks in multiple languages that make building TUIs genuinely enjoyable. And we have an automation layer that gives AI agents eyes.
What we need now is more people building beautiful things. The terminal is a canvas. The tools are ready. The renaissance is here.
Projects mentioned in this post:
- Opaline: Token-based theme engine for Ratatui (20 builtin themes)
- Unifly: UniFi network management CLI + TUI
- Git-Iris: AI-powered git workflow with Iris Studio TUI
- q: The tiniest Claude Code CLI
- Vigil: AI-powered PR lifecycle manager
- ghostty-automator: Terminal automation IPC for Ghostty
- ghostty-automator-python: Playwright-style Python API + AI agent skill
- ChromaCat: Terminal colorization with animated gradient patterns and 40+ themes
- SilkCircuit: Electric meets elegant — terminal design language and theme system
- tui-design skill: 3,000-line TUI design knowledge base for AI agents
Frameworks:
- Ratatui: Rust terminal UI framework (18.7K stars, used by Netflix/AWS/OpenAI)
- Ink: React for the terminal (TypeScript)
- Bubbletea: Elm architecture for Go TUIs (40K stars)
- Textual: Python TUI framework with CSS-like styling
Further reading:
- AI coding tools are shifting to the terminal, TechCrunch
- Your terminal is an AI runtime now, Adel Zaalouk
- Claude Code is the Inflection Point, SemiAnalysis
- Building a TUI Is Easy Now, Hatchet
- Learning From Terminals to Design the Future of User Interfaces, Brandur
- TUI Design, Jens Roemer
✨ Originally published at hyperbliss.tech.
💜 If you dig this kind of work, consider sponsoring me on GitHub.


Top comments (0)