<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/minimal-code-editor-in-javascript-py0dld7eh" -->

---
title: Minimal code editor in JavaScript | daily.dev
description: A walkthrough of building a minimal, dependency-free code editor in vanilla JavaScript using a contenteditable div. Covers basic syntax highlighting via regex,...
canonical: https://daily.dev/posts/minimal-code-editor-in-javascript-py0dld7eh
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Minimal code editor in JavaScript | daily.dev
og:description: A walkthrough of building a minimal, dependency-free code editor in vanilla JavaScript using a contenteditable div. Covers basic syntax highlighting via regex,...
og:url: https://daily.dev/posts/minimal-code-editor-in-javascript-py0dld7eh
og:image: https://api.daily.dev/og/posts/pY0Dld7Eh.png
og:image:alt: Minimal code editor in JavaScript
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Minimal code editor in JavaScript

**[zserge's blog](https://daily.dev/sources/zserge)** · 5 min read · 0 upvotes · 0 comments

## Summary

A walkthrough of building a minimal, dependency-free code editor in vanilla JavaScript using a contenteditable div. Covers basic syntax highlighting via regex, the tricky problem of preserving cursor position across re-highlighting (using range/selection APIs), and adding line numbers with CSS counters. Notes Chrome's quirky cursor recalculation behavior and mentions the non-standard contenteditable='plaintext-only' as an alternative. Concludes that while a ~50-line editor is feasible, production use cases should rely on CodeMirror or Monaco.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://zserge.com/posts/js-editor>

## Questions this post answers

### How do you preserve cursor position when re-applying syntax highlighting to a contenteditable div?

Save the caret position before re-highlighting by measuring the length of text from the start of the element to the current selection end, then after updating the innerHTML, recursively walk the child text nodes to find the offset matching that saved position and recreate a Range there. Using window.getSelection().getRangeAt(0) combined with a cloned range gives the numeric caret offset needed for this restore step.

_Developers wiring up custom text editors can find deep-dive DOM techniques like this through daily.dev._

### Why does the cursor jump around in a contenteditable editor when re-highlighting on every keypress?

Chrome recalculates cursor position unexpectedly when a newline is added or removed, or when arrow keys or selection hotkeys are used, so forcing a caret restore in those cases fights the browser's own cursor handling. The practical fix is to only re-run highlighting on printable character key presses (keyCode >= 0x30 or spacebar), skipping navigation and structural edits.

_Anyone debugging flaky contenteditable cursor behavior can track browser quirks like this on daily.dev._

### Should I build a custom contenteditable-based code editor instead of using CodeMirror or Monaco?

A lightweight vanilla JavaScript editor using contenteditable can be built in under 50 lines with basic syntax highlighting and CSS-based line numbers, but it lacks the robustness needed for production. Battle-tested libraries like CodeMirror or VS Code's Monaco editor remain the better choice for featureful, production-ready code editing.

_Teams weighing a custom editor against CodeMirror or Monaco can compare tradeoffs on daily.dev._

## Similar posts on daily.dev

- [css custom highlight api javascript Tutorial](https://daily.dev/posts/css-custom-highlight-api-javascript-tutorial-zer4jpppo) · SitePoint · 5 upvotes · 1 comments
- [Fine, I’ll build my own text editor](https://daily.dev/posts/fine-i-ll-build-my-own-text-editor-mlxtdf0rd) · Lobsters · 0 upvotes · 0 comments
- [How to Make a Static HTML Page Editable in the Browser with Vanilla JavaScript](https://daily.dev/posts/how-to-make-a-static-html-page-editable-in-the-browser-with-vanilla-javascript-zqg2bele5) · freeCodeCamp · 6 upvotes · 0 comments
- [Set up a live code editor in Next.js with CircleCI](https://daily.dev/posts/set-up-a-live-code-editor-in-next-js-with-circleci-dbot2nebo) · CircleCI · 0 upvotes · 0 comments
- [Typewriter Effect : Frontend Coding Exercises](https://daily.dev/posts/typewriter-effect-frontend-coding-exercises-zbpkrehxs) · Kirupa · 2 upvotes · 0 comments

---

Tags: [#javascript](https://daily.dev/tags/javascript), [#css](https://daily.dev/tags/css), [#dom](https://daily.dev/tags/dom)

[View this post on daily.dev](https://daily.dev/posts/minimal-code-editor-in-javascript-py0dld7eh)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Minimal code editor in JavaScript","url":"https://daily.dev/posts/minimal-code-editor-in-javascript-py0dld7eh","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/minimal-code-editor-in-javascript-py0dld7eh"},"datePublished":"2026-08-23T10:20:24.842Z","dateModified":"2026-08-23T10:21:41.666Z","description":"A walkthrough of building a minimal, dependency-free code editor in vanilla JavaScript using a contenteditable div. Covers basic syntax highlighting via regex,...","image":"https://media.daily.dev/image/upload/s--P4t4XyoV--/f_auto/v1722860399/public/Placeholder%2001","thumbnailUrl":"https://media.daily.dev/image/upload/s--P4t4XyoV--/f_auto/v1722860399/public/Placeholder%2001","isAccessibleForFree":true,"articleSection":"zserge's blog","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"zserge's blog","logo":"https://media.daily.dev/image/upload/s--KQnFBOIT--/f_auto,q_auto/v1787480407/logos/zserge","url":"https://daily.dev/sources/zserge"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/minimal-code-editor-in-javascript-py0dld7eh","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"javascript,css,dom","timeRequired":"PT5M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"zserge's blog","item":"https://daily.dev/sources/zserge"},{"@type":"ListItem","position":3,"name":"Minimal code editor in JavaScript"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/minimal-code-editor-in-javascript-py0dld7eh#faq","mainEntity":[{"@type":"Question","name":"How do you preserve cursor position when re-applying syntax highlighting to a contenteditable div?","acceptedAnswer":{"@type":"Answer","text":"Save the caret position before re-highlighting by measuring the length of text from the start of the element to the current selection end, then after updating the innerHTML, recursively walk the child text nodes to find the offset matching that saved position and recreate a Range there. Using window.getSelection().getRangeAt(0) combined with a cloned range gives the numeric caret offset needed for this restore step. Developers wiring up custom text editors can find deep-dive DOM techniques like this through daily.dev."}},{"@type":"Question","name":"Why does the cursor jump around in a contenteditable editor when re-highlighting on every keypress?","acceptedAnswer":{"@type":"Answer","text":"Chrome recalculates cursor position unexpectedly when a newline is added or removed, or when arrow keys or selection hotkeys are used, so forcing a caret restore in those cases fights the browser's own cursor handling. The practical fix is to only re-run highlighting on printable character key presses (keyCode >= 0x30 or spacebar), skipping navigation and structural edits. Anyone debugging flaky contenteditable cursor behavior can track browser quirks like this on daily.dev."}},{"@type":"Question","name":"Should I build a custom contenteditable-based code editor instead of using CodeMirror or Monaco?","acceptedAnswer":{"@type":"Answer","text":"A lightweight vanilla JavaScript editor using contenteditable can be built in under 50 lines with basic syntax highlighting and CSS-based line numbers, but it lacks the robustness needed for production. Battle-tested libraries like CodeMirror or VS Code's Monaco editor remain the better choice for featureful, production-ready code editing. Teams weighing a custom editor against CodeMirror or Monaco can compare tradeoffs on daily.dev."}}]}
```

