Hacker News new | past | comments | ask | show | jobs | submit login
Why ContentEditable Is Terrible, Or: How the Medium Editor Works (2014) (medium.com/medium-eng)
155 points by rfreytag on April 13, 2016 | hide | past | favorite | 76 comments

This article was very inspirational for me when developing the CMS for a previous startup and informed that development extensively.

Personally, I found the most beneficial thing to be abandoning HTML entirely. Instead I developed a JSON data structure for documents which was stored internally. This is very powerful for a couple of reasons:

1. Any user input can be mapped as pure functions on top of your internal document structure. This way you can also enforce business rules easily. Additionally, any function which you don't support will never occur. (No more Word garbage!) ContentEditable works fine as an input though.

2. You can write functionally pure mappings from your internal structure to other formats. This of course includes HTML, but isn't exclusive to HTML. Content can also be mapped into native mobile components as well.

3. You can be flexible with input types. For example, it was fairly trivial to (for fun) add a Markdown mode which would map any Markdown input onto the internal document representation and then back to Markdown.

4. You can introduce new semantic concepts which HTML doesn't have elements for, like a "quiz." This makes it easy to rejigger the display of such elements later on, or to have the same semantic element render completely differently on mobile and desktop (for example). With HTML, you're stuck doing error-prone tree parsing or Regexes.

Were you able to handle paste-from-word? If so, would you mind sharing that bit of code on github? I would love to make a custom solution, but 90% of the content being put into our CMS is copied from Word. To clean it effectively, I resort to using CKEditor to clean it for me and then storing as HTML. I'd love to not do that, but CKEditor seems to have figured out all the problems, especially when pasting tables or adjacently aligned content.

My users won't learn markdown and expect it to just "work". For complex Word documents, I never came up with a viable solution to parse it.

Yes, we were able to handle pasting from Word. We basically gathered a ton of examples of the HTML which Word generates and then developed mappings from that HTML to the actions we supported. TDD and unit testing made it fairly efficient.

Keep in mind that we purposefully did not support the full scope of HTML which Word would output. Authors couldn't make their headlines 50pt Comic Sans in red, for example.

I'd love to share the code, but unfortunately it's from an old employer and I no longer have access (or the right to share the code).

I'm interested in what kinds of pressures made you consider developing an in-house CMS the best option? When does one make that choice?

