Vimium vs Escape Key: The Missing keyDown Event

Few days ago I sat down to add title editing functionality on a toy project I work from time to time. The idea was to allow editing the title when user clicks on it. The simplified code looks something like this:

import { useState } from "react"

interface Props {
children: string
onSave: () => void
}

export function Title({ children, onSave }: Props) {
const [text, setText] = useState(children)
const [isEditing, setIsEditing] = useState(false)

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
setIsEditing(false)
onSave(text)
}

if (e.key === "Escape" || e.key == "Esc") {
setText(children)
setIsEditing(false)
}
}

if (isEditing) {
return (
<input type="text" value={text} onChange={(e) => setText(e.target.value)} onKeyDown={handleKeyDown} autoFocus />
)
} else {
return <div onClick={() => setIsEditing(true)}>{text}</div>
}
}

Simple enough, right? Well, it didn’t work.

The Issue

Clicking on title would enter an edit mode as expected. However, after clicking Escape key the changes would not be discarded (which should be done by setText(children) line). The focus on input field would be lost, but the Escape key down event would never reach the code.

For the longest time I was completely flabbergasted. It did not make any sense. I went through documentation, StackOverflow, even tried to wring the information out of Claude. Everything indicated the code should work fine.

Eventually and by a complete accident I found some forum post discussing the same issue. The suggestion was to disable Vimium extension, as it seemed to be the culprit. As it happens, I’ve been using Vimium for a few years now. I’m so used to it that I often forget it doesn’t come out of the box with a browser. The solution looked plausible, as Vimium interacts with keyboard events quite heavily.

The Solution

Once I disabled the extension, everything started to work just fine. It was a huge relief, as I’ve spent at least an hour bashing my head against the issue.

The issue, however, is that I don’t want to disable Vimium. Can we do better?

Well, kind of. Vimium uses Escape key to exit insert mode. It swallows the event without passing it down to the web page. That is why the code above does not work.

We can fix it for ourselves by going to Vimium’s options, and adding the following line to Custom key mappings:

map <c-]> passNextKey

With this setting, you need to hold down Ctrl and press ]. Now, any key you press afterwards will be passed down to the web page, including Escape.

The solution is far from ideal:

  • The Ctrl + ] combination is awkward and not particularly elegant. You need to know you’ll have issues with some web page element, and you need to remember the combination to avoid the issues.
  • User must add this mapping, meaning that the fix must be manually applied by every site visitor that uses Vimium. I suspect that the chance of a user immediately realizing that Vimium is to blame is close to zero. More likely scenario is that your website will get blamed for being broken.

Takeaway

Regardless of the solution you chose, the takeaway is that we should not rely on keyboard events alone to manage site’s functionality. For one thing, not all users are comfortable with interacting with your site in this way. For the other, browser extensions can break this functionality. Thus, it’s best to have an alternative way to achieve the same result for those users who can’t/won’t/don’t know how to circumvent similar issues.

Sources

  1. https://github.com/philc/vimium/wiki/Tips-and-Tricks#using-the-escape-key-in-inputs
  2. https://github.com/philc/vimium/issues/2889