CodeMirror themes: style the editor and switch colors without replacing its document

In CodeMirror 6, add an EditorView.theme() extension to style the editor interface. Syntax-token colors use a separate highlight style. To switch themes while the same document remains open, put the theme extensions in a Compartment and reconfigure that compartment. Styling guide · Configuration guide

A support console contains two source editors: the active HTML reply and a reference snippet. Its dark-mode switch should update the intended editors without replacing either document or sending the author's selection back to the beginning. Theme configuration belongs in the editor's extension setup rather than a document reload.

Create a theme for the editor surface

This original dark surface uses CodeMirror's stable interface selectors:

import { EditorView } from '@codemirror/view';

const nightSurface = EditorView.theme({
  '&': {
    color: '#e8edf3',
    backgroundColor: '#17212b'
  },
  '.cm-content': { caretColor: '#94d8f2' },
  '&.cm-focused .cm-cursor': {
    borderLeftColor: '#94d8f2'
  },
  '&.cm-focused .cm-selectionBackground, ::selection': {
    backgroundColor: '#35566f'
  },
  '.cm-gutters': {
    color: '#bac7d3',
    backgroundColor: '#202f3d',
    border: 'none'
  }
}, { dark: true });

Add nightSurface to the actual editor's extensions. & identifies the outer editor element within these generated theme rules. Descendant rules such as .cm-content already receive the theme's generated prefix; inspect the resulting selector before copying an ordinary global CSS selector into this object.

The { dark: true } option enables dark defaults for parts the theme does not explicitly style. It does not automatically supply a complete syntax palette. Review search panels, tooltips, diagnostics, and other extensions the console exposes.

The two selection rules matter because CodeMirror can show a native selection or its own drawn selection. A background that looks correct with the mouse may still leave selected text unreadable under the other path. Keep the caret and selection visible alongside the normal text.

Configure token colors separately

A dark background with pale text is a surface theme. To color parsed syntax, create a highlight style and enable it:

import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
import { tags } from '@lezer/highlight';

const nightTokens = syntaxHighlighting(HighlightStyle.define([
  { tag: tags.tagName, color: '#9bd9ca' },
  { tag: tags.attributeName, color: '#ebc58a' },
  { tag: tags.string, color: '#c8d997' },
  { tag: tags.comment, color: '#b0bbc8', fontStyle: 'italic' }
]));

Keep the editor's language extension installed so it can assign syntax tags. This sample covers four categories, not every token type. Pair the surface and token extensions as one theme choice, and inspect unstyled text as well as the colored tags.

If the support console already uses another highlighter, define which configuration should win instead of stacking color schemes blindly. Highlight styles and ordinary editor CSS solve different parts of the presentation.

Switch the theme through a compartment

Create a dedicated compartment during the editor's existing initialization:

import { Compartment } from '@codemirror/state';

const themeSlot = new Compartment();
const initialTheme = themeSlot.of([nightSurface, nightTokens]);

Add initialTheme to that editor's extensions when its state is created. Retain its document, language support, keymaps, and other configuration. The same themeSlot object must remain available to the switch handler.

To replace that theme choice later:

function setConsoleTheme(view, nextThemeExtensions) {
  view.dispatch({
    effects: themeSlot.reconfigure(nextThemeExtensions)
  });
}

Here view is the existing EditorView, and nextThemeExtensions is the complete chosen surface/highlighter extension set. Use an appropriate compartment and view reference for each editor integration. Replacing the whole EditorState just to change colors can discard unrelated state; reconfiguring this compartment targets the theme portion.

Check the actual editing states

Load the active reply with a heading, a quoted attribute, a comment, and a long paragraph. Make a small unsaved edit, select part of the paragraph, then change theme. Confirm the document text and intended selection remain correct. Check an insertion point, selected text, the gutter, and any open search panel in each theme.

At a narrow panel width, confirm the line-wrapping or scrolling policy still works. A color update should not introduce fixed positioning or change line display in ways that break editor layout. CodeMirror's styling guide warns that arbitrary structural CSS and unsupported transforms can interfere with its view calculations.

For line-reference behavior, see CodeMirror gutters. Finally, read view.state.doc.toString() after switching: the saved HTML should contain the author's source, without theme classes or interface colors copied from the editor DOM.

Keep reading