contenteditable + ::first-letter + Chrome = buggy combo

Published on in CSS

Last updated on

This combination causes weird behavior and data loss because of a bug in Chrome, so it's best not to use contenteditable and ::first-letter together.

Table of contents

What's contenteditable?

The contenteditable HTML attribute makes an element's contents editable in the browser.

Demo:

I'm a blockquote with the `contenteditable` attribute. Select and edit me!

For example, TinyMCE is a WYSIWYG ("what you see is what you get") HTML editor that uses the contenteditable attribute under the hood.

What's ::first-letter?

The ::first-letter CSS pseudo-element can be used to stylize "the first letter of the first line of a block-level element, but only when not preceded by other content (such as images or inline tables)."

Initial on Wikipedia:

In a written or published work, an initial or drop cap is a letter at the beginning of a word, a chapter, or a paragraph that is larger than the rest of the text.

Example:

article > p:first-child::first-letter {
  font-size: 2em;
  font-weight: bold;
}

Demo:

This paragraph has an initial / drop cap letter, meaning that the first letter ("T") should be large and bold.

This paragraph has a regular first letter.

What's Chrome?

Just kidding, I know you know. 😄

Example use case for the combination

  • Your website shows drop cap letters (::first-letter) somewhere.
  • Your website's content creators create content with TinyMCE (which uses the contenteditable attribute under the hood).
  • The TinyMCE editor has been stylized (using CSS) to show drop cap letters so that the content looks the same in the TinyMCE editor and on the live site.
  • Your website's content creators use Chrome.

That's how you end up with the buggy combination that's contenteditable + ::first-letter + Chrome.

Problems with the combination

Note: in my case I had a TinyMCE editor in which the first paragraph had a drop cap letter and other paragraphs were normal (no drop caps). In other cases the problems might be different.

Also note: the problems are not related to TinyMCE per se; it just happens to use the contenteditable attribute under the hood.

Incorrect caret position

When the caret is in the drop cap paragraph, the caret's position is one character too much to the right. This is confusing.

Update in November 2021: this seems to no longer be an issue as of Chrome 95 (though I don't know in which version the issue has been fixed).

Minor data loss

When the caret is at the end of the first paragraph, pressing Delete removes two characters:

  • the line break between the paragraphs
  • the last character of the first paragraph.

Normally only the line break between the paragraphs should be removed, so that the contents of the second paragraph are moved to the end of the first paragraph.

The same issue happens (two characters are removed instead of one) when the caret is at the beginning of the second paragraph and Backspace is pressed.

In addition to causing minor data loss, this buggy behavior is also confusing.

Extra confusion: if the drop cap covers more than one character[1], the operations above remove more than one extra character.

Major data loss

When the caret is at the end of the drop cap paragraph, pressing Backspace empties the whole paragraph! Looking at Chrome DevTools, the <p> tag is still there, but it's empty.

Normally only the last character of the paragraph should be removed.

The same issue happens (the whole paragraph is emptied) when the caret is at the second last position of the first paragraph and Delete is pressed.

In addition to causing major data loss, this buggy behavior is also confusing.

The root cause: a bug in Chrome

Long story short: this is a bug in Chrome. The "Contenteditable with :first-letter styling" Chromium issue was reported in 2013 (almost 9 years ago as of November 2021) and hasn't been fixed yet.

The fix: don't use the combination

In practice you should avoid using the contenteditable HTML attribute and the ::first-letter CSS pseudo-element together.

Avoiding the bug might not be a "fix" per se, but since the bug is in Chrome itself, there's not much else to do.

How I investigated the issue

This was a simple 30-minute investigation, but I hope that this short story will be helpful to some by showing one way of investigating these kind of bugs.

  1. I first tested what styles of the ::first-letter pseudo-element cause the problems by tweaking the styles of the buggy TinyMCE editor in Chrome DevTools. After quick fiddling, it was apparent that any single valid style causes the problems. Because of this, it looked unlikely that the issue was in the CSS styles.

  2. Thinking that this was an issue in TinyMCE, I googled for queries like "tinymce first-letter" to look for bug reports and solutions. But all I could find was one TinyMCE issue on GitHub from 2016 which was closed in 2018 without a solution. "It seems to be a problem with CSS and not TinyMCE itself" they said.

  3. Hmm, what could I try next... I was using TinyMCE version 4 and the latest was version 5. So maybe the issue had been fixed ages ago (in TinyMCE version 5) and everyone had already upgraded to version 5, so that's why no one was talking about this? Note that I was still thinking that the issue was in TinyMCE.

    Well, let's try. I remembered that there was a live demo of TinyMCE on TinyMCE's homepage (update: the demos are not anymore on the homepage as of November 2021). I added drop cap stylings to the demo using Chrome DevTools, and... Bleh, version 5 had the same problems.

  4. But wait! While tweaking the styles, I noticed that the TinyMCE demo used the contenteditable attribute. Could it be that this was not a styling issue (despite the GitHub issue suggesting otherwise) but related to the contenteditable attribute?

    I created a quick 2-minute fiddle of the contenteditable + ::first-letter combo at flems.io – and voilà! My quick fiddle had the same problems.

  5. Because the contenteditable attribute is more common than TinyMCE, I now had higher hopes of finding relevant results. When googling for "contenteditable first-letter," the first SO question that I opened had this snippet of wisdom in the top-voted answer by Marc J (thanks Marc!):

    I just noticed that there is a bug in Chrome that causes the cursor to become in the wrong spot with contenteditable and ::first-letter.

And that was it! Some fiddling, googling, and a little bit of luck.

Footnotes

  1. If a drop cap paragraph starts e.g. with the word "I'm," the drop cap covers both the letter "I" and the apostrophe. "The first letter of an element is not always trivial to identify," as mentioned on the ::first-letter page on MDN.