Building a contentEditable from scratch
March 12, 2026
Building a contentEditable from scratch
Native textareas are great until you need to render inline ghost text or position an autocomplete overlay relative to the caret. At that point you're fighting the browser instead of building your feature.
The problem
I was contributing to intlayer and needed to show translucent autocomplete suggestions directly inside the text input — not in a dropdown below it, but inline, like a ghost continuation of what the user was typing.
Textareas don't let you render arbitrary HTML alongside the user's text. They're opaque black boxes from the DOM's perspective.
The solution
Replace the textarea with a contentEditable div. This gives you full control over the DOM inside the input, which means you can:
- Render ghost text as a
<span>with reduced opacity after the caret - Position autocomplete overlays using
window.getSelection()andRange.getBoundingClientRect() - Handle paste events to strip formatting and insert plain text
Key challenges
The hardest part wasn't rendering — it was maintaining consistent caret behavior across browsers. Firefox and Chrome handle contentEditable selection differently, and you need to normalize the behavior yourself.
const setCaret = (node: Node, offset: number) => {
const range = document.createRange();
range.setStart(node, offset);
range.collapse(true);
const sel = window.getSelection();
sel?.removeAllRanges();
sel?.addRange(range);
};Takeaway
contentEditable is powerful but low-level. If you only need styled text input, reach for a library. If you need pixel-perfect control over what renders inside the input, it's the only real option.