My pain building a WYSIWYG editor with contenteditable

abstract picture

Published @ 17/09/2021 by

author picture

Fatos, Founder & CEO @ answerly.io

Development

Naturally, when a text editor comes up in a discussion, creating a custom solution is not the first thing that comes to mind. So I went ahead, and I started looking for what reliable WYSIWYG editors I could use. There are quite a few ready solutions on the internet. Some are famous and used by well-known platforms such as WordPress. But many are also abandoned and are left to collect dust in GitHub repositories.

picture of the answerly answer editor

After lots of time searching and testing editors online, it turns out that we have some rather unique requirements. None of the available editors met our criteria, so I was set on an adventure to code a custom WYSIWYG editor.

In my years of frontend experience, this project gave me the wildest ride in terms of rare bugs and unexpected behavior. I've googled so hard that I ended up finding obscure WebKit bugs that aren't closed since a decade ago!

I want to clear out that I have only used modern Chrome and Firefox. Contenteditable in Safari and older browsers is another world that I will cover in future blog posts.

A custom WYSIWYG editor in 2021?

You might ask yourself, do we need an in-house solution when there are hundreds of options out there? And the answer is surprising, yes - we do!

The nature of our services asks for it; we need a custom data type to store and process the contents of a contenteditable for later use in our product line. For example, the content you create through our editor must be searchable, and also it is rendered in all of our products in very diverse styles - such as in the helpdesk widget, search widget, or our knowledge base platform.

picture of github results when searching WYSIWYG

The current solutions don't offer too much control in processing the data from the contenteditable - the output is always dirty HTML, and storing and processing HTML generated by a contenteditable in the database is a no-go.

Oh, and we also needed as many elements as possible, including custom ones which not only the current libraries didn't support - but also it's not possible to extend functionality or register new ones unless you fork the whole thing.

Our editor currently supports:

  • Bold
  • Italic
  • Underline
  • Headings
  • Bulleted List
  • URLs
  • Inline Code
  • Images
  • Table
  • Horizontal Dividers
  • Blockquote
  • Code block (with syntax highlights and multi-language support)
  • Youtube videos
  • and a few more

Starting the development

My thought process was to use the contenteditable attribute to create a straightforward editor first - by following best practices available online. The kind of editor that only supports bold, italic, and underline. And then figure out a way to extend functionality and add custom elements as we need them.

picture of the answerly answer editor

My thinking took that direction because when I see open source projects such as Firefox or Chromium, "inconsistent mess" is not the first thought that goes in my mind. I thought the current contenteditable attribute could handle such a simple editor - but I was wrong.

Pain #1: execCommand is deprecated

To make a text bold with contenteditable, you have to use the JavaScript method execCommand which, if you look up into the MDN, it's a deprecated method! And there is no other method available. It's the only one you can use to style text in a contenteditable!

picture of the answerly answer editor

Frankly, this is quite a red flag, but I decided to push forward anyway.

Many editors on the planet right now are at risk of simply stopping working in the next version of Chrome or Firefox when the browser vendor decides to drop execCommand altogether. I haven't seen any editors that don't use the execCommand method yet.

Pain #2: Backspace merge from hell

Picture this scenario: Your cursor is at the beginning of a paragraph. Just before this paragraph, there is a heading (H2), and you hit backspace. What should happen?

picture of the answerly answer editor

In layman's terms: the paragraph should join the heading. And in more technical terms: the contents of the P tag should go inside the contents of the H2 tag. But this is not what was happening when I looked in dev tools.

<!-- current HTML -->
<h2>My heading</h2>
<p>My very short paragraph.</p>
            
<!-- expected outcome -->
<h2>My headingMy very short paragraph.</h2>
            

Upon hitting backspace, the browser decides to convert the P tag into a SPAN - and then it applies some inline CSS to this SPAN to make it look like the H2 tag and then puts this new SPAN element inside the H2 tag.

<!-- actual outcome -->
<h2>My heading<span style="font-size: 20px; line-height: 25px;">My very short paragraph.</span></h2>
            

To add more to this complexity, if the paragraph has styles such as bold and italic - it will also convert the B and I elements into a SPAN and preserve the text style with inline CSS through the text-style property.

When I found out that contenteditable is committing sins with HTML elements, I questioned whether I would finish this project. But this is only pain #2, so keep reading to find out more!

Pain #3: Funny line breaks

The most well-known way to add a line break (vertical space) since Firefox 1 is by using the BR tag.

When the cursor is in the middle of a chunk of text, and you hit Enter twice - you would naturally think that chunk of text gets separated in two, and then contenteditable adds two BR tags in between.

picture of the answerly answer editor

Not quite. Contenteditable might add more than two BR tags - or even worse, it may add two P tags with extra margins to fake a line break. As an example:

<p>Line 1</p>
<p>Line 2</p>
<p>Line 3</p>
            

The situation is even more complicated when dealing with user selection. A lot of issues are found when handling pasted content - sometimes the user copied content with extra line breaks, and then paste this content into contenteditable. The result is often unclear and unexpected, which means I need to handle this situation manually.

Pain #4: User selection quirks

It turns out that different browsers have different quirks when it comes to user selection.

The most well-known example is the fact that, in Firefox, you need to use document.getSelection instead of window.getSelection. This is not well documented, and since it's an inconsistent mess, handling user selection is one of the worst and most time-consuming parts of this project.

picture of the answerly answer editor

Different quirks are quite common in older and modern browsers alike. It will take a lot of time and effort to get the user selection working as expected - and this is often one of the most overlooked problems in WYSIWYG editors.

This is one of the most challenging aspects of using contenteditable, and it has taken a lot of time and effort to get it working correctly.

Conclusion

It's safe to say that building a custom WYSIWYG editor with contenteditable is not a fun ride. It has taken a lot of time and effort to overcome the bugs and inconsistencies that come with contenteditable. However, it has also been a valuable learning experience.

If you're considering building your own WYSIWYG editor, make sure you are aware of the quirks and challenges involved. There are many well-established editors out there, and while they might not fit all your needs, they are often more reliable and easier to use than creating a custom solution from scratch.

In the end, I hope that sharing these experiences can help others who are facing similar challenges. If you have any questions or need help, feel free to reach out!