Fine, I’ll build my own text editor

Lobsters Hottest Tools

Summary

The author describes building a custom text editor, experimenting with canvas, contenteditable, and textarea methods, and discusses challenges like performance and accessibility in web development.

<p><a href="https://lobste.rs/s/1malyo/fine_i_ll_build_my_own_text_editor">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 09/01/26, 11:47 AM

# Fine, I’ll build my own text editor! Source: [https://dbushell.com/2026/09/01/text-editor/](https://dbushell.com/2026/09/01/text-editor/) ![No AI - Made by Human](https://dbushell.com/assets/images/ai-policy.svg)Tuesday 1Sept2026 [“They don’t make ’em like Sublime Text anymore”](https://dbushell.com/2026/08/07/sublime-text/)resonated with a lot of folk\. Software these days is garbage\. That got me thinking; I’m good at building garbage\! Why can’t I build my own text editor? VS Code is built upon[Monaco Editor](https://microsoft.github.io/monaco-editor/)which is a`<div\>`soup hellscape\. I was late to the VS Code train because for years my Intel inside™ Mac was too slow\. That issue was resolved when I bought Apple silicon\. If that’s the standard I have a lot of room to make mistakes\. ## Canvas My first experiment renders everything on a`<canvas\>`element\. You can’t tell, but your CPU is doing a lot of work to render that picture at 60–120 frames per second\. Lack of interactivity is an obvious problem for a text editor\. I made a list of the “minimum viable” features and implemented them\. - Pointer down to position text cursor - Arrow keys to move text cursor - Highlight current line - Type to enter text - Fancy cursor animation This next demo is interactive, click around and type\. Before you @ me about Vim bindings: shut up, I’ve got more pressing issues\. Canvas gives me nothing for free\. Amongst many desirable features, I’m missing: - Text selection - Undo/redo history - Multi\-line paste - Overflow scrolling That last one is critical\. Life is too short to implement custom elastic scrollbars\. I decided to cheat and use native browser overflow on a hidden element\. A`<div\>`is sized to match the canvas text and the scroll position is used to calculate render offsets on the`canvas`\. I’m pleased with how that’s coming along but I’m also disheartened because`<canvas\>`is entirely inaccessible\. I could continue to add text selection and other features but I’m not solving the fundamental accessibility issue\. I had a better idea\. ## Content editable Instead of rendering text on the`<canvas\>`I can just render it natively in the overflow`<div\>`and make it editable with a[`contenteditable`attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/contenteditable)\. That attribute has a`plaintext\-only`value that is perfect for code\. All content remains within a single text node\. ``` <div contenteditable="plaintext-only" autocapitalize="off" autocorrect="off" spellcheck="false" translate="no"> <!-- text goes here --> </div> ``` Attributes like`spellcheck`must be disabled to avoid input latency spikes\. Want to guess how many days it took me to discover that fix?*Days\!* Using`contenteditable`gives native text selection and undo history etc\. So much accessibility goodness is wired up for free by the browser\. The[`Selection API`](https://developer.mozilla.org/en-US/docs/Web/API/Selection)provides metrics I use to continue rendering a custom text cursor\.`::selection`is available so I can style that too\. I’ve set the native[`caret\-color`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/caret-color)invisible, which is probably a no\-no\. The`contenteditable`technique is promising but I’ve noticed strange performance issues beyond a certain character count\. Chromium browsers perform worse than WebKit and whatever Firefox is now but it’s unpredictable\. ## Textarea Instead of plaintext`contenteditable`would a simple`<textarea\>`be viable? In short: yes\. Turns out a`<textarea\>`is far more performant for longer text\. In this final demo I’ve added syntax highlighting too\. My original plan was to use[custom`::highlight`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/::highlight)on the`contenteditable`element\.`<textarea\>`can’t use CSS highlights so a third layer was required\. For demo purposes I added some`<div\>`soup for the visible lines to apply[MicroLighter](https://davatron5000.github.io/microlighter/)\. **Edit:**I’m told the new[OpaqueRange API](https://olliewilliams.xyz/blog/opaquerange/)unlocks custom highlights for`<textarea\>`— neat\! Too many CSS highlights are another performance bottleneck\. A more robust solution would be to use[Tree\-sitter](https://tree-sitter.github.io/tree-sitter/)to generate a syntax tree and walk that to generate highlights for only visible lines\. I was hoping to avoid virtualised scrolling entirely but I could improve it using the[inverse sticky technique](https://pierre.computer/writing/on-rendering-diffs#:~:text=systems%20and%20browsers.-,The%20Inverse%20Sticky%20Technique,-For%20CodeView%2C%20many)\. Or I can go back to`contenteditable`because the file sizes I’d be editing don’t hit the performance wall\. Anyway, looking good, right? Looks like 90% of a text editor with 1% of the features\. From here it’s pretty straight forward to[draw the rest of the owl](https://knowyourmeme.com/memes/how-to-draw-an-owl)\. I’m tempted to keep drawing but then I think about all the little things like tab indentation\. Right now I just hijack the tab key to insert two spaces… My demos above are unoptimised and far from perfectly accessible but at least I’m not starting from a losing position\. Rendering on`<canvas\>`would be a nightmare\. I’m filing this project away for a rainy day\. --- JavaScript strings and text ranges work with UTF\-16 code units\. It’s easy to naively introduce bugs\. I’m sure my demos are full of them\. I’ll leave with a code example to nerd snipe\. ``` "🍋‍🟩".length; // 5 [..."🍋‍🟩"].length; // 3 const segmenter = new Intl.Segmenter("en", {granularity: "grapheme"}); [...segmenter.segment("🍋‍🟩")].length; // 1 ```

Similar Articles

Gtk+Adwaita Markdown Editor in ~600 lines

Lobsters Hottest

A compact GTK+Adwaita split-view Markdown editor implementation in about 600 lines, using a GTK WebView and CDN-loaded marked.js and KaTeX. It serves as a prototype inspired by Apostrophe to demonstrate how easy building such apps has become.

Native all the way, until you need text

Hacker News Top

A veteran macOS/iOS developer recounts the struggle of implementing a Markdown-supporting chat UI using Apple's native frameworks (SwiftUI, AppKit, TextKit) and ultimately finds that web-based technologies like Electron offer a more practical solution for rich text rendering.

Don't Roll Your Own …

Lobsters Hottest

The author expands the 'don't roll your own crypto' principle to web development, arguing against custom implementations of scrolling, link navigation, text selection, and other browser-native behaviors.