Article started: 27.03.2025
Every CMS, admin panel and developer tool hits the same wall sooner or later: a plain <textarea> is a miserable place to write code. There’s no syntax highlighting, no line numbers, no indentation logic, and hitting the Tab key just throws you into the next form field. So you go looking for a JavaScript editor library, and that’s where the fun starts.
The good news is that the field has matured a lot. CodeMirror 6 is now the default workhorse for embedded editors, Shiki has become the go-to highlighter behind modern docs sites, and Monaco Editor keeps shipping the full VS Code experience (whether or not your bundle can survive it).
The landscape also shifted in ways you might have missed. CodeMirror moved its development off GitHub to a self-hosted Forgejo instance in 2026, the tiny CodeFlask has gone quiet, and there are now several well-maintained wrappers that save you from wiring the big editors into a framework by hand. This guide walks through all of it, with current numbers, real code, and an honest take on which one you should actually pick.
Why Transform Textareas Into Code Editors?
Let me make the pitch quickly, because “just use a textarea” sounds fine until you watch someone try to edit JSON in one:
- Syntax highlighting. Color turns a wall of text into something you can actually scan. Braces, strings and comments stop blending together.
- Line numbers. Essential as soon as your users need to talk about “line 42” in a bug report or a support ticket.
- Auto-indentation and bracket matching. The editor keeps structure sane, so people don’t spend their afternoon fixing whitespace by hand.
- Proper Tab handling. Tab should insert indentation inside a code field, not jump the focus away. This one detail is the difference between usable and infuriating.
- Code folding and search. On anything long, being able to collapse sections and find/replace is the feature that makes the tool feel professional.
- Validation and completion. The heavier editors can flag syntax errors and autocomplete as you type, which is gold for configuration and query fields.
The bottom line: a code editor turns a text box people tolerate into a field people don’t fight. If your product asks a human to write structured text, it’s worth the few kilobytes.
The 2026 Landscape at a Glance
Here’s where the main options stand right now. Stars and download figures are pulled from GitHub and the npm registry in September 2026, and sizes are the minified production builds (gzipped figures noted where they matter).
| Editor | Version | GitHub stars | Weekly npm downloads | Size (minified) | Maintenance |
|---|---|---|---|---|---|
CodeMirror 6 | 6.43.x | 7.8K (dev repo) | ~10.4M | ~400 KB (135 KB gz) | High |
CodeMirror 5 | 5.65.21 | 27.2K | — | ~174 KB | Maintenance |
Monaco Editor | 0.56.0 | 46.8K | ~6.2M | ~24 MB (full build) | High |
Ace Editor | 1.44.0 | 27.1K | ~1.0M | ~475 KB | Medium |
@uiw/react-codemirror | 4.25.11 | 2.3K | ~3.3M | varies | High |
@monaco-editor/react | 4.7.0 | 4.7K | ~4.8M | varies | Medium |
CodeJar | 4.3.0 | 2.0K | ~22K | ~2.5 KB | Low |
EasyMDE | 2.21.0 | 3.1K | ~202K | ~327 KB | Medium |
react-simple-code-editor | 0.14.1 | 1.7K | ~636K | varies | Low |
CodeFlask | 1.4.1 | 1.1K | ~7K | ~25 KB | Unmaintained |
A couple of things jump out. First, CodeMirror’s GitHub star count looks small because its repositories are now read-only mirrors — the project itself moved to a self-hosted Forgejo instance, and its npm packages are released constantly (@codemirror/view shipped 6.43.13 the same day I wrote this). Downloads, not stars, are the real signal here: over 10 million a week. Second, the React wrappers have become genuinely huge in their own right, which tells you most people are not wiring these editors up by hand anymore.
The Major Players
1. CodeMirror 6 — the modular default
CodeMirror is the editor I reach for first, and it’s the one most modern apps quietly depend on. Version 6 is a complete rewrite built from small, composable packages: you assemble exactly the editor you need from @codemirror/state, @codemirror/view and a pile of extensions, rather than shipping one monolithic bundle.
That modularity is the whole point. The official bundling guide puts a naive build of the basic setup plus one language at around 1 MB unminified, dropping to about 400 KB minified and 135 KB gzipped once you strip comments and whitespace. Go minimal and you’re looking at roughly 75 KB gzipped. That’s a fraction of what the IDE-class editors cost you.
Install it with npm:
|
1 |
npm install codemirror @codemirror/lang-javascript |
A basic editor is one EditorView on a container element:
|
1 2 3 4 5 6 7 8 9 10 11 |
import { EditorView, basicSetup } from "codemirror"; import { javascript } from "@codemirror/lang-javascript"; const view = new EditorView({ doc: "const answer = 42;\n", extensions: [basicSetup, javascript()], parent: document.querySelector("#editor"), }); // Read the current value whenever you need it console.log(view.state.doc.toString()); |
The big behavioural change from v5 is that there is no fromTextArea() and no onChange callback. You read the value from view.state.doc.toString(), and you react to edits through an update listener. Syncing back into a hidden textarea so forms still work looks like this:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import { EditorView, basicSetup } from "codemirror"; const hidden = document.querySelector("#code"); const view = new EditorView({ doc: hidden.value, extensions: [ basicSetup, EditorView.updateListener.of((update) => { if (update.docChanged) { hidden.value = update.state.doc.toString(); } }), ], parent: document.querySelector("#editor"), }); // Make sure the textarea is current before the form posts document.querySelector("form").addEventListener("submit", () => { hidden.value = view.state.doc.toString(); }); |
Perfect for: general-purpose code editing, CMS and config fields, note-taking and data tools, and anything that has to work well on mobile. If you’re starting fresh in 2026 and don’t have a hard requirement for full IDE intelligence, start here.
A note on versions: CodeMirror 5 isn’t dead, but it’s in maintenance. It still receives the occasional patch (5.65.21 landed in early 2026), keeps its friendly CodeMirror.fromTextArea() API, and has a vast ecosystem of modes and add-ons. New projects should use v6; existing v5 apps can migrate using the official migration guide, though be warned the API changed substantially.
2. Monaco Editor — the VS Code engine, in your browser
Monaco is the editor that powers VS Code, extracted and shimmed to run on the web. Nothing else comes close on raw capability: IntelliSense-style completion, real TypeScript and JavaScript language services, validation, multi-cursor editing, diff views and a whole IDE’s worth of commands.
It currently sits at 0.56.0 with around 46.8K stars and roughly 6.2 million weekly downloads. It’s actively developed by Microsoft and feels genuinely first-class — but it’s heavy. The published min/vs build is on the order of 24 MB across all its files and language workers. You can (and should) tree-shake and lazy-load that down, but you’ll never call Monaco small.
|
1 |
npm install monaco-editor |
Unlike CodeMirror 5, Monaco can’t attach to a textarea. You mount it into a <div> and mirror the content into your real form field:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import * as monaco from "monaco-editor"; const hidden = document.querySelector("#code"); const editor = monaco.editor.create(document.querySelector("#editor-container"), { value: hidden.value, language: "javascript", theme: "vs-dark", automaticLayout: true, }); editor.onDidChangeModelContent(() => { hidden.value = editor.getValue(); }); |
Perfect for: full IDE-like experiences, cloud editors and playgrounds, multi-file environments with real type checking. Heads up: Microsoft explicitly states Monaco is not supported in mobile browsers or mobile web frameworks, and the web workers that power its language services need careful bundler configuration. Budget real time for the build setup.
3. Ace Editor — the battle-tested veteran
Ace is the editor you’ve been using for a decade without knowing it — it predates CodeMirror 6 by years and still powers plenty of production tools. It’s stable, reasonably fast, ships a huge catalogue of language modes and themes, and the whole thing can be loaded as a single minified file of around 475 KB.
It’s at 1.44.0 with about 27.1K stars and roughly 1 million weekly downloads. Maintenance is honest-but-slower than CodeMirror or Monaco: it gets updates, just not the constant churn of the newer projects. Ace also has a long history of being usable as a textarea replacement via its bookmarklet, which is a nice progressive-enhancement trick.
|
1 |
npm install ace-builds |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import ace from "ace-builds"; import "ace-builds/src-noconflict/mode-javascript"; import "ace-builds/src-noconflict/theme-monokai"; const hidden = document.querySelector("#code"); const editor = ace.edit("editor"); editor.session.setMode("ace/mode/javascript"); editor.setTheme("ace/theme/monokai"); editor.session.setValue(hidden.value); editor.session.on("change", () => { hidden.value = editor.session.getValue(); }); |
Perfect for: legacy applications that already use Ace, projects that want an editor that “just works” without a modular build step, and anyone prioritising stability over the newest features.
Lightweight Alternatives
4. CodeJar — the tiny one that punches up
CodeJar is an embeddable editor weighing in at roughly 2.5 KB minified with zero dependencies. It does the essentials — indentation preservation, auto-closing brackets and quotes, Tab indentation, undo/redo — and delegates colouring to whatever highlighter you already use, so it pairs naturally with Prism or highlight.js.
|
1 |
npm install codejar prismjs |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import { CodeJar } from "codejar"; import Prism from "prismjs"; import "prismjs/components/prism-javascript"; const element = document.querySelector(".editor"); const jar = CodeJar(element, Prism.highlightElement, { tab: " " }); jar.updateCode("const answer = 42;\n"); jar.onUpdate((code) => { document.querySelector("#code").value = code; }); |
Perfect for: simple snippet fields, “try it” boxes in docs, and any project where bundle size is the deciding factor. It sits at around 22K weekly downloads and gets occasional updates rather than constant ones — the tiny feature surface is exactly why it stays reliable.
5. CodeFlask — still around, but no longer maintained
CodeFlask is another micro editor — a small Prism-powered field with line numbers and a genuinely pleasant API. It’s around 25 KB minified and roughly 13 KB gzipped.
Here’s the honest part: its last npm release was back in 2019 and the repository has been untouched since 2023. It still works for simple cases, but I can’t recommend it for new projects in 2026. If you want the CodeFlask experience with a living maintainer, CodeJar plus Prism covers the same ground.
Highlighting Engines You’ll Pair With Them
Shiki — VS Code-accurate colour, tree-shakeable
Shiki has quietly become the highlighter of choice for documentation and blogs because it uses the exact TextMate grammars and themes from VS Code, so output is pixel-accurate to an editor. It’s ESM-first and fully tree-shakeable, and with roughly 16.9 million weekly downloads it’s clearly the modern default.
Shiki is a highlighter, not an editor, so it’s the wrong tool if you need live editing. But for read-only code display and for powering a lightweight editor’s colouring, it’s excellent:
|
1 2 3 4 5 6 |
import { codeToHtml } from "shiki"; const html = await codeToHtml("const answer = 42;", { lang: "javascript", theme: "github-dark", }); |
Prism and highlight.js — the classic pairing
Prism and highlight.js are the old guard, and they’re still everywhere — Prism at around 21.9 million weekly downloads, highlight.js at roughly 26.2 million. Both are regex-based, smaller and simpler than Shiki, and both integrate with CodeJar and CodeFlask as the highlighting function. Pick Prism for fine-grained language control, highlight.js for auto-detection and zero configuration.
Framework and Markdown Wrappers
In 2026 you usually don’t bolt these editors onto a framework by hand. The wrappers are mature, and their download numbers rival the editors themselves.
@uiw/react-codemirror — CodeMirror 6 for React
@uiw/react-codemirror wraps CodeMirror 6 in a clean controlled component and is genuinely popular — around 3.3 million weekly downloads. It exposes hooks, theming and a huge set of language extensions.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import { useState } from "react"; import CodeMirror from "@uiw/react-codemirror"; import { javascript } from "@codemirror/lang-javascript"; export default function Editor() { const [code, setCode] = useState("console.log('hi');"); return ( <CodeMirror value={code} height="240px" extensions={[javascript()]} onChange={(value) => setCode(value)} /> ); } |
@monaco-editor/react — Monaco for React
@monaco-editor/react is the easiest way to get Monaco into a React app, handling the loader and worker setup for you. It clocks around 4.8 million weekly downloads. It’s the pragmatic choice when you’ve decided you need Monaco’s power but not its integration headaches.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import { useState } from "react"; import Editor from "@monaco-editor/react"; export default function CodePane() { const [code, setCode] = useState("console.log('hi');"); return ( <Editor height="240px" defaultLanguage="javascript" value={code} onChange={(value) => setCode(value ?? "")} /> ); } |
react-simple-code-editor — the Prism-based middle ground
react-simple-code-editor gives you a textarea that overlays Prism highlighting on top of itself — no virtual DOM editor, no workers, just a cleverly layered field. It’s around 636K weekly downloads. Development is slow these days, but it does one job well and is perfect for small inputs.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import { useState } from "react"; import Editor from "react-simple-code-editor"; import { highlight, languages } from "prismjs/components/prism-core"; import "prismjs/components/prism-javascript"; export default function TinyEditor() { const [code, setCode] = useState("const answer = 42;"); return ( <Editor value={code} onValueChange={setCode} highlight={(v) => highlight(v, languages.js, "javascript")} padding={10} /> ); } |
EasyMDE — when the “code” is Markdown
EasyMDE transforms a textarea into a Markdown editor with a toolbar, side-by-side preview and keyboard shortcuts. If your users are writing docs, not code, a Markdown editor is usually what they actually need. It’s at 2.21.0 with around 202K weekly downloads and an active project behind it.
|
1 2 3 4 5 6 7 |
import EasyMDE from "easymde"; import "easymde/dist/easymde.min.css"; const easyMDE = new EasyMDE({ element: document.querySelector("#markdown"), spellChecker: false, }); |
How to Choose the Right Editor
Forget feature checklists for a second and match the editor to the job:
- General-purpose editing, CMS fields, mobile users: CodeMirror 6. It’s the balanced default for a reason.
- Full IDE experience with real IntelliSense: Monaco. Accept the size and the setup work.
- Legacy app, or you want zero build drama: Ace. Load one file and move on.
- Bundle size is the whole point: CodeJar (plus Prism or highlight.js).
- React project: reach for a wrapper —
@uiw/react-codemirrorfor CodeMirror 6,@monaco-editor/reactfor Monaco,react-simple-code-editorfor the tiny Prism option. - Users are writing docs, not code: EasyMDE, and save everyone a lot of trouble.
- You only need to display code: skip the editor entirely and use Shiki, Prism or highlight.js.
Here’s how the main editors compare on the features that decide the choice:
| Editor | Textarea helper | Highlighting | IntelliSense | Mobile | Official wrappers |
|---|---|---|---|---|---|
CodeMirror 6 | No (compose it) | Built-in (Lezer) | Via extensions / LSP | Yes | React, Vue, Svelte |
CodeMirror 5 | fromTextArea() | Built-in (modes) | Add-ons | Yes | Many community |
Monaco Editor | No (sync manually) | Monarch / TextMate | Yes (TS / JS) | No | React, Vue |
Ace Editor | Bookmarklet / SDK | Built-in (modes) | Basic | Partial | Community |
CodeJar | No | External (Prism / hljs) | No | Yes | React, Angular |
My recommendation, the bottom line: start with CodeMirror 6 for almost everything. Reach for Monaco only when you genuinely need IDE intelligence and can pay for it in bytes. Drop to CodeJar when your budget is kilobytes, and hand your Markdown writers EasyMDE.
Implementation Tips That Save You Pain
1. Keep the textarea, always
Treat the editor as a progressive enhancement. Leave the original <textarea> in your markup, hide it, and let it carry the value the form actually submits. If JavaScript fails to load, users still have a working field — not a blank hole where your editor should be.
2. Sync before the form posts
This is the number one source of “my editor content vanished” bug reports. Every editor except CodeMirror 5 needs you to copy its value back into the textarea yourself. Do it on a submit listener, and also on change if you’re doing live validation.
3. Take accessibility seriously
Code editors are hard for assistive tech, and it’s easy to ship something an entire group of developers can’t use. CodeMirror 6 was rebuilt with screen-reader and keyboard support as a first-class goal, and Monaco publishes an accessibility guide. Whichever you choose, test with a real screen reader, keep the textarea as a fallback, and don’t rely on colour alone to convey meaning.
4. Test mobile on a real device
Mobile is where editors fall apart. Monaco doesn’t support it at all; CodeMirror 6 and CodeJar are built around the platform’s native selection and editing, which is why they hold up. If a phone is ever in scope, test the actual typing experience on actual hardware — autocomplete popups and selection handles behave very differently from desktop.
5. Lazy-load the heavy ones
If you’re shipping Monaco or a language-rich CodeMirror setup, load it on demand. A dynamic import fired on first focus keeps a multi-megabyte dependency off your initial page load, and dynamic language loading keeps you from shipping grammars nobody uses.
Glossary
- Textarea enhancement
- Turning a plain
<textarea>into a rich code field while keeping the original element for form submission and as a no-JavaScript fallback. - Tree shaking
- A bundler feature that drops code you never import. It’s what lets a modular editor like CodeMirror 6 ship only the extensions you actually use.
- IntelliSense
- VS Code-style autocompletion, hover information and inline type checking. Monaco has it built in; CodeMirror 6 gets it through the language-server protocol.
- Monarch / Lezer / TextMate grammar
- Three ways to describe a language for highlighting. Monaco uses Monarch (and TextMate via extra packages), CodeMirror 6 uses Lezer, and Shiki uses VS Code’s TextMate grammars.
- Web worker
- A background thread browsers use to run heavy work off the UI thread. Monaco’s language services live in workers, which is why its bundler setup takes some care.
- Controlled component
- A framework component whose value is driven by your application state. The React editor wrappers are controlled components that keep the editor and your state in sync.
- Progressive enhancement
- Building the baseline experience that works everywhere first, then layering richer behaviour on top only when the browser can handle it.
FAQ
Which JavaScript code editor should I use to turn a textarea into a code editor in 2026?
For most projects CodeMirror 6 is the best default, because it is modular, mobile-friendly and actively maintained. If you need a full IDE experience with IntelliSense, use Monaco Editor and accept the large bundle. For tiny inputs where size is everything, CodeJar is the smallest serious option.
Does CodeMirror 6 still have fromTextArea() like CodeMirror 5 did?
No. CodeMirror 6 removed the static fromTextArea() method because the maintainers considered it fragile. Instead you create an EditorView on a container element and keep your own hidden textarea in sync by listening for changes. If you want fromTextArea(), you have to stay on the CodeMirror 5 line.
How do I get the value from a CodeMirror 6 editor, or listen for changes?
Read the current text with view.state.doc.toString(). To react to edits, add an EditorView.updateListener extension and check the update.docChanged flag. There is no onChange callback like in version 5, because everything in CodeMirror 6 flows through extensions and transactions.
Can Monaco Editor be attached directly to a textarea?
No. Unlike CodeMirror 5, Monaco has no fromTextArea() helper. You create a separate div for the editor and then mirror its content into a hidden textarea with editor.onDidChangeModelContent(), so form posts, validation and fallbacks keep working.
Monaco or CodeMirror 6, which is faster and smaller?
CodeMirror 6 is far smaller and lighter. A basic setup with one language is roughly 400 KB minified and about 135 KB gzipped, and a minimal build can get down near 75 KB gzipped. Monaco’s full published build is around 24 MB before tree-shaking. Monaco handles very large files and IDE features better, while CodeMirror wins on load time and mobile.
How big is Monaco Editor and how can I shrink the bundle?
The full Monaco min/vs build is roughly 24 MB, but you do not have to ship all of it. Use the ESM build, import only the languages you need, load the editor lazily with a dynamic import on first focus, and let your bundler code-split the language workers. That usually brings real-world payloads down to a few megabytes.
What is the smallest code editor library?
CodeJar is the tiniest serious option at roughly 2.5 KB minified with no dependencies, and you pair it with Prism or highlight.js for colouring. CodeFlask is a bit bigger at about 13 KB gzipped, but it has been unmaintained since 2019, so treat it as legacy code.
Is CodeMirror 5 dead, and should I migrate to CodeMirror 6?
CodeMirror 5 is not dead but it is in maintenance mode. It still receives occasional patches like 5.65.21 in early 2026, but no new features. New projects should start on CodeMirror 6, and existing version 5 apps can migrate with the official migration guide, though the API changed substantially.
Does Monaco Editor work on mobile?
No. Microsoft states that Monaco is not supported in mobile browsers or mobile web frameworks. If mobile matters to you, use CodeMirror 6 or CodeJar, both of which are built around the platform’s native selection and editing behaviour.
How do I make sure the editor value is submitted with my form?
Before the form submits, copy the editor’s current text back into the original textarea that the form actually posts. CodeMirror 5 does this for you via editor.save(), but in CodeMirror 6, Monaco, Ace and CodeJar you write the value yourself on a submit listener.
Are these JavaScript code editors accessible to screen reader users?
CodeMirror 6 was rebuilt with accessibility as a first-class goal and works well with screen readers and keyboard-only users. Monaco provides an accessibility guide and a paging-based screen reader mode. Whatever you pick, keep the original textarea available and test with a real screen reader.
Which code editor works best with React?
For CodeMirror 6 use @uiw/react-codemirror, for Monaco use @monaco-editor/react, and for a tiny Prism-based field use react-simple-code-editor. All three wrap the underlying library in a controlled component so you can bind the code value to React state.
Sources
Editor Documentation and Repositories
- CodeMirror — official site and documentation
- CodeMirror — bundling example and bundle-size notes
- CodeMirror 5 to 6 migration guide
- CodeMirror — announcement of the migration to Forgejo
- Monaco Editor — official site
- Monaco Editor — repository and README (microsoft/monaco-editor)
- Monaco Editor — accessibility guide
- Ace Editor — official site
- Ace Editor — repository (ajaxorg/ace)
- CodeJar — official site and API reference
- CodeFlask — project page
Highlighting Engines
- Shiki — official documentation
- Prism — official site
- Prism — line numbers plugin
- highlight.js — official site
Framework and Markdown Wrappers
- @uiw/react-codemirror — docs and live demos
- @monaco-editor/react — repository (suren-atoyan/monaco-react)
- react-simple-code-editor — repository
- @uiw/react-textarea-code-editor — repository
- EasyMDE — repository (Ionaru/easy-markdown-editor)
