Simulating readOnly on Input Elements

I recently had to simulate a read-only state on an input element. I know what you're probably thinking: "just set input.readOnly = true." But hear me out.

When an input is read-only, the browser won't validate it using the constraint validation API. You know, the built-in validation that you get when you use pattern, required, minlength, maxlength, etc. It just skips it.

This behavior is per the spec, so the browser isn't wrong here. Typically, a read-only input shouldn't be validated since the user can't change its value.

But we like to do crazy things on the web, and that's where my requirement came into play. In Shoelace, I'm using a readonly <input> to compose part of the UI for a custom <select> element. This saves me a lot of code, since the select element uses the same exact styles as the input — plus I might want to take advantage of that input later on and add autocomplete or something like that.

So I initially made the input read-only, which worked fine until I realized that validation didn't work. Clearly, the way to solve this was to remove the readOnly prop and prevent key presses.

Except that's a lot harder than it should be.

Using event.preventDefault() on the input's keydown event will prevent all key presses, breaking tabbing, refreshing, and other important combinations. So then I started looking for ways to detect printable keys, which there's really no foolproof way of doing.

Finally, it dawned on me that I could temporarily make the input read only on keydown and revert it on keyup.

input.addEventListener('keydown', () => {
input.readOnly = true;
});

input.addEventListener('keydown', () => {
input.readOnly = false;
});

This works perfectly, doesn't require any hacky character checking, and doesn't block tabbing or shortcuts. 🥳

Just one other thing if you're trying to simulate a read-only state. Don't forget to prevent the cut and paste events, which can be activated via mouse!

input.addEventListener('cut', () => {
event.preventDefault();
});

input.addEventListener('paste', () => {
event.preventDefault();
});
Author avatar

About the author

Creator of Surreal CMS and other web things. Follow me for tweets about JavaScript, CSS, and web programming.