The web of dependencies behind a “modern” terminal

A reader recently told me that getting a modern terminal experience feels like it requires too many pieces that should “just work” out of the box. My first instinct was to disagree — but the more I enumerated what I’d want in a setup, the longer the list got. So I started writing down what “modern” means to me and which layer of the system is responsible for each piece.

What counts as “modern”

The features I’d genuinely miss break down roughly like this:

  • Multiline paste safety — pasting three commands shouldn’t execute them all immediately. That’s a shell and terminal emulator job.
  • Unbounded shell history — commands saved forever, and written to disk the moment you run them, not when you exit the session. Shell responsibility.
  • Prompt with context — current directory and git branch visible at all times. Shell.
  • 24-bit colour — makes theming an editor like neovim dramatically easier. Terminal emulator.
  • System clipboard integration — copy in Firefox, paste with p in vim. Editor, possibly with OS/terminal help.
  • Command-specific autocomplete — git and friends should complete sensibly. Shell.
  • Coloured ls output — shell config.
  • A theme that matches — terminal and editor should look coherent. Both.
  • Self-healing terminal — stray escape codes shouldn’t permanently garble your display. Shell.
  • Sane keybindingsCtrl+Left Arrow should navigate words. Shell or application.
  • Mouse wheel in pagers — scrolling through less shouldn’t require keyboard gymnastics. Terminal emulator and application.

My own recipe

My personal setup is deliberately light:

  1. Use fish as the shell, mostly unconfigured beyond setting EDITOR and aliasing ls to ls --color=auto.
  2. Use any terminal emulator with 24-bit colour — GNOME Terminal, Terminator and iTerm have all served at various points. Font choice is my only real configuration.
  3. Use neovim, with a config I’ve been refining for roughly nine years.
  4. Use the base16 framework to keep themes consistent across tools.

That approach reflects a few personal constraints. I rarely SSH into other machines, I don’t mind reaching for the mouse occasionally, and I work across many small projects rather than one large monorepo.

Out-of-the-box options for the impatient

If you don’t want to spend a decade tuning vim, there are reasonable defaults:

  • Shell: fish, or zsh with oh-my-zsh.
  • Terminal emulator: anything with 24-bit colour. Popular choices include GNOME Terminal, Konsole, Terminator or xfce4-terminal on Linux; iTerm on macOS — Terminal.app notably lacks 256-colour support; and cross-platform options like kitty, alacritty, wezterm and ghostty.
  • Shell config: set EDITOR to your preferred terminal editor; alias ls to ls --color=auto.
  • Text editor: this is the hard one. micro and helix both look promising — micro understands conventional GUI shortcuts like Ctrl-C and Ctrl-V. I haven’t used either seriously; helix lacks a GUI and plugin ecosystem, and retraining vim muscle memory feels too costly.

I’d steer clear of xterm, rxvt and Terminal.app — they’re missing features that make daily terminal work harder than necessary. But I don’t want to pretend this is easy. Two problems in particular make it harder than it should be.

Problem one: the default shells are dated

bash and zsh dominate usage, yet neither gives a great default experience. You have to customise the prompt, git completions aren’t preconfigured, and history defaults are anemic — bash stores only 500 lines by default, and macOS’s zsh ships with 2000. Bash’s tab completion is also frustrating: with multiple matches you can’t cycle through them.

fish fixes much of this out of the box, which is why I love it. But its non-POSIX nature makes it a genuinely hard sell for many people. You can, of course, customise a bash prompt — something like export PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' or adopt starship — but every small fix adds up, especially when you need configs to stay in sync across machines.

oh-my-zsh is the most popular answer to this problem, and many people use it happily. For me, though, configuration frameworks are a debugging liability. The base oh-my-zsh install adds around 3000 lines of config, and when something breaks I find it harder to reason about. My tendency is to accumulate plugins, watch everything slow down, and eventually throw it all away and start clean.

Problem two: the editor divide

The terminal survey I ran showed vim, emacs and nano as the clear favourites. The realistic options are:

  • Use vim or emacs and invest the time to configure it — you can get almost anything you want, eventually.
  • Use nano and accept its limits — selecting and cutting text with the mouse isn’t supported, for example.
  • Use micro or helix for a genuinely good out-of-the-box experience, accepting that less mainstream tools occasionally hit rough edges.
  • Avoid terminal editors where possible — VSCode’s integrated terminal covers a lot of ground, and many people set code as their EDITOR.

Problem three: individual applications fight back

Even with the shell and editor sorted, individual programs can sabotage the experience. On macOS, /usr/bin/sqlite3 has no Ctrl+Left Arrow support because it lacks GNU readline. Fixing that meant understanding why macOS ships without GNU tools, installing sqlite from Homebrew for readline support, then adjusting my PATH so Homebrew’s version wins.

Debugging this sort of thing rarely feels worth the time, and the only reason I cracked it was that I spend enormous time thinking about terminals. A practical shortcut is to use newer programs — htop supports mouse-driven column sorting where top requires learning obscure shortcuts — but discovering modern tools takes effort, and SSH sessions won’t always have them available.

Everything is entangled

Changing one piece of the setup destabilises others in surprising ways. I currently don’t use tmux, but if I returned to it — say, for heavy SSH work — I’d have to think through questions like whether my terminal supports OSC 52 so tmux can sync its clipboard with the system one over SSH. And if I wanted iTerm’s tmux integration, I’d need to change how colours are managed: I currently set them through a shell script at startup, but that colour state is lost when session restoration rewrites the terminal.

These dependencies are often invisible until they break. If tmux-to-system clipboard copy has always “just worked”, you can’t attribute it to OSC 52 support in your current terminal — until you switch terminals and it silently stops. The feature you rely on can be the one you never know you need.

Change incrementally

My current setup looks uncomplicated, but it took about 20 years to reach. Because terminal configuration changes have such opaque consequences, overhauling everything at once makes it near impossible to diagnose what went wrong.

I prefer small changes and accept that adaptation might be slow. I switched from ls to eza a year or two ago — I appreciate that eza -l defaults to human-readable file sizes — but I still feel tentative about it. Occasionally a big change is worth it: my move from bash to fish a decade ago is one I’ve never regretted.

The honest conclusion is that a modern terminal experience is not replicable through one universal config. There’s no single arrangement compatible with every tool combination. You experiment, find a locally stable setup that works, and accept that adopting something new may force you to rethink pieces you thought were settled.