mrkeyoor.com_
Sat 08 Aug 21:00 UTC
npmWeb Frontendupdated 08 Aug 2026

@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.

Verdict

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.

API stability4/5The npm line is still on major 4, which the README identifies as the CodeMirror 6 generation, and the core controlled-value, extensions, callback, hook, and ref APIs have settled around that model. The main compatibility risk sits below the wrapper: CodeMirror extensions are independent packages, so mismatched or duplicated @codemirror/state and @codemirror/view versions can still break otherwise unchanged React code.
Docs4/5The project site and README include runnable examples for languages, Markdown, themes, the hook, persisted EditorState, merge views, and the full TypeScript prop surface. They also call out the important extension-reference performance issue. The weak spot is troubleshooting: server rendering, peer duplication, controlled-selection behavior, and the boundary between wrapper props and upstream CodeMirror concepts require reading issues or CodeMirror's own reference.
Maintenance5/5Version 4.25.11 was published on July 8, 2026 and the GitHub repository was pushed at the same time, so the package and source are moving together. The repository is not archived, publishes from a large monorepo of companion themes and extensions, and uses an active CI workflow. GitHub reports 173 open issues and pull requests, which is meaningful support load but not evidence of abandonment.
Ecosystem5/5The package recorded 4,142,333 downloads for July 31 through August 6, 2026 and sits on top of CodeMirror 6's broad extension ecosystem. Its README catalogs maintained language modes, legacy modes, theme packages, merge editing, and wrapper-specific extensions. The tradeoff is fragmentation: many common capabilities arrive as separate packages whose versions and imports the application must manage.

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
Skip it if

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

PackageRegistryPick it when
@monaco-editor/reactnpmChoose it for a VS Code-like editor with models, workers, richer built-in language services, and a larger runtime cost
react-acenpmChoose it when an existing application already uses Ace modes and commands and migration value is low
react-simple-code-editornpmChoose it for a compact textarea-based editor when syntax highlighting and basic editing are enough
codemirrornpmChoose CodeMirror directly outside React or when you want full control over EditorView creation and lifecycle