background image

Handling ‘insertFromPaste’ type ‘beforeInput’ events on contenteditable elements

I’ve been thinking about and messing with contenteditable a lot recently. I’ll talk about it a little generally, and then provide a snippet to handle some cross-browser weirdness when it comes to contenteditable=”plaintext-only”.

At the end of 2022 I had a solid week to myself where everyone was out of town for Christmas and I was living alone. So I spent approximately seven days in a row attempting to write my own hacky interpretation of Gutenberg.

It always comes back to Gutenberg.

It ended up in a state that I think is interesting, but not particularly useful. Here’s a screenshot of where it ended up:

Screenshot of old block editor project

It had editable text, a slash menu to add new blocks, nested layouts, toggle-able indicators for block boundaries, a tree menu that showed block nesting and allowed for selections and block actions, and saved changes to localStorage so you could continue working on things.

Most of the focus was on blocks, and block selections and actions. All of the text was handled by slapping contenteditable on block elements and calling it a day.

The model that sat behind the view was just an object with what I now think of as quite a naïve structure. But it’s a project that I’m very proud of, and was a really great way to improve my knowledge of the DOM, CSS, and JavaScript.

What am I doing now?

I didn’t look back at that project until June 2026, because as anyone who has worked with contenteditable knows – it’s a super-weird and difficult-to-work-with attribute, I was writing way too much JavaScript to handle UI stuff that CSS can do far more easily now, and life caught up and I didn’t have that kind of dedicated time to focus on the project any more.

But now things are different – contenteditable=”plaintext-only” is supported cross-browser, and CSS has a whole bunch of new features that make handling the complicated UI stuff so much easier, and without the need for any JavaScript.

The desire to make some kind of block/content editor never left me, and with all the newly-supported features I got to work researching and building things.

Progress is slow, because this time I’m much more focused on having a well-structured model and managing text selections and operations properly alongside the block ones, and that means working more closely with contenteditable and all of its quirks than I ever have before.

‘insertFromPaste’ is handled differently in Chromium

The approach I’ve been investigating is to use contenteditable=’plaintext-only’ to try and keep things as simple as possible, and handle inline formatting using ‘beforeInput’ events with custom logic.

That was until I started testing beyond just Chromium. I found that any ‘beforeInput’ events with the type ‘insertFromPaste’ didn’t work in Firefox or Safari/Webkit.

Turns out that’s because in contenteditable=’plaintext-only’ elements in Chromium, pasted text is passed as a ‘data’ property of the event, while in other browser engines it is supplied as a ‘DataTransferItem’ under event.dataTransfer.items, which is the same way pasted content is handled in contenteditable=’true’ for all engines.

So, here is the promised snippet to deal with that:

const editables = document.querySelectorAll("[contenteditable]");

editables.forEach((element) => {
  element.addEventListener("beforeinput", (e) => {
    console.log(e);

    if (e.inputType == "insertFromPaste") {
      if (e.data) {
          e.preventDefault();
        console.log("handling as data");
        console.log(e.data);
        return;
      }
      console.log("handling as transfer");

      Array.from(e.dataTransfer.items)
        .find((item) => item.type == "text/plain")
        .getAsString((thing) => {
          console.log(thing);
        });
    }
  });
});

How annoying.

Everything I’ve learned and worked on recently makes me feel both more respect and more disdain than I previously did for Gutenberg.

I mean – what an incredible job handling all of the quirks of contenteditable, truly. And also, what a squandering of that work by such poor architectural choices and CSS.

Maybe one day I’ll build something good enough to warrant having this level of negative attitude towards Gutenberg. Until then, sorry.