We were a media company so our CMS was our core technology. We wanted to do some interesting things with built-in analytics and automatic a/b testing which would have required extensive plugins and modifications to WordPress (our primary alternative). It also seemed unlikely that we would be able to find very many good developers (the kind we'd need to build some of the analytics technologies we were considering) who were enthusiastic about developing on WordPress.

In retrospect, it was probably the wrong call. While we made created some unique products and technology that I'm still proud of, it was challenging to get writers on board with some of the newer things (like automatic A/B testing of headlines). It was very expensive to build and maintain a technology team and ultimately without editorial buy-in the returns just weren't there (though our marketer loved it).

My last move before leaving the startup was to transition everything to WordPress.

So you did this work for basically nothing?

Atlassian does something similar for Confluence, for presumably the same reasons.


On the basis that browser based WYSIWYG editors have become de-rigour in almost all big online publishing platforms, it surprises me that the browsers themselves are not being updated to provide a standard input field (standardised via w3c of course) whose 'value' attribute outputs basic HTML mark-up. Something along the lines of:

  <input type="editor" supports="bold;italic;images;headings"  ... >
This would remove the massive amount of work that UI developers still have to do to hand crank something as simple as a good editor that GUI OSs have enjoyed for decades.

If the browsers incorporate complex editors, they're likely to be subtly or even wildly different from each other in behavior. What's the "correct" behavior after hinting enter in an unordered item list, create a new bullet or start a new line for the current one? Is there a popup to create links when you highlight text? Can a heading be split over multiple lines? Some of the answers depend on the constraints of your underlying storage format like Markdown vs HTML. On the other hand, if I pick ProseMirror or Draft.js, I can be reasonably secure in that the behavior is consistent for my users.

If a capability can be implemented outside of the core browser, I think it's a good idea to do so. It's sort of like putting something into the Linux kernel vs doing it in a userspace module. The kernel/browser should provide the low-level hooks for userspace to innovate, not try to incorporate userspace components. An external project can iterate more rapidly, and there's also a lower barrier for people to contribute code and fixes. Duplicate projects "waste" resources, but they're also competing with each other and that improves quality in the long run.

Ideally, your CMS shouldn't even store HTML at all. It's not a pure system and makes changes hard to make in the future.

You're much better off storing an internal representation of documents and then outputting that as HTML where necessary.

In addition to the large requirement set that would have to be decided on that was mentioned elsewhere, it would probably be held back given the large hurdle involved in securing such a field. I'd imagine most of the teams would be apprehensive to be the first try to tackle that. Especially given that it will end up being reimplemented a bunch of times across browser platforms. It just sounds like introducing a large new attack surface.

Good idea... but I'm not sure the "supports" attribute would be friendly to configure, and standardisation would be hard.

<input type="editor" supports="images;links;headings" spans="italic;bold;blinky" extras="carousel" oncarousel="function (event) {return document.createElement('div') /* TODO: implement carousel */; }">

With "supports" being common things with extra markup (especially images and links), spans deferring your problems to css classes, and extras deferring your problems to javascript. The element returned by an extra would always be treated as a single black box by the editor.

> supports="headings"

* Does it allow further markup inside the headings? If so, which tags? strong? emph? span? a?

* Which attributes are allowed on the <h1> etc. tags? class? style? name? id? lang? dir? id?

You see where this is going.

> spans="blinky"

Hell no.

> You see where this is going.

Yes: the wrong direction.

Trying to support every possible thing obviously won't work - we can't replace writing HTML with a clever enough rich text field. But you can still make progress on web usability by solving the most obvious bits and adding some hooks for the easiest ways to extend it.

It looks like you'd define your own DTD, to be a subset of HTML.

>(standardised via w3c of course)

Why? They are irrelevant for that now. It would be standardised through the WHATWG.

And WHATWG is more relevant? Have you ever heard of Chrome? Whenever WHATWG decides on something different than what the Chrome devs wanted, they implement their solution anyway and ignore the decision.

Anyone trying to develop for the web constantly feels the pain of trying to get something working everywhere.

Wow, that would be sweet!

Yup. Wysiwygs are hard. But just saying "You can edit paragraphs, that's all you get" is worse. Clients need to be able to do more advanced things in an editor, and coding a hard limit on never being able to do them feels like pure folly.

Is TinyMCE perfect? Far from it. But it's stable and reliable across browsers, with a well-defined API, and it gets the job done.

I know a lot of folks here would just as soon just use Markdown for all content writing. I like Markdown. Clients hate it. It feels like an obscure language, when they just want something "like Microsoft Word".

And CMS's can hardly say "Yeah, we know we used to support these greater markup sets, but now we're just not going to, so don't open up your old posts, or they're going to lose some content in our bright shiny new editor" -- that's massively problematic as well for user trust.

If you're starting something new, and want to place a hard cap on what your users can do markup-wise, sure, give this a shot. But be very aware of the restrictions that you're imposing.

I hate markdown. I always use a single line break to indicate a single line break. Having to use two breaks the most basic expectation of editing text.

> Having to use two breaks the most basic expectation of editing text.

You've never used LaTeX then. I almost always put a line break after a full stop when editing in plain text, because it adds semantic meaning.

Moreover, many single-spaced visual styles have unindented paragraphs with an extra line between paragraphs.

Two Markdown line breaks don't translate to a single line break in the output but to a paragraph break.

(To output a single line break, precede your source line break with two spaces. As others have pointed out, these behaviors are to let you manually wrap your source but have the output be auto-wrapped.)

> As others have pointed out, these behaviors are to let you manually wrap your source but have the output be auto-wrapped

Ah. That makes sense finally. However - I've never needed this ability whilst I constantly suffer from the non-intuitive default behaviour. Cure worse than disease?

It makes more sense in an environment where there's no automatic line wrapping. Breaks are a good way of maintaining readability in these environments, and you don't want those breaks to cause paragraph breaks in the rendered text.

In danger of being redundant:

> I always use a single line break to indicate a single line break.

Markdown isn't all that different from Word in this respect: hard linebreaks are second class citizens. In word, a single <enter> inserts a paragraph break, while <shift-enter> inserts a linebreak. In markdown a single linebreak inserts an (optional) space, while "<space><space><enter>" is translated to a hard linebreak.

It is very rarely that you need linebreaks in article/body text (eg: a book, newspaper article). One exception might be for dialogue:

   - "I don't know.", said Tom.
   - "Me neither.", I replied.
You'd probably want a break after the periods, no matter if the column broke up on eg: the comma or in the middle of a longer quote. This is all very different with source code, obviously.

It's a trade-off: Are most plain documents somewhat sloppily formatted wrt. paragraph/line breaks? I think this is probably true. And for natural language text, what you usually want for good layout, is soft word wrap. Lack of good word-splitting (hyphenation) for <p>-text in browsers have probably set back web layout 20 years -- and chrome is still a hold-out: https://bugs.chromium.org/p/chromium/issues/detail?id=47083

I don't use Word. I use normal text editors and plain text emails. So when I write text I often break it up into smaller parts. I write things like:

>argumentative essays

>off the cuff

pick one

if the quote is on the same line (like it is in Markdown formatting) the effect of a "choice" is lost, which is why I have to put two line breaks after it

Markdown isn't plain text, however - while annoying, you could achieve the desired formatting with markdown both in plain "unrendered" mode, and in eg: html, by appending "<space><space><enter>" for forced linebreaks. There's no equivalent AFAIK for hn markup, you'd have to use a pre-block:

  - pest
  - cholera
  pick one.
I believe the choice to translate single-enter to a space that can be reflowed is actually based on among other things email-formatting: https://tools.ietf.org/html/rfc2646#section-4

I suppose the reasoning for not sticking with "<space><cr><lf>" for soft breaks, and "<cr><lf>" for hard breaks is that plain text on *nix uses just <lf>. So either most people would get ugly <cr>-marks and/or inconsistent <cr><lf>s in their README.md-files, or something other than rfc2646 was needed.

Do you mean you don't like

    Continuation of Line1
If you're wrapping text, how else would you be able to do it?

Hard-wrapping text probably isn't something most people should be doing in most cases, especially for text documents. Computers are generally at least OK at wrapping automatically for display or editing - we've had code for it almost as long as we've had GUIs.

It's certainly not a concern for many of the places Markdown gets used - online systems where the primary form of entry is a textarea. Therefore, those places should likely not use Markdown.

What about something like Jekyll and text being edited in vim or emacs? There are many people, myself included, that prefer hard breaks for a variety of reasons.

Just because something doesn't fit your use-case doesn't mean it's not common.

There's a point to be made that if vim and emacs can't do soft line wrapping, perhaps people should be using tools that can provide basic modern conveniences like that instead of affecting the rest of us.

> vim and emacs can't do soft line wrapping

I never said they couldn't and they most certainly can (I simply find it awkward to work with). I've simply seen it more common to have them hardwrap than it is to see hardwrapping in a GUI editor.

And if Vim or Emacs couldn't do soft line wrapping, you'd have a point.

I use Vim to wdit markdown and have no problems editing long soft-wrapped lines. Should I?

You can edit with soft-wraps, I just don't like to. (I find it awkward to work with as line-movment is in lines, not soft-lines).

Also, when mixing code, you may want markdown in comments for better formatting of autogenerated docs, and you don't want softwrapping on while code (at least I don't).

