Hi all. I’ve tagged a new release of all the @codemirror/... packages with a 0.18.0 version (they are bumped in sync for breaking changes on 0.x versions). This release improves styling and reconfiguration handling, and introduces a few incompatible changes which I’ll list below. There had been a ton of fixes accumulating on 0.17 minor releases as well, so if you haven’t upgraded for a while, you probably should.



The themeClass function and ``-style selectors in themes are no longer supported (prefixing with cm- should be done manually now).

Themes must now use & (instead of an extra $) to target the editor wrapper element.

The editor no longer adds cm-light or cm-dark classes. Targeting light or dark configurations in base themes should now be done by using a &light or &dark top-level selector.

@codemirror/gutter, @codemirror/panel, @codemirror/tooltip:

Extra CSS classes for gutters must now be specified with the class option. The style option no longer exists.

So if you defined your own themes, you can no longer use $ notation for “theme selectors” in the theme, but prefix the class selectors manually ($foo becomes .cm-foo). I figured removing one layer of magic, which wasn’t really pulling its weight, should make the system a bit more approachable.

To indicate the place of the top element in a theme selector (for the purpose of inserting the scope class in the right place), you now use & instead of an extra $ (& has a precedent as a current-scope placeholder in several CSS preprocessors), or &dark/&light if you want to target only editors with a given type of theme in a base theme.

So a theme like this…

  "$activeLine": {...},
  "$$dark $activeLine": {...}


  ".cm-activeLine": {...},
  "&dark .cm-activeLine": {...}

When creating DOM elements, also prefix classes manually, since themeClass no longer exists.



When multiple highlight styles are available in an editor, they will be combined, instead of the highest-precedence one overriding the others.

HighlightStyle.define now expects an array, not a variable list of arguments.

The first point is unlikely to cause much problems, but if you’re defining a highlight style, you’ll have to type HighlightStyle.define([...]) instead of (without the brackets) HighlightStyle.define(...).

New features

Highlighting got some major new features. Beyond multiple highlighters being able to combine, we also have:

It is now possible to map style tags to static class names in HighlightStyle definitions with the class property.

The new classHighlightStyle assigns a set of static classes to highlight tags, for use with external CSS.

Highlight styles can now be scoped per language.



tagExtension and the reconfigure transaction spec property have been replaced with the concept of configuration compartments and reconfiguration effects (see Compartment, StateEffect.reconfigure, and StateEffect.appendConfig).

If you are doing dynamic reconfiguration, you’ll have to change your code a bit. Roughly, something like this…

const configTag = Symbol("myconfig")

const extension = tagExtension(...)

state.update({reconfigure: {[configTag]: ...}})

Now looks like…

const configCompartment = new Compartment

const extension = configCompartment.of(...)

state.update({effects: configCompartment.reconfigure(...)})

In short, reconfiguration happens through effects, and instead of talking about “tags” the terminology is “compartment” now—you divide your configuration into compartments whose content can later be changed.

Full reconfiguration also happens through effects (TransactionSpec.reconfigure no longer exists).


thank you very much for bringing this code editor to life

before the update i removed the editor outline when focused with

$: {
    outline: 'none'

now with

"&": {
    outline: 'none',

the outline is present

for anyone interested in migrating a theme check

codemirror/theme-one-dark (

1 Like

For anyone else stumbling on this, I found I had to use view.dispatch rather than state.update to get my dynamic reconfiguration working.

The new example in the docs is great: CodeMirror Configuration Example

Yeah, state.update has no side effects, it just constructs a transaction, so calling that and dropping its return value will do precisely nothing.

@marijn classHighlightStyle is cool, but I found it still mount basic style when view init, it will interfere with externally defined styles. Can you tell me how to remove these basic styles?

Oh I found a destructive way to turn off internal styles.

import { StyleModule } from "style-mod"

StyleModule.mount = () => { /* Disabled it ! */ }


You can’t, and you shouldn’t. Many of these styles are required to make the editor function at all.

Yes, I found that some styles will affect the behavior. For example, Markdown’s list format auto-completion requires .cm-content to set white-space: pre;.

I am working on a very basic Markdown editor, many styles are not needed, such as line numbers, search, etc. If necessary, I will set the built-in styles one by one.

It seems to work now. Demo:

@marijn How does one set up an empty Compartment and then reconfigure it? I followed the example for setting up a language but getting TypeError: state.facet(...) is null if I am using language.of([]). Do compartments require to provide AN extension?

This issue seems to occur only with language plugin because it tries to access the previous state.facet(language).parser. Should there be a default “text” language specified?

This is probably the fix?! Create newly added state fields directly on the new state · codemirror/state@9775992 · GitHub


view = new EditorView({
  state: EditorState.create({
    doc: value,
    extensions: [


  effects: ext.language.reconfigure(javascript()),

I think you’re running into the same issue as this thread.