mrkeyoor.com_
Wed 23 Sept 00:34 UTC
npmWeb Frontendupdated 22 Sept 2026

@uiw/react-codemirror review

@uiw/react-codemirror 4.25.11 wraps CodeMirror 6's `EditorView` in a React component and hook. It synchronizes a controlled value, reports CodeMirror transactions through callbacks, disposes the view on unmount, exposes state and view through a ref, and accepts the upstream extension graph for languages, linting, completion, themes, and keymaps. Version 4.25.11 fixed a memory leak caused by recreating the default theme. It remains an editor shell, not a browser IDE: language servers, projects, file models, collaboration, execution, and persistence are separate work.

Verdict

@uiw/react-codemirror 4.25.11 installed in 9.3 seconds and its full browser import measured 398.8 KB minified, so it belongs on screens that need CodeMirror 6 rather than basic highlighted text. It is a good React lifecycle wrapper once the team accepts separate language packages, client-only mounting, and the upstream extension model.

We installed it

Lab card: what happened when we installed @uiw/react-codemirrorScreenshot of @uiw/react-codemirror documentation
Install✓ · 9.3s22 packages on disk · 14 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser129.5 KBgzipped (398.8 KB minified), bundled with esbuild
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @uiw/react-codemirror install cleanly?

Yes. In a fresh container with an empty cache, npm install @uiw/react-codemirror finished in 9 seconds, leaving 22 packages and 14 MB on disk. npm audit reported no known vulnerabilities.

How much does @uiw/react-codemirror add to a browser bundle?

