How to detect an element which is editable by the user

In my extension I’d like to distinguish events having been triggered in elements that are editable by the user from those triggered elsewhere. The test should also include read-onlyness of elements, even if it is only declared by, e.g., ARIA attributes.

So far I came up with the following criterion (t being the event target)

    editable =
      ((t.isContentEditable) ||
       (t === document.activeElement)) &&
      (! ((t.getAttribute( "readonly" )?.toLowerCase() === "true") ||
          (t.ariaReadOnly) ||
          (t.getAttribute( "aria-readonly" )?.toLowerCase() === "true")))

Does that look OK? Anything else to consider?

Thanks!

1 Like

Well, I just noticed that the condition (t === document.activeElement) is too broad, of course. It yields true on pretty much every focusable element, for example, on buttons or the document body.

While it may be OK in the context of my particular extension, it seems that a general solution would need to test in addition the type of the DOM element, and for HTMLInputElement even the type attribute.

So something along the lines of:

   let edtypes = { text: 1, search: 1, url: 1, tel: 1, email: 1 }
   let editable =
     ((t.isContentEditable) ||
      (t instanceof HTMLTextAreaElement) ||
      ((t instanceof HTMLInputElement) && (t.type in edtypes))) &&
     (! ((t.getAttribute( "readonly" )?.toLowerCase() === "true") ||
         (t.ariaReadOnly) ||
         (t.getAttribute( "aria-readonly" )?.toLowerCase() === "true")))

Anything more straight-forward available?

1 Like

Are you only interested in contentEditable? If so, you’d also probably want to check the contenteditable state of ancestors, since it also applies to descendants.

What about other elements, such as those typically associated with forms (input, textarea, select, etc.)? There a decent number of builtin elements you’ll want consider in addition to the input types you mentioned. See the “other elements that are used when creating forms” list on MDN’s element.

What about non-standard UI elements provided by libraries such as comboboxes or date pickers? These are often implemented using a variety of JavaScript event listeners and DOM mutations. If the creator of the UI element is responsible, you may be able to lean on ARIA attributes to detect and handle these updates. That said, this kind of support can be spotty – I’ve seen my fair share of implementations that are impractical for other code on the page to detect and respond to.

Thanks for your reply.

And sorry for not being more precise in my question - I have been skipping over one important restriction: I’m only interested in elements that allow text to be entered/modified by the user.

I thought using isContentEditable on an element would care appropriately about the ancestors’ setting of contentEditable? At least my tests seem to confirm that …

Will check, thanks for the pointer.

Uh, I haven’t thought about anything like that, TBH. I hope there isn’t much among these that allows text entry and will continue to blissfully ignore these.

So it does! Don’t mind me. I think when I first read your code I somehow converted isContentEditable in my brain into checking if the contenteditable attribute was set on the target element.

Hopefully I’m just being a crotchety old man and warning against the way we used to write web UI back in my day. This is the kind of thing I recall seeing a lot of around 2010. Hopefully these days sites are at least pulling in well written components and you won’t have to worry about all that.

I don’t know if it’s still of interest, but I came across your thread while I was trying to figure out the same thing. And I did eventually come up with a solution, which turned out surprisingly simple in the end:

if(
    document.activeElement.readOnly === false 
    || 
    document.activeElement.contentEditable === 'true'
) 
{ /* focused element is editable */ }

btw. aria-readonly doesn’t make a field readonly, it only declares that it’s going to be; the readonly behavior itself has to be scripted, and that’s not directly detectable (unless you wrote the script).

I also ran across this thread while trying to figure this out! I made two tweaks to your solution.

The first is using event.target rather than document.activeElement, which in my testing was always the same thing, and to me reads a bit better - but i would be interested to hear reasons why this might not work.

The second was to write the second clause so:

target.isContentEditable

Firstly, this correctly handles inherited content editability, and secondly, the HTML standard now defines a “plaintext-only” value for contentEditable, and you want to catch that as well.

Yeah you’re spot on with the second change, I came to the same conclusion after more extensive testing.

The use of document.activeElement is because I needed to be able to test it from anywhere, whereas event.target limits it to use inside a focus or focusin event.

(And yes – within a focus event listener, the event target is always the same as activeElement)