The problem I had was simple: I wanted a low-tech way to type styled text, but I didn't want to load a complex 500KB library, especially if I was going to initialize it dozens of times on the same page.
Markdown in a plain <textarea> was the best alternative to a full WYSIWYG, but its main drawback is how ugly it looks without any formatting. I can handle it, but my clients certainly can't.
I went down the ContentEditable rabbit hole for a few years, but always came to realize others had solved it better than I ever could.
I kept coming back to this problem: why can't I have a simple, performant, beautiful markdown editor? The best solution I ever saw was Ghost's split-screen editor: markdown on the left, preview on the right, with synchronized scrolling.
Then, about a year ago, an idea popped into my head: what if we layered a preview pane behind a <textarea>? If we aligned them perfectly, then even though you were only editing plain text, it would look and feel like you were editing rich text!
Of course, there would be downsides: you'd have to use a monospace font, all content would have to have the same font size, and all the markdown markup would have to be displayed in the final preview.
But those were tradeoffs I could live with.
Anyways, version 1 didn't go so well... it turns out it's harder to keep a textarea and a rendered preview in alignment than I thought. Here's what I discovered:
- Lists were hard to align - bullet points threw off character alignment. Solved with HTML entities (• for bullets) that maintain monospace width
- Not all monospace fonts are truly monospace - bold and italic text can have different widths even in "monospace" fonts, breaking the perfect overlay
- Embedding was a nightmare - any inherited CSS from parent pages (margin, padding, line-height) would shift alignment. Even a 1px shift completely broke the illusion
The solution was obsessive normalization:
// The entire trick: a transparent textarea over a preview div
layerElements(textarea, preview)
applyIdenticalSpacing(textarea, preview)
// Make textarea invisible but keep the cursor
textarea.style.background = 'transparent'
textarea.style.color = 'transparent'
textarea.style.caretColor = 'black'
// Keep them in sync
textarea.addEventListener('input', () => {
preview.innerHTML = parseMarkdown(textarea.value)
syncScroll(textarea, preview)
})
A week ago I started playing with version 2 and discovered GitHub's <markdown-toolbar> element, which handles markdown formatting in a plain <textarea> really well.That experiment turned into OverType (https://overtype.dev), which I'm showing to you today -- it's a rich markdown editor that's really just a <textarea>. The key insight was that once you solve the alignment challenges, you get everything native textareas provide for free: undo/redo, mobile keyboard, accessibility, and native performance.
So far it works surprisingly well across browsers and mobile. I get performant rich text editing in one small package (45KB total). It's kind of a dumb idea, but it works! I'm planning to use it in all my projects and I'd like to keep it simple and minimal.
I would love it if you would kick the tires and let me know what you think of it. Happy editing!
---
Demo & docs: https://overtype.dev
Nitpicking a bit: it's not as much _rendering_ markdown as it's _syntax highlighting_ it. Another interesting approach there could be to use the CSS Custom Highlight API [0]. Then it wouldn't need the preview div, and perhaps it'd even be possible to have non-mono fonts and varying size text for headers.
[0] https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_...
https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/...
Two issues I saw:
- on my phone, the framerate noticeably drops as I scroll over the widget
- caret positioning within the textarea seems to desync against the letter positions in the highlighted div
And the whole concept severely constrains the styling choices that could be applied to the highlighted output, but I suppose that was a given.
Some links I've collected in the past that describe this approach:
- https://css-tricks.com/creating-an-editable-textarea-that-su...
- https://github.com/WebCoder49/code-input
I believe https://grugnotes.com also does this for markdown.
We lost editing for two reasons:
1) The HTTP PUT method didn't exist yet, so edited HTML files could only be saved locally.
2) Mosaic built a cross-platform web browser that defined what the WWW was for 99% of users, and they didn't include editing because that would have been too complex to build from scratch in their multi-platform code base.
Why is this not catching fire? Especially in the day of vibe coding this is a far better and far more effective route to app building
For https://grack.com/demos/adventure/, I used a hidden <input text> for something similar. I wanted to take advantage of paste/selection and all the other goodies of text, but with fully-integrated styling.
Like you, I found that standard text input boxes were far more interesting than contentEditable because they're just so simple overall.
I think there's probably an interesting middle ground here where you render real markdown with the textarea effectively fully hidden (but still with focus), emulate the selection events of the rendered markup into the textarea, and basically get a beautiful editor with all the textarea solidness.
https://blog.superhuman.com/delightful-search-more-than-meet...
My mind is utterly blown with what you can do with 912 bytes.
I imagine that I can create a really simple blog post that can load under 14 kb so that it can be sent in a (single request?) while still having comment feature with this one while also being super fast..
So good that words can't explain lol
OverType doesn't use this and the result is that you gotta build all the features in JS.
[1]: https://developer.mozilla.org/en-US/docs/Web/API/Document/qu...
By definition a “Markdown editor” cannot be WYSIWYG - you can have a WYSIWYG editor that is powered by markdown underneath though, which this is not.
Still a cool project, but someone who does not understand markdown would wonder why pressing the heading button makes my text into a hashtag instead of making it bigger.
I also made a similar thing a while ago called contextarea.com, maybe, I should add overtype!
Not that it isn't a really cool project! I'm only saying it has clear drawbacks.
At least it works in my Sciter like this:
<style>
textarea::mark(myHighlight) {
background-color: yellow;
color: red;
}
</style>
<body>
<textarea>Lorem ipsum dolor sit amet</textarea>
</body>
<script>
// Select a range of text to highlight
const range = document.createRange();
const textNode = document.querySelector('textarea').firstChild;
range.setStart(textNode, 6); // Start at the 6th character
range.setEnd(textNode, 11); // End at the 11th character
// Highlight the range
range.highlight("myHighlight")
</script>
Ask your browser vendor to enable highlight API in <textarea> too :) so such tricks will not be required.This seems to be the perfect use case for a web component and its shadow DOM. Instead of using a div.editor, this component could wrap around a textarea and it would progressively enhance the textarea experience.
My life-long gripe is that Microsoft Word forms the basis for what people think text editing is and should be. This could be incredibly useful for developers though.
The text overlay approach doesn't work if you want width-affecting/height style changes (I assume) since that'd cause the overlay to stop matching. The downside is contenteditable cursor movement is broken for inexplicable reasons.
One thing that would be great is a bit better support for lists (todos, unordered and ordered) where when you press enter once it will add another `-` or `- [ ]`, etc item and when you press it a second time it becomes a blank line.
I too was close to making my own wysiwyg, trello for example has a md wysiwyg, so i knew it was doable with contenteditabe. But after talking with the dev community I was constantly warned by people who took the path before to not do it. So in the end i did not as i did not want to invest more time than i wanted to commit to the project.
Good for you, that you did and you made it to the finish line.
(It works fine in Chrome on the same Android phone. Android 16, Firefox 141.0.3)
[1] https://developer.mozilla.org/en-US/docs/Web/CSS/font-synthe...
I noticed on the site the really cool animation you got has a 1px solid border on one of the overlays in firefox. Figure you might care since it's clearly supposed to be flashy.
I noticed toolbar is missing from options doc, reason I went looking was if I could add my own custom button to the toolbar
Love what you have done and will use in a project next week.
Especially applaud your avoidance of npm, dependencies, and the usual ubiquitous JavaScript deluge.
(Btw, there might be a typo on the landing page on the set up part. There is 2 times de <div> instead of the textarea i guess;))
Edit: and the link to the Github is brocken
[0]: https://github.com/MeanderingProgrammer/render-markdown.nvim
Anyway, nice work mate.
Only down side to it is that you cant apply any padding to the styled inline elements.
On get request - find matching .MD file and return
On put request - write to given location
And how does this handle rendering larger documents?
A natural extension seems to be a source code editor with syntax highlighting, like those used in https://marimo.io/, Jupyter, https://plutojl.org/ and other notebook-like Web editors.
The animation shows variable-pitch fonts, but the demo seems to be all the same fixed-pitch font for me. (On Firefox ESR and Chromium.)
What I am wondering is if I can modify the project enough so that lets say when I do # test, then it can automatically modify it to be enlarged instead of just colored/ basically i think that this is how reddit comments work..
Image support would be really preferred too, but honestly, this is seriously so cool that I can iamgine using this right now, but someone here mentioned spell/pell https://github.com/sylvainpolletvillard/spell and https://github.com/jaredreich/pell and so they are in the size of 1kb-2kb, even bytes and this is 40kb iirc, so why is there such a big size difference and how are those guys being so small.
Once again, amazing project, my mind is truly blown by how simple it is, I will try to integrate this or spell or just anything whenever I can!
That supports color coding for different languages?
> A PEAK UNDER THE HOOD
I think you meant "PEEK"
I have a couple projects I could see this being really useful in, at least as an option instead of pure plain text. I still feel like consumers don't like markdown though, it's frustrating.
One thing I noticed, when doing a list (bullet, numbered etc) it would be great if the list continued on barrage return (enter) - most general users would expect that I think.
That's it, I am loving it.
Having it available to simplify integration into larger projects is also great.
These two things are entirely orthogonal, even if this only does the first one.
If you need to use npm in the rest of the project, which can be helpful for any project that uses front end Javascript, having one library (essentially) that uses a different mechanism is not great.
I have built many projects and while I swore at convoluted bundlers in the past, they are pretty nice these days.