Lexical – a web text editor framework that powers Facebook
playground.lexical.devI'm the author of Lexical and one of the many engineers working on Lexical full-time at Meta. If you'd like to know anything, or ask any questions, please do!
For those of you looking for the sourcecode for the playground, you can find it here:
https://github.com/facebook/lexical/tree/main/packages/lexic...
We also have a Discord channel you can check out:
What does “web text editor framework that powers Facebook” actually mean?
I don’t use Facebook, but AFAIK, there are no “blog features” in the platform, are there?
Other than a WYSIWYG editor [1], what other use cases do you intend this project for?
see https://news.ycombinator.com/item?id=14838232
> The competition has no choice but to spend all their time porting and keeping up, time that they can’t spend writing new features. Look closely at the software landscape. The companies that do well are the ones who rely least on big companies and don’t have to spend all their cycles catching up and reimplementing and fixing bugs that crop up [only on Windows XP.]
so it is the same with the modern day web frameworks as it is in the old days of windows and microsoft's dominance.
Joel Spolsky was as right in early 2002 as he is right in 2022.
Joel Spolsky wasn't particularly right in 2002, and he hasn't gotten more relevant with age.
As for this particular article, what is your criticism? That FB makes a rich text editor available as open source (under the MIT license)? How on earth does that mean "companies have to spend all their cycles catching up and reimplementing and fixing bugs"?
You can always roll your own, or use one of the many others available if this one doesn't taste right? (Also: Who on earth gains a competitive advantage from having a text editor, in 2022?)
> How on earth does that mean "companies have to spend all their cycles catching up and reimplementing and fixing bugs"?
The fact that there's draft.js, and a myriad of other existing editor libraries out there already, all of which could've been contributed to instead.
If a startup chose to base their technology on a library that is released by a major corporation, they face the risk of getting "rug pulled" (or the library updates incompatibly and now you're locked in either to using the old version, or painful upgrade to the new version).
Of course, they don't have to choose to use such a library, but if they don't then there's a community of people who then questions them on why they are spending time replicating a technology.
The point is, if a startup chose to use this library, they face the risk of having to expend time keeping up in pace (and associated cost of doing so) with facebook.
This equivalent scenario exists in someone writing against libraries released by microsoft on windows, and that's what spolsky is complaining about.
This is no different than choosing a volunteer-based OSS solution or rolling your own. If you need things that are specific to your use case, you'll always need to shoulder the cost.
That is no different from something you do yourself, either. What you call "rug pull" is merely the point where the solution you got for free suddenly costs as much to maintain as your inhouse solution.
> This is no different than choosing a volunteer-based OSS solution
the big difference with a true volunteer based OSS solution, like for example, linux, is that there's many individuals involved. The decision making in that project then won't turn into what spolsky said. I wouldn't imagine the linux maintainers "rug pull", but i can imagine facebook doing a rug pull.
Oh please. Any project that isn't under your direct control will at some point "rug pull", because they will make a decision that's different from what you'd have preferred.
Major controversies that come to mind around Linux pretty much immediately are systemd, Wayland, the sound server wars. I also vaguely recall major back and forth around memory management. That's normal and expected. That's why you make "buy vs build" decisions, and any third party package is susceptible to that.
And "many individuals involved" means absolutely nothing - do you honestly believe that somehow this editor is a lone work of genius? Or that companies somehow have a coherent vision for every single detail? Have you ever worked in a large company?
If you are using someone else’s work for free, then what’s there to complain?
See also “nerd sniping”: https://xkcd.com/356/
What are you on about
A really nice project.
A recruiter called me from your company recently, and I kind of cringed hearing him talk about the meta verse.
I realize now they probably would be much more effective with engineers if they skipped that part and just focused on all the cool work that’s being done.
Thanks for the offer, let me take you up on this. Could you compare Lexical and Prosemirror from an architecture perspective? What motivated your decisions in that regard?
Sure thing. I actually wrote up a really detailed response to this on ProseMirror's discussion board:
https://discuss.prosemirror.net/t/differences-between-prosem...
Ultimately though, there are many similarities between the two – intentionally so. We were inspired by ProseMirror and some of its APIs and approaches. I think the biggest underlying differences are in how we tackle things from a DX perspective. We tried to bring a more composable API to making text editors work – from the plugin patterns, how updates and reads work, to how you listen for changes and react to them. Much of this was inspired by my prior work working on the React core team and from creating Inferno.
Interesting you say DX is a large aspect of what you are going for. ProseMirror is awesome but the API can be somewhat complex for simple things (but at the same time incredibly powerful when you get to understand it). I absolutely love TipTap[0] which is providing a much more user friendly API on top of ProseMirror, along with a clean Plugin system.
Going to have to have a play with Lexical, excited to see Yjs support!
[0]: http://tiptap.dev
ProseMirror has some of the worst developer experience possible. I wasted so much time figuring out it’s convoluted API and abandoned it soon after.
It does apparently have the best live editing / collaboration features of all text editors out there.
I would disagree on "worst developer experience possible", it has a very steep leaning curve, but once you grok it it makes a lot of sense.
Fundamental problem with Tiptap is its horrendous docs. If lexical can put out better docs, it is better than Tiptap automatically.
Hey since you work at Meta can you find when code support / markdown will be added to text editing components?
It’s so frustrating posting code snippets to friends threads on FB
Lexical supports code support / markdown. We actually use it a lot for Workplace, which is a kind of Facebook-like website for companies. It's really down to the product surfaces what features from Lexical they choose to use.
Hey on my business’s website we currently use draft.js for our user discussion product and we convert the user input to markdown for storing in our db. How hard would it be to switch to Lexical and does it support rendering from markdown syntax? Do you recommend any other approaches for safe storage and rendering of rich text? Thanks!
I work at Meta on the Lexical team - yes, Lexical supports markdown and rendering from markdown syntax. Lexical isn't a drop-in replacement for Draft, but we're migrating all of our surfaces internally and it isn't particularly difficult, especially if you aren't storing in the DraftJS-specific format. As for alternatives, you can also consider storing it as JSON, which Lexical supports.
Lexical has markdown support via `@lexical/markdown`. See https://lexical.dev/docs/api/lexical-markdown.
> Prevent the DOM from being manipulated externally (i.e. extensions) to guarantee that the DOM always matches the EditorState. [1]
Can you talk a bit more about how this is achieved? Is it DOM obfuscation?
[1] https://discuss.prosemirror.net/t/differences-between-prosem...
Thanks for your work in developing this!
Does Lexical have a feature to set arbitrary CSS on a particular selection range? I've been looking for a solution to do this since the WebKit's -[WebView applyStyle:] is deprecated as part of WebView Legacy. It isn't clear to me from glancing at the docs if RangeSelection.formatText() or other methods can handle anything or just a subset of styling code. Thanks!
Yes, we have something like this in @lexical/selection: https://github.com/facebook/lexical/blob/af099ffd9f464b523d6...
Just mentioning SlateJS which has [decorations](https://docs.slatejs.org/concepts/09-rendering#decorations) for this
Does it have vim keybindings? And does it need react or is there a standalone way to embed it?
People have contributed bindings for Vue, Svelte, and Solid so far. I’m not sure what state they are in though. React is definitely the most mature binding.
Does wordperfect 5.1 say anything to you :) ? You just remade it and I love you for that.
Why not add a spell checker & word counter?
Awesome work. I have been early user of Lexical building getstable.co.
Thanks for great framework and design ;)
Hi, I did (or somewhat invented/devised) a similar concept, but for visually programming UI design. See: https://jjuliano.github.io/markdown-ui/docs/container.html
sorry, I didn't realize this was a text editor. I was initially looking at the time-travel screen (the black box), and thought that is where to type things to be able to use it.
Lexical is awesome!
Is it a LOT to ask? => Could the docs PLEASE link to a downloadable runnable self-contained HTML page containing one or two editor instances in Vanilla JS? Same ask for ProseMirror.
Maybe such a page would pull the JS from a CDN, maybe it would refer to filepaths on the server.
I don't know how to form a working web page from the snippets in the docu because I do not know the relevant javascript. I would just like a working example, on my own computer, with a minimum number of moving parts, that I can play around with.
How do you handle Android and multi-lingual input? Writing the state machine to deal with Chrome/Android’s bananas selection bugs in Notion’s editor gave me a lot of headaches. Do you add extra DOM nodes on Android? Any key insights that help deal with IME on Android?
Android is indeed a headache to support. The biggest issue is how all keyboards force composition on all keystrokes, meaning you're constantly having to read the DOM input and work out what might have been entered. We don't use extra DOM nodes, but we do listen to `beforeinput`, `input` and use DOM MutationObserver to try and detect common Android patterns. I'd search this module for references to Android:
https://github.com/facebook/lexical/blob/main/packages/lexic...
> event.timeStamp < lastKeyDownTimeStamp + ANDROID_COMPOSITION_LATENCY
I tried so hard to avoid adding timeouts/timestamp logic for Android because it feels like a nondeterministic hack. In the end I gave up and did something similar.
I also found beforeInput doesn’t offer much solace on Android or with CJK language, because messing around with preventDefault or the DOM during composition disturbs the user.
One weird thing I never figured out is that if the user puts the selection in an empty block, GBoard wants to jump to the nearest word in a different paragraph. We do some brutal hacks to get around that :-/
I read your comment on ProseMirror forum about preventing interference from Granmarly, etc. Do you do this by reverting MutationRecords? Where is the code/docs for that?
Yeah, I really wanted to avoid them too. There doesn't seem a good way. The issue you describe with selection jumping is a well known issue with GBoard. Here's the logic for the mutation handling:
https://github.com/facebook/lexical/blob/main/packages/lexic...
https://github.com/facebook/lexical/blob/5802651c24d88b2f7d2...
> // Check for any random auto-added <br> elements, and remove them.
> // These get added by the browser when we undo the above mutations
> // and this can lead to a broken UI.
This made me laugh out loud. Browsers!
Until here our approach looked quite similar. Leave the observer connected during reverts and avoiding double-peocessing using .takeRecords never occurred to me, I always disconnect it. Maybe some of these brs are causing chaos…
Now thinking about it, I don't know why we didn't just do that. There must have been a reason, but I really can't recall what it was. If you're ever interested in contributing, this would be an epic contribution! :D
It's currently a bit broken on Android.
I select some text and then try to change the font size. The selection is lost when tapping on the font size menu.
ProseMirror does keep the selection intact.
It's probably because we're using a native select element, which likes to steal selection on Android.
ANDROID!!!!!
I put Lexical through Hog Bay's Moby Dick test [0] (paste the entirety of Moby Dick, scroll to the middle, and try editing). It didn't pass. AFAIK, only ProseMirror passes, albeit with some lag.
We have some important changes landing soon that will make insertions O(1) rather than the current O(n) - so this will greatly improve performance.
We're trying to choose between Lexical and Slate at work. Do you have any code examples that would be similar to this?
Example: https://www.slatejs.org/examples/richtext
Code: https://github.com/ianstormtaylor/slate/blob/main/site/examp...
<sarcasm>If you are planning to build a Notion competitor, I recommend Slate</sarcasm>. Seriously though, if you need to support Android or CJK, you should understand Slate’s limitations before you buy into it. I think ProseMirror is the best choice today because it has been around for a long time and has battle-tested MutationObserver logic. Lexical is worth considering depending on your risk appetite. It also uses MutationObserver and appears to approach Android correctly. I haven’t read enough of the code to recommend it yet. In Slate, Android is a second-class citizen with an extremely bare-bones MutationObserver reconciler. The main codebase only uses beforeInput.preventDefault which doesn’t work on Android.
ProseMirror is a great choice. Lexical might offer you more mileage if you're working with React (especially React 18 and the new concurrency features) and are happy to invest into a project that is still pre 1.0.
is there a list/writeup of these problems somewhere? is this only applicable if you want to do editable rich text? could you perhaps try to explain the high level problem? what's different on iOS? thanks!
Writing a ContentEditable editor means learning the internal state machines and undocumented behaviors of every browser you support. There are many articles online about ContentEditable / rich text editing. This one is a classic https://medium.engineering/why-contenteditable-is-terrible-1...
The major issue for both Android and CJK input is dealing with “composition” of characters by an “input method editor”. https://en.m.wikipedia.org/wiki/Input_method
In English, usually it’s okay to assume one key press event = one letter input. The opposite is true on Android generally, and for CJK languages on all platforms. These use input systems (like handwriting) where keypresses never occur, or many keypresses compose to produce one character. There are also new rules for IME composition input like “you can’t touch the DOM or change the selection during composition or the input will cancel and your user will be mad”. It turns the problem from “Hard” difficulty to “Nightmare” difficulty.
The baffling thing on Android is that each keyboard (Gboard, Samsung Keyboard, Swiftkey, Simeji, Sogou, Naver) have different composition behaviors with different event patterns… and GBoard’s ENGLISH input is probably the most strange! It composes whole English words like they were Chinese characters, and it wants to compose SO badly it will move text selection from wherever the user tapped to the nearest English word. And as an added bonus on Android only, the browsers selection APIs report the wrong selection during different phases of the event flow. Hah. Like when you tap a key, Android will send a selectionChange event before the first input-related event, and that selectionChange will probably report some weird lie about the selection position.
wow. at this point to me it seems the next step is to implement the keyboard in JS too, and/or organize a protest in Mountain View.
also, opting to use a big old plaintext textbox and something markdown-ish avoids these problems, right? (I'm trying to figure out why typing becomes slow on discord/slack in Firefox routinely. though I guess those are not real textboxes, but also contenteditable things :/)
Thanks for the advice!
Why the sarcasm?
I work at Notion and recently rebuilt much of the editor core, and I don’t think Slate is a good choice because it considers nice, simple code more important than CJK or Android support. Notion doesn’t use Slate or any other editor framework. I just study a lot of editor core code.
I don’t understand the Android argument - these are mainly meant for browsers right?
1. Android also has a browser. It is an important platform with many users.
2. Building an editor is so complicated that most organizations want to do it only once. They build a web editor, and wrap it in a native app. The shell like file browser, sharing screens, etc are “native” but the editor surface is a webview. This is how Quip, Dropbox Paper, Google Docs, Notion, etc work. Even iOS worked this way initially for all editable styled text (per https://twitter.com/kocienda/status/1400484473540513792?s=21...)
3. If you are going to bet the core competency of your business on a framework, it’s important to understand the motivations and limitations of said framework.
Thanks, I see. I'm using Slate for TigYog.app, and fairly happy with it, but neither CJK nor Android are important there.
Btw I'd love to see any public posts about the architecture of the Notion editor!
Slate has a nice, approachable API from a React perspective. I think depending on your project it could be an okay trade off.
I co-wrote a post about our editor internals after finishing up the recent re-work but we decided not to publish it to conserve our Competitive Advantage.
Dang. Makes sense, since it's such a defining characteristic. But dang, I would love to read that :D
Guess I'll just have to get a job at Notion some day and dig it up...
What are your thoughts on TipTap?
Since it doesn’t implement editing itself I haven’t studied its source. Under the hood it’s ProseMirror so it should be okay, although I’d be uneasy about stacking abstractions too high: Your code —> TipTap React —> TipTap Extension —> ProseMirror Plugin —> ProseMirror Core —> DOM feels like there’s a lot of incidental complexity to learn all of the APIs across these layers. Right now the docs site is returning HTTP 502 to me ¯\_(ツ)_/¯.
The Lexical playground is one giant kitchen-sink example all written in React. You could check out the code for it locally and `npm run start` and play around with removing/adding plugins. You can also check out some examples on Codesandbox: https://codesandbox.io/s/lexical-rich-text-example-5tncvy
Thanks! Very helpful.
I've given Slate and Draft.JS a go in the past and they both aren't great from a developer experience perspective and are generally buggy. Slate also had issues where it would throw a type error and break the page, Draft.JS struggles with cursor placement issues.
A few days ago, I was looking to create an editable interface for an email composer for a project I’m working on (https://PretzelBox.cc, since you asked).
I wanted to see if Lexical was a good fit. My app is plain javascript with a bit of Alpine and htmx. I just couldn’t figure out how to <link> to the appropriate js file in my html file. And I’m not a frontend dev so I have no idea how to browserify (or whatever the technical term is) the right npm modules.
I ended up using MediumEditor which is ancient in relative terms. Anyway, now composing email feels like writing a Medium post, fwiw.
In what sense does this "power" Facebook? Do you mean it's used for most or some specific text editing components of Facebook?
It's replacing any previous web text editor UX components that we had in the past (likely DraftJS). We're also building Lexical for iOS which is slowly rolling out across iOS devices too.
Well, I want the previous editor back. In fact, just give me a text area. The current editor breaks the selection and, in turn, the clipboard manager, it doesn't work well with Compose and dead keys, and it sometimes ties itself into a knot, and the text can't be edited anymore.
When pasting links stopped working for me is about when I stopped using Facebook.
It's 2022 and we're reinventing every wheel, but in the browser.
GitHub has a Markdown editor with a paste handler (converting tables and such). It once had a bug. I had to disable handlers in the browser to be able to paste.
A textarea would not work
Very interested to know if there are any plans to support React Native? I’ve been struggling to find a good library that doesn’t require a web view.
It's under consideration, but as of now we need native support for iOS and Android first. React Native doesn't currently support contentEditable.
This looks great!
Curious whether the team is looking at improving the support of having images in tables cell? I played with the playground and it seems to work, but there seems to be some (relatively minor) glitches/bugs (e.g. difficult to place an image in a cell, moving images across cells seems difficult, and cannot Ctrl-x an image and Ctrl-v to paste it).
We definitely have plans to improve Tables drastically over time, but I'm not sure when we'll get to those particular issues. If you care to make an Issue for this on GitHub, that would be much appreciated :)
It is interesting (and cool) that this uses Typescript over Flow. It seems like Flow is still being developed too.
Just looking at the top level package.json [1], I wonder at which point you abstract all those copy/pasted targets into a script that takes an argument.
[1] https://github.com/facebook/lexical/blob/main/package.json#L...
When they first launched a few months ago it was all Flow, and then it was quickly converted to Typescript. I think they still ship the Flow types too.
I've been migrating the Lexical codebase to TypeScript recently, especially since it was open-sourced. This was done for a few reasons, but if nothing else, we know TypeScript has a much wider adoption that Flow. Removing learning curves helps to encourage contributions from the community.
Even FB engineers can learn a thing or two. ;)
Good tip.
Thanks! I guess the last 20 years of writing code were not a waste after all. Heh. =)
Recently I have been testing vditor, vditor is a markdown editor that have a instant rendering feature [1]. If I'm creating my own editor, how hard is it to implement it using Lexical?
[1] https://github.com/Vanessa219/vditor/blob/master/README_en_U...
I'd love to use Lexical to replace what we're using today. I've been checking in on the repository every few weeks. However, there are many features that are still pending https://github.com/facebook/lexical/projects/1 . We need several of the features that are "V1.0" and we obviously need the stability of a 1.0 SemVer release :)
Is there a general timeline goal for the V1.0 release?
What features in particular are you missing? We haven't really discussed a deadline for 1.0, as we've been trying to take our time making sure each release is impactful and correct.
There do not seem to be text direction controls. I see it does basic heuristic text direction guessing, apparently based on first strong LTR or RTL character in a paragraph, but this is often not adequate for determining text direction correctly, hence the need for explicit control, at least at the paragraph level, ideally also at subparagraph level. Any plans to add such controls? There are about a billion users of bidirectional languages (Arabic, Hebrew, Farsi, Urdu, Yiddish, et al).
You can set the direction of the ElementNode explicitly to define LTR/RTL semantics. By default it’s automatically detected.
Thank you. I see. I guess I'm talking about the playground whereas I guess you're talking about Lexical itself and its API. It would be really nice to have a demo of this working with the API in the playground.
How does this compare to Draft.js, another rich text editor created by Facebook?
Check out this answer I used in a previous HN thread:
I tried most of the existing editors and worked with some of them a lot. Lexical have a nice declarative API; so far, enjoying using it. Even without proper docs, I quickly got an idea of what it is and how to develop. React ecosystem lacked a proper text editor and lexical is really promising.
I realize this isn't probably your product surface but do you have any idea why FB messenger still has issues with double texting using the text box?
Also, how do you guys design to reduce latency in Lexical? Esp considering the fluidity of modern chat clients ex. Telegram which are much more performant.
You mean typing latency, right? The most important thing we do there is handling reconciliation ourselves rather than delegating to a framework (e.g., React). If you have full control over the reconciliation process, it's just a matter of continuous incremental optimization.
Could you explain this comment a little more?
What does "handling reconciliation rather than delegating" mean here?
Sure - DraftJS, for instance, delegates DOM reconciliation to React. Lexical does not. The core library is framework-agnostic, with it's own diffing and reconciliation processes.
That's really good
How do people search for jobs that work in this problem domains - parsers / editors / compilers?
For Meta, or in general? For Meta, there are plenty of teams that require some infra knowledge around these common concepts. It's been fairly straightforward to find a team as a front-end engineer that allows you to excel in these areas at Meta (once you're successful in the interview loops).
Hmm, just tried the playground in Firefox on Linux, it’s about as buggy as fancy things like this tend to be.
• Inserting emoji with my Compose key: some (e.g. U+1F641) get turned into image-backed emoji and then duplicated unless preceded by another image-backed emoji; some (e.g. U+1F928) get ignored and left as normal text.
• If I type an emoji that gets duplicated while inside a link (but not inside bold/italic), one goes where the caret is, and the other goes immediately before the link, and it leaves the caret there.
• The image-backed emoji are incompatible with inline text formatting: if you type them in the middle of formatted text, instead of ending up with the likes of <u>text EMOJI text</u>, you end up with <u>text </u>EMOJI<u> text</u>, and although you can subsequently apply the formatting so that it shows in the debug tree, it doesn’t change the HTML markup (e.g. subscript/superscript/underline/code should certainly be visible, and even italic and bold can change emoji rendering).
• If I select part of a link starting outside the end of a link (that is, go one character past the link and then press the Left arrow key—Firefox has valid caret positions for both inside and outside the start and end of an element, depending on which direction you came from, which my faint recollection was a major trouble point on WebKit many years ago, that you couldn’t do that and so literally couldn’t model various reasonable, kinda like multiple selection which is also Firefox-only and extremely useful on tables) and delete that, instead of just deleting the selected characters, it deletes the entire link. (As above, this seems to be specific to links, not affecting bold/italic.)
• Very early on (before I had done any editing at all) when I tried the time travel feature, I somehow activated the autocomplete popup on the words “The playground” by clicking or something, even though autocomplete was not turned on (I only confirmed that that was what it was, and not a spelling corrector, by later enabling it and typing “the”) and it was supposed to be read-only.
• Resizing table rows and columns is very buggy, with things like the grippies easily getting lost, the table-cell-action-button not updating its position, and it sometimes failing to act on mouse up. (Props for including the functionality, though, as it’s normally overlooked.)
• Clicking in table cells always moves the caret to the start of the cell rather than where you clicked, meaning you have to use arrow keys to get to the right place, and can’t select properly by mouse either.
• Pop-up menus don’t scroll with the document while open.
• Autocomplete popup doesn’t close when you move the caret by mouse, though they will no longer work if you click on them.
• Incidentally, who thought it a good idea to apply `list-style-position: inside`?
And this was from a fairly quick test. I found most of this stuff immediately and would expect to hit most of these issues in real life.
I honestly wish people would try to be less fancy with WYSIWYG components, because the web platform just isn’t well-adapted to it.
Thank for the detailed input. Would it be possible for you to create a GitHub issue around these (or many issues?) so that the team can track them and fix them. Thanks again!
Sorry, I’m just passing by and don’t want to interact further (I have no vested interest in the space at this time); up to you what you do with it.
Does Lexical require react?
No, it can be used with vanilla JS.
There is an official (and mature) binding with React, but there are also unofficial bindings with Solid, Vue and Svelte.
Each has a dedicated channel in the Discord (including repo links).
How is Facebook using this?
It's used throughout Meta. The Post Composer, and Comment Composer on Facebook/Workplace, on Messenger, WhatsApp Web, Instagram Messenger, and many other surfaces too!
Wow, that looks really nice!
I'm more excited about this than you'd expect LOL
Clickbait title, what does that even mean for a text editor to "power" facebook