How do I make Tab and Enter behave differently when the completion popup menu is open?
Answer
pumvisible()
Explanation
The pumvisible() function returns 1 when the insert-mode completion popup menu (pum) is visible, and 0 otherwise. Combined with <expr> mappings, it lets you create context-aware keys: <Tab> cycles through completions when the menu is open, but inserts a literal tab otherwise; <CR> confirms a completion or inserts a newline.
How it works
Add these <expr> mappings to your vimrc / init.vim:
inoremap <expr> <Tab> pumvisible() ? "\<C-n>" : "\<Tab>"
inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"
inoremap <expr> <CR> pumvisible() ? "\<C-y>" : "\<CR>"
Key bindings used:
<C-n>— move to the next completion item<C-p>— move to the previous completion item<C-y>— accept the currently highlighted completion<C-e>— cancel the popup without selecting (useful as an escape hatch)
Example
Typing sys and pressing <C-n> opens the completion popup. Now:
<Tab>cycles forward,<S-Tab>cycles backward<CR>accepts and exits insert mode cleanly- Outside the popup, these keys behave normally
Tips
pumvisible()works with both<C-n>/<C-p>keyword completion and<C-x>sub-mode completions- For Neovim with a completion plugin (nvim-cmp, blink.cmp), the plugin handles this automatically — check its docs before adding manual mappings
- If you want
<Tab>to also trigger completion when the menu is closed, add:pumvisible() ? "\<C-n>" : "\<C-n>"but note this opens the menu unconditionally