@teispace/teieditor
A rich-text editor you own the code to.
A feature-rich editor on top of Lexical, distributed shadcn-style: use it as a drop-in component, or run one command to copy the components into your project and own them outright. Slash commands, a Notion-style mode, embeds, math, and four I/O formats.
the rich-text trap
Every application eventually needs a text editor, and every text editor decision is a trap.
You reach for a batteries-included library, ship in an afternoon, and it's wonderful — right up until the day a designer asks for one small change to how the toolbar behaves. Then you discover the toolbar is compiled into a component you can't reach, configurable only through the props the author anticipated, and your "small change" is now an argument with an abstraction. The usual escape routes are a fork you have to maintain forever, a wrapper that fights the library, or a polite "we can't do that."
The alternative is to build on primitives. Lexical — Meta's editor framework — is genuinely excellent at that layer: a solid model, good plugin architecture, sane handling of the horrifying edge cases that make editors hard. But it is deliberately unopinionated. Lexical gives you the engine, not the car. Every team that adopts it rebuilds the same toolbar, the same link editor, the same slash menu, the same table controls, and each rebuild is a few weeks nobody planned for.
So the choice on offer was: rent a component you can't change, or spend a month building the same UI everyone else is building.
the shadcn insight
The way out is the one shadcn/ui demonstrated for component libraries: don't ship a component, ship the code.
teieditor works both ways deliberately. If you want speed, install the package and render <TeiEditor /> — done, it's a normal dependency. If you want control, run one command and it copies roughly twenty-five component files into your own repository. From that moment they are your files. Change the toolbar, delete the plugins you don't want, restyle everything, no permission required and nobody to ask.
The interesting engineering is in making the second mode survivable. Copy-in distribution is easy exactly once; the hard part is the second time, when the upstream has improved and you have already edited half the files. The CLI tracks what it wrote, notices what you have since modified, and refuses to clobber your changes — you get the updates you can safely take and a clear account of the ones you can't. Without that, "you own the code" quietly means "you are on your own forever," which is not a gift.
what it actually ships
Two complete editors, not a starting point:
- Full mode — a fixed toolbar, a floating bubble menu on selection, a slash-command palette with twenty-five-odd grouped commands, a link editor, code action menus, table context menus, an emoji picker, and block drag handles.
- Notion-style mode — the same capability with the toolbar removed. Slash commands, bubble menu and drag handles only, for the clean document feel people now expect from a writing surface.
Underneath: headings, quotes and code blocks with syntax highlighting, nested lists, tables, images, layout columns, auto-detected embeds for YouTube, Twitter and Figma, live Markdown shortcuts as you type, KaTeX for maths, find-and-replace, and word count.
And it reads and writes four formats — HTML, Markdown, JSON and plain text — because the editor is never the whole story. Something downstream always wants the content in a shape the editor didn't feel like producing.
the decisions I'd defend
Lexical is a peer dependency, not a dependency. The editor packages resolve from your application, so there is exactly one copy of Lexical in the tree and you control its version. Bundling a second copy of an editor framework is how you get two conflicting editor states and a very confusing afternoon.
Everything visible is replaceable. The scaffolded output is ordinary React and Tailwind — no proprietary styling layer, no theme object standing between you and a class name. If you can edit a component in your own codebase, you can edit this.
The default is not a demo. Plenty of editor libraries ship an impressive example that quietly isn't production-ready. Both modes here are meant to be shipped as-is; scaffolding is for when you want to go further, not for when you want it to work at all.
the meta-point
I built this because I needed it twice in the same year and refused to build it a third time. That is the honest origin of most good internal tooling.
The wider point is about how software is distributed. We have spent a decade treating "it's a dependency" and "it's your code" as opposites — you either get updates or you get control. The copy-in model with real update tracking says you can have both, provided somebody does the unglamorous work of tracking what changed. That work is most of this package.