@uiw/react-codemirror
@uiw/react-codemirror is a React wrapper around CodeMirror 6, the browser code editor toolkit. It creates and disposes the CodeMirror EditorView for you, maps editor changes into React callbacks, supports controlled values and refs, and accepts normal CodeMirror extensions for languages, autocomplete, linting, themes, keymaps, and custom behavior. It is an editor component, not an IDE: language intelligence, file management, collaboration, and code execution remain your job.
The sensible React choice when you want CodeMirror 6 and accept its extension model. Do not install it expecting a complete browser IDE or a one-package language bundle; choose Monaco for that job or a textarea editor for much less machinery.
Use it if
- You need an embeddable code or configuration editor in a React 17+ application and want CodeMirror 6 behavior without managing EditorView lifecycle yourself
- You want to add only the language parsers, themes, lint sources, and completion sources your screen actually needs through CodeMirror extensions
- You need a controlled value plus React callbacks, or a ref that exposes the underlying EditorState and EditorView for imperative operations
- You value CodeMirror's mobile-friendly, accessible editing model more than Monaco's closer imitation of desktop VS Code
- You need VS Code-level IDE features out of the box: the README exposes a text editor and extension plumbing, but language servers, project-wide symbols, file models, and workers are not included; @monaco-editor/react is the better fit
- You only need highlighted, lightly editable text: this package pulls in CodeMirror state, commands, a theme, basic setup, and view peers, while react-simple-code-editor deliberately provides a much smaller textarea-based surface
- Your team does not want to learn CodeMirror 6's extension system: anything beyond the wrapper's basic props requires concepts such as extensions, facets, state effects, transactions, views, and separate language packages
- You need server-rendered editor markup: CodeMirror creates a DOM-backed EditorView, so React server frameworks must keep the editor in a client component or load it with SSR disabled
- You expect one install to cover every language and theme: the README lists language modes and themes as separate npm packages, and Markdown fenced-code highlighting additionally needs @codemirror/language-data
- You depend on CodeMirror 5 add-ons or configuration names: the README states that wrapper v4 uses CodeMirror 6, whose API and extension model are not compatible with the old editor
Setup reality
The first install looks small, but a useful editor needs a matched set of packages. Install @uiw/react-codemirror, then install each language extension you use, such as @codemirror/lang-javascript or @codemirror/lang-json. React, React DOM, codemirror, @codemirror/state, @codemirror/view, @codemirror/theme-one-dark, and @babel/runtime are declared as peers, even though several are also direct dependencies; a strict package manager can make version alignment visible, and duplicate CodeMirror state packages can produce confusing extension-type failures. The wrapper enables basicSetup by default, including history, search, bracket matching, autocomplete, folding, and keymaps. Turn individual pieces off or pass basicSetup={false} if you provide them yourself, or duplicate extensions can compete. Keep extensions in module scope or useMemo because the README warns that changing the array reference causes costly reconfiguration. Language support and advanced completion do not appear from a string such as language="javascript"; you import an extension and pass its result. In Next.js and similar server frameworks, put the editor behind a client boundary because EditorView needs document. Controlled value updates are convenient, but sending a freshly transformed value on every keystroke can move selection or create parent-render churn. For read-only display, set both editable={false} and readOnly={true}: CodeMirror treats whether edits are allowed and whether its content DOM is editable as related but separate settings. Shadow DOM users must pass the root prop. There are no native builds, credentials, or config files, but the real setup cost is choosing and versioning the CodeMirror extension graph.
Patterns
Create a controlled JavaScript editorcontrolled-editor
import { useCallback, useState } from 'react';
import CodeMirror from '@uiw/react-codemirror';
import { javascript } from '@codemirror/lang-javascript';
const extensions = [javascript({ jsx: true, typescript: true })];
export function Editor() {
const [value, setValue] = useState('const answer: number = 42;');
const onChange = useCallback((next) => setValue(next), []);
return <CodeMirror value={value} height="240px" extensions={extensions} onChange={onChange} />;
}Install @codemirror/lang-javascript separately. Keep the extensions array stable so ordinary React renders do not reconfigure the editor.
Add JSON parsing and highlightingjson-editor
import CodeMirror from '@uiw/react-codemirror';
import { json } from '@codemirror/lang-json';
const jsonExtensions = [json()];
export function JsonEditor({ value, onChange }) {
return <CodeMirror value={value} extensions={jsonExtensions} onChange={onChange} />;
}The language extension parses and highlights JSON; it does not automatically validate against a schema or format the document.
Render a read-only code viewerread-only-viewer
<CodeMirror
value={source}
editable={false}
readOnly={true}
basicSetup={{ foldGutter: false, highlightActiveLine: false }}
/>
Use both flags when users must not modify content: readOnly blocks transactions while editable controls the browser-editable content surface.
Disable expensive default features selectivelytrim-basic-setup
<CodeMirror
value={value}
basicSetup={{
autocompletion: false,
foldGutter: false,
highlightSelectionMatches: false,
searchKeymap: false,
}}
/>
basicSetup is on by default. Disable features here before adding custom versions of the same extensions.
Provide an autocomplete sourcecustom-completions
import { autocompletion } from '@codemirror/autocomplete';
const words = (context) => {
const word = context.matchBefore(/\w*/);
if (!word || (word.from === word.to && !context.explicit)) return null;
return {
from: word.from,
options: [
{ label: 'customerId', type: 'property' },
{ label: 'createdAt', type: 'property' },
],
};
};
const extensions = [autocompletion({ override: [words] })];
<CodeMirror extensions={extensions} />;
Install @codemirror/autocomplete. Setting override replaces language-provided completion sources for this extension.
Attach a synchronous linterlint-document
import { linter, lintGutter } from '@codemirror/lint';
const noTabs = linter((view) => {
const diagnostics = [];
const text = view.state.doc.toString();
for (const match of text.matchAll(/\t/g)) {
diagnostics.push({ from: match.index, to: match.index + 1, severity: 'warning', message: 'Use spaces' });
}
return diagnostics;
});
const extensions = [lintGutter(), noTabs];
<CodeMirror extensions={extensions} />;
Install @codemirror/lint. For expensive validation, debounce work or use an asynchronous source instead of blocking every edit.
Apply a packaged themeuse-dark-theme
import CodeMirror from '@uiw/react-codemirror';
import { githubDark } from '@uiw/codemirror-theme-github';
<CodeMirror value={value} theme={githubDark} />;
Each UIW theme is a separate package. Import one theme directly instead of installing a package containing every theme.
Use a ref for an imperative editaccess-editor-view
import { useRef } from 'react';
import CodeMirror from '@uiw/react-codemirror';
export function InsertButtonEditor() {
const ref = useRef(null);
const insert = () => {
const view = ref.current?.view;
if (!view) return;
const pos = view.state.selection.main.head;
view.dispatch({ changes: { from: pos, insert: 'console.log();' }, selection: { anchor: pos + 12 } });
view.focus();
};
return <><button onClick={insert}>Insert log</button><CodeMirror ref={ref} /></>;
}
Dispatch through the exposed EditorView. Mutating the DOM or EditorState directly bypasses CodeMirror's transaction model.
Mount CodeMirror into your own containermount-with-hook
import { useEffect, useMemo, useRef } from 'react';
import { useCodeMirror } from '@uiw/react-codemirror';
import { markdown } from '@codemirror/lang-markdown';
export function HookEditor() {
const host = useRef(null);
const extensions = useMemo(() => [markdown()], []);
const { setContainer } = useCodeMirror({ value: '# Notes', extensions });
useEffect(() => { setContainer(host.current); }, [setContainer]);
return <div ref={host} />;
}
The hook is useful when the component wrapper does not fit your DOM. The container is null during the first render, so attach it in an effect.
Persist value and undo historypersist-undo-history
import CodeMirror from '@uiw/react-codemirror';
import { historyField } from '@codemirror/commands';
const fields = { history: historyField };
const savedState = localStorage.getItem('editor-state');
<CodeMirror
value={localStorage.getItem('editor-value') ?? ''}
initialState={savedState ? { json: JSON.parse(savedState), fields } : undefined}
onChange={(value, update) => {
localStorage.setItem('editor-value', value);
localStorage.setItem('editor-state', JSON.stringify(update.state.toJSON(fields)));
}}
/>;
Treat stored JSON as versioned application data. A changed extension or state-field set can make an old serialized state unsuitable to restore.
Add an application key bindingcustom-keymap
import { keymap } from '@codemirror/view';
const saveKeymap = keymap.of([{
key: 'Mod-s',
preventDefault: true,
run(view) {
saveDocument(view.state.doc.toString());
return true;
},
}]);
<CodeMirror extensions={[saveKeymap]} />;
Return true when the command handled the key. Keep preventDefault enabled for shortcuts that the browser would otherwise consume.
Mount correctly inside a shadow rootshadow-dom-root
export function ShadowEditor({ shadowRoot, value }) {
return (
<CodeMirror
value={value}
root={shadowRoot}
placeholder="Enter a query..."
minHeight="160px"
/>
);
}
Pass the actual ShadowRoot or alternate Document. CodeMirror otherwise uses the global document for event and style behavior.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @monaco-editor/react | npm | Choose it for a VS Code-like editor with models, workers, richer built-in language services, and a larger runtime cost |
| react-ace | npm | Choose it when an existing application already uses Ace modes and commands and migration value is low |
| react-simple-code-editor | npm | Choose it for a compact textarea-based editor when syntax highlighting and basic editing are enough |
| codemirror | npm | Choose CodeMirror directly outside React or when you want full control over EditorView creation and lifecycle |