zudo-codemirror

Type to search...

to open search from anywhere

Dynamic Configuration

CreatedMar 29, 2026UpdatedMar 29, 2026Takeshi Takatsudo

Using Compartments to dynamically swap extensions at runtime in CodeMirror 6: toggling features, switching themes, and runtime reconfiguration.

Compartments

In CodeMirror 6, extensions are normally fixed at the time you create the EditorState. A Compartment lets you change extensions after the editor is already running. You wrap an extension in a compartment, then later dispatch an effect to replace its contents.

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

const myCompartment = new Compartment();

Creating a Compartment

A compartment wraps an initial extension value using its of() method. You pass the compartment’s of() result as part of the extensions array when creating the state.

import { Compartment } from "@codemirror/state";
import { EditorState } from "@codemirror/state";
import { lineNumbers } from "@codemirror/view";

const lineNumberCompartment = new Compartment();

const state = EditorState.create({
  doc: "Hello",
  extensions: [
    lineNumberCompartment.of(lineNumbers()),
  ],
});

Reconfiguring a Compartment

To change the extension inside a compartment at runtime, dispatch a transaction with a reconfigure effect:

// Remove the extension (pass an empty array)
view.dispatch({
  effects: lineNumberCompartment.reconfigure([]),
});

// Re-add the extension
view.dispatch({
  effects: lineNumberCompartment.reconfigure(lineNumbers()),
});

Passing an empty array [] effectively disables the extension. Passing a new extension replaces whatever was there before.

Example: Toggling Line Numbers

import { Compartment } from "@codemirror/state";
import { EditorState } from "@codemirror/state";
import { EditorView, lineNumbers } from "@codemirror/view";

const lineNumberCompartment = new Compartment();
let lineNumbersEnabled = true;

const state = EditorState.create({
  doc: "Toggle line numbers on and off.",
  extensions: [
    lineNumberCompartment.of(lineNumbers()),
  ],
});

const view = new EditorView({ state, parent: document.getElementById("editor") });

function toggleLineNumbers() {
  lineNumbersEnabled = !lineNumbersEnabled;
  view.dispatch({
    effects: lineNumberCompartment.reconfigure(
      lineNumbersEnabled ? lineNumbers() : [],
    ),
  });
}

Try toggling line numbers on and off using the button below:

Toggle Line Numbers

Example: Switching Between Themes

import { Compartment } from "@codemirror/state";
import { EditorView } from "@codemirror/view";

const themeCompartment = new Compartment();

const lightTheme = EditorView.theme({
  "&": { backgroundColor: "#ffffff", color: "#333333" },
  ".cm-gutters": { backgroundColor: "#f0f0f0" },
});

const darkTheme = EditorView.theme({
  "&": { backgroundColor: "#1e1e1e", color: "#d4d4d4" },
  ".cm-gutters": { backgroundColor: "#252526" },
}, { dark: true });

const state = EditorState.create({
  doc: "Switch between light and dark.",
  extensions: [
    themeCompartment.of(lightTheme),
  ],
});

const view = new EditorView({ state, parent: document.getElementById("editor") });

function setTheme(dark: boolean) {
  view.dispatch({
    effects: themeCompartment.reconfigure(dark ? darkTheme : lightTheme),
  });
}

Click the “Toggle Theme” button to switch between a light theme and the oneDark theme at runtime using a compartment:

Toggle Theme with Compartment

Example: Toggling Vim Mode

The @replit/codemirror-vim package provides vim keybindings. Wrapping it in a compartment lets users enable or disable vim mode at runtime.

import { Compartment } from "@codemirror/state";
import { vim } from "@replit/codemirror-vim";

const vimCompartment = new Compartment();
let vimEnabled = false;

// Initial setup: vim off
const extensions = [
  vimCompartment.of([]),
];

function toggleVim() {
  vimEnabled = !vimEnabled;
  view.dispatch({
    effects: vimCompartment.reconfigure(
      vimEnabled ? vim() : [],
    ),
  });
}

⚠️ Warning

When toggling vim mode, the vim extension should be placed early in the extensions array so it can intercept key events before other keymaps.

Example: Toggling Line Wrapping

import { Compartment } from "@codemirror/state";
import { EditorView } from "@codemirror/view";

const wrapCompartment = new Compartment();

// Initial setup with wrapping enabled
const extensions = [
  wrapCompartment.of(EditorView.lineWrapping),
];

// Toggle wrapping off
view.dispatch({
  effects: wrapCompartment.reconfigure([]),
});

// Toggle wrapping on
view.dispatch({
  effects: wrapCompartment.reconfigure(EditorView.lineWrapping),
});

Multiple Compartments

You can use as many compartments as you need. Each one manages its own extension independently.

import { Compartment } from "@codemirror/state";
import { EditorView, lineNumbers } from "@codemirror/view";

const lineNumberCompartment = new Compartment();
const wrapCompartment = new Compartment();
const themeCompartment = new Compartment();

const state = EditorState.create({
  doc: "",
  extensions: [
    lineNumberCompartment.of(lineNumbers()),
    wrapCompartment.of(EditorView.lineWrapping),
    themeCompartment.of(myTheme),
  ],
});

You can reconfigure multiple compartments in a single dispatch by passing an array of effects:

view.dispatch({
  effects: [
    themeCompartment.reconfigure(darkTheme),
    wrapCompartment.reconfigure([]),
  ],
});

Checking Current Compartment Value

You can check what extension a compartment currently holds using compartment.get():

const currentExtension = lineNumberCompartment.get(view.state);

This returns the current extension value, which can be useful for determining the current state before toggling.

When to Use Compartments

Compartments are the right tool when you need to:

  • Toggle features on and off at runtime (line numbers, wrapping, vim mode)
  • Switch between themes or highlight styles
  • Change language support when the user switches files
  • Update configuration based on user preferences

For state that changes with every transaction (like a word count), use a StateField instead. Compartments are for swapping entire extensions, not for fine-grained state updates.

Revision History