I'm not sure what you mean about there being hard limits in place. While the implementation discussed here deals exclusively with paragraphs, the ideas are extensible to far more.

Quill, for example, is pretty feature-rich and has a similar model to that described here.[0]

The core idea is simple: forget about HTML, instead have your own data structure which represents a document in a saner way. Implement a series of functionally pure actions on that data structure and then map UI activity onto those actions (potentially using ContentEditable fields as an input). Then, React-style, your HTML is a pure function of the internal document structure.

I used a similar technique to build a rich text editor at a startup and we definitely didn't encounter anything which couldn't be done, including fairly complex structures (ex. quizzes with photo options and captions.). It definitely worked well and was a lot less error-prone and hacky than TinyMCE.

That being said, it was also a 6 month effort to get a full feature set in place. If you don't have that kind of time and just want something that works (and you don't need to extend it much), TinyMCE is fine.

Agreed that Markdown is a non-starter for almost anyone non-technical.

[0] https://github.com/quilljs/quill

I'm technical and Markdown enrages me almost daily.

TinyMCE is great. I've worked on multiple projects where this editor is being used and IMHO it is one of the easiest way to get out of the WYSIWYG Content-editable situation.

The source code [1] is modular and easily readable, so you can make yourself a decent plugin and add it to your code.

For some a blocker might be GNU license, but overall I think it's a great choice saving developer-hours.

