The most surprising thing about Zsh’s vi mode is how it fundamentally changes your relationship with the command line, transforming it from a linear text editor into a powerful, modal editing environment.

Let’s see it in action. Imagine you’re typing a command, maybe kubectl get pods -n production.

kubectl get pods -n production

Now, you realize you forgot to add --all-namespaces. Instead of reaching for the arrow keys or Ctrl+A to go to the beginning, you hit Esc to enter normal mode. Then, you press i to go back into insert mode before the n.

kubectl get pods -n production

(Hit Esc, then i)

kubectl get pods -n production

Now you’re back in insert mode, right where you want to be.

But vi mode is much more than just editing text. It’s about efficient navigation and manipulation. Let’s say you have a long command history. You want to find that docker-compose up -d command you ran last week. Instead of endlessly pressing Ctrl+R, you hit Esc to enter normal mode, then /docker-compose and Enter. Boom, there it is. You can then press n to cycle through other matches.

Here’s how Zsh’s vi mode builds its mental model:

  1. Modal Editing: The core concept is borrowing from Vim. You’re not always in "insert mode" where typing inserts characters. You have "normal mode" (accessed by Esc) where keys are commands, and "insert mode" (accessed by i, a, o, etc.) where typing inserts characters. This separation allows for much more powerful and less error-prone editing.

  2. Command Line as a Buffer: Zsh treats the current command line as a buffer, just like in Vim. This means all of Vim’s motion commands (h, j, k, l, w, b, 0, $, ^, G, gg) and text manipulation commands (d for delete, c for change, y for yank, p for paste, . for repeat) become available.

  3. History as a Buffer (of sorts): While not a direct buffer, your command history behaves like one. Commands like / (search) and ? (reverse search) let you navigate it with vi-like precision. Ctrl+P and Ctrl+N also work for previous/next command, but Esc followed by k or j can also be used if configured.

  4. Key Bindings and Configuration: You enable vi mode with bindkey -v. You can customize it extensively. For example, bindkey '^[' vi-insert ensures that Esc always puts you in insert mode, which is often what people expect. Many users also map j and k in normal mode to navigate history:

    bindkey ' ' vi-cmd       # Space in insert mode goes to normal mode
    bindkey '^[k' up-line-or-history # Use Esc-k to go up history
    bindkey '^[j' down-line-or-history # Use Esc-j to go down history
    

    This is configured in your .zshrc.

Let’s say you’ve typed a long command and want to delete everything up to the first slash. In insert mode, you’d have to backspace a lot. In vi mode: Esc (normal mode), f/ (move to the next /), d (delete) then 0 (to the beginning of the line). The entire command up to that point is deleted.

The true power comes from combining these commands. For instance, if you want to change the word production to staging in kubectl get pods -n production, you can: Esc (normal mode) w (move to production) ciw (change inner word) Then type staging.

One common point of confusion is the difference between vi-cmd and vi-insert. When you type bindkey -v, Zsh sets up the default vi mode bindings. vi-insert is the mode where typing inserts text, and vi-cmd is the mode where keys are commands. The Esc key is the primary way to switch from vi-insert to vi-cmd. If you’ve been typing and hit Esc twice, the second Esc usually doesn’t do anything because you’re already in vi-cmd. Conversely, pressing i or a in vi-cmd switches you to vi-insert.

Once you’re comfortable, you’ll find yourself editing commands with a speed and precision that feels alien to traditional shell editing. You’ll start thinking in terms of motions and edits, not just typing and backspacing.

The next natural step is to explore more advanced Vim text objects and operators within Zsh, like dit (delete inner tag) or yap (yank around parenthesis), and how they can be applied to your shell commands.

Want structured learning?

Take the full Zsh course →