129.5 KB gzipped (398.8 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does @uiw/react-codemirror work with both ESM and CommonJS?

Yes. Both import '@uiw/react-codemirror' and require('@uiw/react-codemirror') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does @uiw/react-codemirror include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

@uiw/react-codemirror or @monaco-editor/react: which should you use?

@monaco-editor/react: Use it for a VS Code-like editor with models, workers, and richer built-in language services at a larger runtime cost. @uiw/react-codemirror 4.25.11 installed in 9.3 seconds and its full browser import measured 398.8 KB minified, so it belongs on screens that need CodeMirror 6 rather than basic highlighted text.

When should you not use @uiw/react-codemirror?

The browser budget cannot absorb our measured 398.8 KB minified, 129.5 KB gzipped full import. A textarea-based editor is much cheaper for light editing.

API stability4/5Major version 4 remains the CodeMirror 6 line, with a settled component, `useCodeMirror` hook, controlled value, callbacks, extensions, initial state, and view ref. Version 4.25.11 made a focused memory-leak fix for default theme recreation rather than changing that surface. Compatibility failures can still arrive from the extension graph: duplicated or mismatched `@codemirror/state` and `@codemirror/view` packages may reject extension values even when wrapper props are unchanged. Seven peer dependencies make lockfile resolution part of API reliability.
Docs4/5The official site returned successfully and the README contains runnable examples for controlled editing, languages, Markdown code fences, themes, a custom theme, merge views, the hook, persisted state, and the public prop list. It explicitly tells readers to keep extension arrays stable to prevent expensive reconfiguration. Troubleshooting is spread between this project and CodeMirror's own reference. Server rendering, peer duplication, selection behavior under transformed controlled values, strict read-only configuration, and route-size budgeting need more explanation than the wrapper docs provide.
Maintenance5/5GitHub showed 2,249 stars, 173 open issues and pull requests, an unarchived repository, and a push on July 8, 2026. npm published 4.25.11 the same day with a concrete fix for default-theme recreation leaking memory. Earlier 2026 patches corrected workspace module types, generated language aliases, and sandboxed test coverage. Source and releases are moving together. The 173-item open queue is significant support load, but the release cadence and narrow fixes show that the CodeMirror 6 line is actively maintained.
Ecosystem5/5The npm endpoint recorded 4,337,919 downloads for August 18 through August 24, 2026. The wrapper sits on CodeMirror 6's language, autocomplete, lint, command, view, theme, and merge ecosystems, while UIW maintains many companion theme packages. CommonJS and ESM both loaded in our Node 22 checks. Breadth comes with fragmentation: a realistic editor spans 22 installed packages in our sandbox before application-specific languages and services, and the full import measured 129.5 KB gzipped.

Use it if

  • A React 17 or newer screen needs a real code or configuration editor and CodeMirror 6 is the chosen editing engine.
  • You want React lifecycle handling while keeping access to CodeMirror extensions, transactions, state, and the underlying view.
  • Language parsers, themes, completion, and lint packages can be selected and versioned per screen.
  • Mobile editing and CodeMirror's browser-native accessibility model matter more than reproducing desktop VS Code.
Skip it if

Setup reality

We installed @uiw/react-codemirror 4.25.11 in 9.3 seconds in a fresh Node 22 Bookworm sandbox. It left 22 packages and 14 MB on disk. npm audit reported 0 known vulnerabilities. The package is 952 KB unpacked, declares 6 direct dependencies and 7 peers, and our inspection found no bundled TypeScript declarations. Both CommonJS require() and ESM import worked through its exports map.

The full browser probe measured 398.8 KB minified and 129.5 KB gzipped. A production editor should import only the languages, themes, and extensions the screen uses, then inspect its real route chunk. React 17+, React DOM, CodeMirror, state, view, one-dark theme, and Babel runtime appear in the peer contract. Strict package managers expose misalignment, and duplicate CodeMirror state packages can produce extension-type errors that look unrelated to versions.

basicSetup is enabled by default and brings history, search, matching, completion, folding, and keymaps. Disable pieces or set it to false before supplying replacements. Keep extension arrays at module scope or in useMemo; the README warns that new references trigger costly reconfiguration. A string prop does not install a language parser. Add packages such as @codemirror/lang-javascript, and add @codemirror/language-data when Markdown fences need automatic language selection.

CodeMirror creates a DOM-backed EditorView, so use a client component in server frameworks. Controlled updates on every keystroke can cause parent render churn or selection surprises if the parent transforms the value. For a true viewer, set both editable={false} and readOnly={true} because browser editability and transaction permission are separate controls. Shadow DOM needs the actual root. There are no credentials, native compilers, or config files; the setup cost is the extension graph and route weight.

Patterns

Create a controlled JavaScript editor controlled-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 this extension array stable or React renders can reconfigure the editor.

Add JSON parsing and highlighting json-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 JSON parser adds syntax parsing and highlighting. Schema validation and formatting need separate extensions or application code.

Render a read-only code viewer read-only-viewer

<CodeMirror
  value={source}
  editable={false}
  readOnly={true}
  basicSetup={{ foldGutter: false, highlightActiveLine: false }}
/>

Set both flags: `readOnly` blocks document changes, while `editable` controls whether the content DOM accepts browser edits.

Disable expensive default features selectively trim-basic-setup

<CodeMirror
  value={value}
  basicSetup={{
    autocompletion: false,
    foldGutter: false,
    highlightSelectionMatches: false,
    searchKeymap: false,
  }}
/>

`basicSetup` starts enabled. Disable a built-in feature before adding a replacement so two extension instances do not compete.

Provide an autocomplete source custom-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`. Using `override` replaces completion sources that a language extension would normally supply.

Attach a synchronous linter lint-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`. Expensive validation should be debounced or asynchronous so it does not block every transaction.

Apply a packaged theme use-dark-theme

import CodeMirror from '@uiw/react-codemirror';
import { githubDark } from '@uiw/codemirror-theme-github';

<CodeMirror value={value} theme={githubDark} />;

UIW publishes themes as individual npm packages. Import the chosen theme directly so the editor route does not acquire a catalog.

Use a ref for an imperative edit access-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`; direct DOM or state mutation bypasses CodeMirror's transaction and extension model.

Mount CodeMirror into your own container mount-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 container ref is null during initial render. Attach it in an effect and memoize extensions used by the hook.

Persist value and undo history persist-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)));
  }}
/>;

Serialized editor state is versioned application data. Extension or state-field changes can make older saved JSON unsuitable.

Add an application key binding custom-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` after handling the shortcut. `preventDefault` stops the browser's own save action from also firing.

Mount correctly inside a shadow root shadow-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`; otherwise CodeMirror uses the global document for styles and events.

Alternatives

PackageRegistryPick it when
@monaco-editor/reactnpmUse it for a VS Code-like editor with models, workers, and richer built-in language services at a larger runtime cost.
react-acenpmUse it when an existing application already depends on Ace modes and commands.
react-simple-code-editornpmUse it when a textarea with syntax highlighting is enough and route weight matters.
codemirrornpmUse CodeMirror directly outside React or when your code should own `EditorView` construction and disposal.

More web frontend guides

postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.