Example: Styling
CodeMirror uses a CSS-in-JS system to be able to include its styles
directly in the script files. This means you don't have to include a
library CSS file in your page for the editor to work—both the editor
view's own styling and any styling defined for dependencies are
automatically pulled in through the JavaScript module system.
Themes are simply extensions that tell the
editor to mount an additional style
module and add
the (generated) class name that enables those styles to its outer DOM
element.
Old-Fashioned CSS
The important elements in the editor have regular (non-generated) CSS
class names, which can be targeted with manually written style sheets.
For example, the outer element has class cm-editor
.
However, the CSS rules injected by the library will be prefixed with
an extra generated class name, so that they only apply when explicitly
enabled. That means that if you need to override them, you must take
care to make your own rules at least as specific as the injected
rules, for example by prefixing them with .cm-editor
. They only need
to be as specific, not more specific, because the injected rules are
placed before any other style sheets, and will thus have a lower
default precedence than your rules.
.cm-editor.cm-focused { outline: 2px solid cyan }
.cm-editor .cm-content { font-family: "Consolas" }
Note that the cm-focused
rule applies directly to the element
that'll have the cm-editor
class, and thus needs no space between
the selectors, whereas the cm-content
rule does need one because it
applies to a node inside the editor.
Things you can Style
An editor (with a gutter and
drawSelection
enabled) has a DOM structure
like this:
<div class="cm-editor [cm-focused] [generated classes]">
<div class="cm-scroller">
<div class="cm-gutters">
<div class="cm-gutter [...]">
<div class="cm-gutterElement">...</div>
</div>
</div>
<div class="cm-content" contenteditable="true">
<div class="cm-line">Content goes here</div>
<div class="cm-line">...</div>
</div>
<div class="cm-selectionLayer">
<div class="cm-selectionBackground"></div>
</div>
<div class="cm-cursorLayer">
<div class="cm-cursor"></div>
</div>
</div>
</div>
Of course, there are limits to how you can style the editor. Things
like making the editor lines display: inline
or the cursor
position: fixed
will just break stuff. But within reasonable bounds,
the library tries to be robust when it comes to styling.
-
You can style content text with varying fonts, size, color, etc.
The editor does not expect a monospace font or a fixed line height.
-
To set the outer padding for a document, you add vertical padding
to cm-content
and horizontal padding to cm-line
.
-
By default, the editor adjusts to the height of its content, but
you can make cm-scroller
overflow: auto
, and assign a height
or max-height
to cm-editor
, to make the editor scrollable.
-
Colors can be adjusted throughout, but when adding background
colors to content, it is recommended to use partially transparent
colors. That way such a style doesn't hide other styling behind it
(including the selection).
-
Whitespace
behavior inside the editor can be pre
or pre-wrap
to control
line wrapping. (The line wrapping
extension simply sets it to
pre-wrap
.)
-
The editor's text direction is
automatically derived from the direction
style of the content
DOM.
-
The library supports having CSS transforms applied to its parent
elements that do 2D scaling and translation. Anything else
(rotation, 3D transformation, shearing) will break the editor.
Themes
Themes are defined with EditorView.theme
.
That function takes an object whose properties are CSS selectors and
whose values are
styles, and
returns an extension that installs the theme.
import {EditorView} from "@codemirror/view"
let myTheme = EditorView.theme({
"&": {
color: "white",
backgroundColor: "#034"
},
".cm-content": {
caretColor: "#0e9"
},
"&.cm-focused .cm-cursor": {
borderLeftColor: "#0e9"
},
"&.cm-focused .cm-selectionBackground, ::selection": {
backgroundColor: "#074"
},
".cm-gutters": {
backgroundColor: "#045",
color: "#ddd",
border: "none"
}
}, {dark: true})
There's a few things going on here. Firstly, some of the rules contain
“&
” placeholders. This indicates the position of the outer editor
element in the rule. By default, a generated class name is prefixed to
the rules, with a space after it (so ".cm-content"
becomes ".gen001 .cm-content"
). But in rules that directly target the outer element
(which gets the generated class), that doesn't work, and you have to
place an &
character to indicate where to insert the class selector.
Secondly, because there are two ways of showing the selection in
CodeMirror (the native selection and the
drawSelection
extension), themes will
usually want to style both—the caret-color
and ::selection
rules
apply to the native selection, whereas the .cm-cursor
and
.cm-selectionBackground
rules style the library-drawn selection.
Lastly, since this is a dark theme, it passes a dark: true
option,
so that the editor will enable its dark default styles for things not
explicitly styled by the theme.
A real theme will want to style a few more things, including elements
created by extensions (such as panels and
tooltips). You'll also usually want to include a
highlight style in your theme. You can
see the One Dark theme
for an example, and possibly copy and modify it to create your own
theme.
Base Themes
When you create an extension that adds some new DOM structure to the
editor, you'll usually want to include a base
theme that provides a default style for
the elements. Base themes act a lot like regular themes, except that
they are mounted with lower precedence and can provide separate rules
for dark and light themes.
For example, a hypothetical extension that replaces all instances of
the letter o with blue circles might want to include a base theme like
this...
import {EditorView} from "@codemirror/view"
let baseTheme = EditorView.baseTheme({
".cm-o-replacement": {
display: "inline-block",
width: ".5em",
height: ".5em",
borderRadius: ".25em"
},
"&light .cm-o-replacement": {
backgroundColor: "#04c"
},
"&dark .cm-o-replacement": {
backgroundColor: "#5bf"
}
})
The &dark
and &light
placeholders act much like &
, except that
they expand to a class that is only enabled when the editor's theme is
light or dark. In this case, the base theme gives its circles a
brighter color in a dark theme (on the assumption that the background
will be darker there).
The extension returned by baseTheme
must be added to the editor
configuration to (reliably) take effect—the style rules will only be
mounted in the DOM when an editor that uses them is created. It is
usually bundled up in an array with other related extensions and
returned from the exported function that produces the extensions for
the feature (see for example the zebra stripes example).
Highlighting
Code highlighting uses a somewhat different system from editor-wide
theming. Code styles are also created with JavaScript and enabled with
an editor extension. But by default they don't use stable,
non-generated class names. A highlight
style directly returns the class names
for the syntactic tokens.
Highlight associate highlighting
tags with
styles. For example, this one assigns styles to keywords and comments.
import {tags} from "@lezer/highlight"
import {HighlightStyle} from "@codemirror/language"
const myHighlightStyle = HighlightStyle.define([
{tag: tags.keyword, color: "#fc6"},
{tag: tags.comment, color: "#f5d", fontStyle: "italic"}
])
Each of the objects given to
HighlightStyle.define
mentions a
tag (which
are assigned to tokens by language packages), and
otherwise contains style properties just like the objects in a theme.
When defining an editor theme, you'll usually want to provide both a
theme extension and a highlight style that looks good with it. Wrap a
highlight style (or other
highlighter)
in syntaxHighlighting
to create an
extension that enables it.
import {syntaxHighlighting} from "@codemirror/language"
syntaxHighlighting(myHighlightStyle)
If you need to style tokens with plain old CSS, you can use the
classHighlightStyle
,
which just adds a static class (for example cmt-keyword
) to tokens,
without actually defining any rules for that class.
Without any custom styling, a CodeMirror editor grows vertically,
scrolls (rather than wraps) long lines, and doesn't have any border
except a focus ring when focused.
To enable line wrapping, add the
EditorView.lineWrapping
extension
to your configuration. It is also possible to adjust the white-space
style of the content element in some other way, but only pre
and
pre-wrap
are supported by the library, and wrapping can be
unreliable if you don't also set overflow-wrap: anywhere
, so it is
recommended to just use this extension to enable wrapping.
Adjusting the vertical behavior of the editor can be done by giving
its outer element a height, and setting overflow: auto
on the
scroller element.
const fixedHeightEditor = EditorView.theme({
"&": {height: "300px"},
".cm-scroller": {overflow: "auto"}
})
To let the editor grow until it reaches a maximum height, and scroll
from that point on, use max-height
instead of height
in a setup
like the one above.
Giving the editor a minimum height is, due to some obscure CSS
limitations, a bit more involved—you have to assign that height to the
content and the gutter, not the wrapper element, to make sure that
those take up the entire height of the editor.
const minHeightEditor = EditorView.theme({
".cm-content, .cm-gutter": {minHeight: "200px"}
})