1 : https://github.com/tinymce/tinymce/tree/master/js/tinymce/cl...

Facebook's draft.js[1] tries to solve a lot of these problems and uses React under the hood to maintain a mapping between the model of the document that you are editing and the DOM.

I have only played with it, not used it in production, but if you are using React it could be a good solution.

The one limitation I've seen is that is doesn't really support documents that need a hierarchical model (an example of this would be tables, but also things like "I want a code block within a blockquote").

If you want to see draft.js's document model, I made a tool to do that, too[2].

[1]: https://facebook.github.io/draft-js/ [2]: http://afiedler.github.io/draft-js-dm-demo/

One big problem with draft.js is it doesn't play well with tools like grammarly that reach into the dom and modify it.

Medium's editor side steps this by just rerendering after each input.

Draft.js does (or did at some point) have some affordances for this so that it could handle mobile devices.

Keyboards on Android Chrome used to emit keycode 0 for every character that was inserted and then mutate the DOM directly. And spelling corrections just mutated he DOM without firing events.

There are a bunch of bugs immediately apparent. Try typing while making a selection with mouse. Is that expected behaviour? nah.

What is the "expected behavior" for simultaneously drag-selecting text and typing? And why? And who decided that?

I've found that there are many edge cases in rich content-editing. If your general-purpose editor does something reasonable and constructive with esoteric inputs it's fine.

Draft.js appears to cancel the selection process and drops the typed character wherever the mouse happens to be. Reasonable.

What? This should work properly. If it doesn't, please file a detailed bug with info about your browser.

how is it that no-one wants to implement table support nowadays?

My attempt at implementing what medium describes: https://github.com/NickStefan/tome-editor

My favorite idea was using independent ranges data objects to track styling. Eg {start: 2, end: 3, range: color, value: blue}. Then only the rendering api needs to worry about overlapping ranges. Basically avoids manipulating trees. That's partly what makes content editable suck so much.

Could you publish an online demo, even a gif if you don't want to host one? You're probably missing out on quite a few users who are too lazy to clone and install, just to bookmark for later use.

I've been meaning to do that! I'll definitely move it up the priority list.

I had intended the api to be entirely programmatic and allow others to build GUIs for how they wanted, say, font size drop downs to look. I could host the test html page I've been testing it with. It exposes some random styling buttons and visualizes the data model below the rendered text.

I would like to see a comparison between

Trix : https://github.com/basecamp/trix (From Basecamp)

ProseMirror : https://prosemirror.net/

Draft.js : https://facebook.github.io/draft-js/

They all use contenteditable for I/O only (no execCommand), and they all maintain their own internal document model.

Which is why the interesting part of the comparison would focus on where they differ.

I once used ContentEditable combined with Angular JS. It was the single worst UX development experience in my life. What a terrible control. I was trying to make an interface that binded an array of models, which had individual properties that bound to a properties panel on the side. Editing the property panel would instantly update the text, and updating the text would update the property panel. (the way i'm describing it you might wonder why do you need it in two places, but i assure you it made a bunch of sense... and was a super cool idea).

I ran into the problem though that if you accidentally deleted the text, but than tried to write it again all the bindings instantly broke. Or conversely you might start writing some text with the intent of it being outside the binded area, but it would still be inside... so the analysis that set the property panel control didn't work as expected.

I eventually got something that worked... but it was far from the original vision. I never really did figure out how to gracefully work with the control.

Frankly, it's not surprising that what you describe was difficult. Very few libraries would have been designed with that sort of "updates in both directions" architecture in mind.

It could me possible if contentEditable had events that corresponded to edits that were about to happen. Instead it just emits the raw interactions (e.g. keypress) and then mutates the DOM. It's very easy to get a mutation cannot be retroactively mapped to an edit.

I assume you've seen this: https://github.com/yabwe/medium-editor

It uses ContentEditable. Wonder what your thoughts are?

The medium-editor project is pretty good. It's not a "true" clone of the medium editor, but it gets development up and going pretty quickly with a contentEditable editor. We used medium-editor for http://www.doctant.com.

As others have noted, it's really tough to compete with text editing/rich text applications (e.g., Sublime, Vim, Word, etc.) on the web. So far, the real Medium is the only app that I've used that isn't a pain to write in even though it's contentEditable.

Another promising project is draftjs from Facebook. Check it out: https://facebook.github.io/draft-js/.

I made an extensive prototype with it and definitely do not recommend it.

It's really just a UI clone of the Medium editor. It doesn't work at all the same way under the hood and instead tries to use ContentEditable as the store of a document.

Any approach which works that way is doomed to failure. ContentEditable works fine as an input but you need to have a pure mapping from all inputs to an internal structure which only your editor controls.

I've used it before - about a year ago and found it to be quite buggy in actual use.

I've used it about 3 months ago and it didn't feel so buggy. Quite stable and nice, BUT not totally stable, I'm afraid :(

I've always wondered how web non-contenteditable editors implement WYSIWYG. This is a good guide.

The ability tof have your storage format be your own is also an advantage that non Web editors have.

So what's a good editor that works this way? I know that quilljs is coming out soon but it's still not released yet. Trix has a ton of bugs and the 1.0 version is still in development. Is there a stable one.

https://prosemirror.net is nice to use and seems quite mature. Made by the guy who also did the popular CodeMirror.

The API and internal code is still expected to change. He very recently finished a large breaking refactor, so it is not mature. Having said that it is a nice editor with a lot of promise.

What ton of bugs? Trix is used by thousands of people daily in Basecamp 3. I'd like to think it's pretty stable.

I've been pretty happy using TextAngular so far


ContentEditable (or rich text editor in general) would be very useful. But browser vendors like Microsoft, Google and Apple all have vast interest to keep the sad quirky status quo. It would cannibalise their web word processors of their cloud services. Weird is that Mozilla Firefox shows little interest as well.

contenteditable stole 4 months of my life from me. I wish I'd read way way more before charging a hill so so many before me had died upon. Very happy we have sensible alternatives today like Medium.js amongst the others people have mentioned.

Here's a list of HTML content editors that I put together back when basecamp released Trix: https://news.ycombinator.com/item?id=10413549

His description of the problem is brilliant. One might disagree with the axioms presented, but the conclusion is spot on: << It’s impossible to build what ContentEditable wants to be, because they have conflicting requirements. >>

Brilliantly simple.

Remember reading this article when it was first published... very interested to do so again!

Is anyone actually still using bare contenteditable in a serious way?

Yeah, I just built an [internal] CSS documentation system; every item has a fully editable example using CSS in style tags on the page, each with contenteditable applied to allow users to play around with it; editing within the style tag immediately alters the styling of the associated example. Works beautifully, as I have no need to persist anything, and zero JS used. (I get this is an edge case; I've tried using it a couple of times for persisted input and, yeah, not a good idea past simple I/O)

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact