zudo-codemirror-wisdom

Type to search...

to open search from anywhere

Display Scale Integration

CreatedApr 3, 2026Takeshi Takatsudo

Make CodeMirror responsive to a global display scale factor by computing scaled values in JavaScript and applying them via EditorView.theme().

Display Scale Integration

This recipe shows how to make a CodeMirror editor responsive to a global display scale factor. The display scale is a multiplier (e.g., 1.0, 1.25, 1.5) that uniformly scales the editor’s font size and padding, allowing users to zoom the editor independently of the browser zoom level.

The Pattern

The approach computes scaled pixel values in JavaScript and applies them via EditorView.theme(). This is done at editor creation time, and the editor is recreated when the scale factor changes.

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

const settings = {
  fontSize: 14,
  fontFamily: "Menlo",
  lineHeight: 1.6,
  paddingHorizontal: 16,
  paddingVertical: 12,
};

const displayScale = 1.25;

const scaledFontSize = settings.fontSize * displayScale;
const scaledPaddingH = settings.paddingHorizontal * displayScale;
const scaledPaddingV = settings.paddingVertical * displayScale;

const editorTheme = EditorView.theme({
  ".cm-scroller": {
    fontFamily: `"${settings.fontFamily}", monospace`,
    fontSize: `${scaledFontSize}px`,
    lineHeight: String(settings.lineHeight),
  },
  ".cm-content": {
    padding: `${scaledPaddingV}px 0`,
  },
  ".cm-line": {
    paddingLeft: `${scaledPaddingH}px`,
    paddingRight: `${scaledPaddingH}px`,
  },
});

What Gets Scaled

PropertyBase ValueAt 1.25x
Font size14px17.5px
Horizontal padding16px20px
Vertical padding12px15px

Line height is a unitless ratio (e.g., 1.6) and does not need scaling — it automatically adjusts because it is relative to the font size.

Why Scale in JavaScript Instead of CSS?

An alternative approach would be to use a CSS transform: scale() or a CSS custom property like --display-scale with calc(). However, scaling via JavaScript and EditorView.theme() has advantages:

  • Pixel-perfect values — CodeMirror needs to know the exact font size for cursor positioning, line height calculations, and viewport measurements. CSS transforms can cause sub-pixel rendering issues.
  • No layout disruption — CSS transform: scale() does not change the element’s layout size, which breaks CodeMirror’s scroll calculations.
  • Gutter alignment — When line numbers are shown, the gutter padding must also scale. This is straightforward with computed values.
const themeRules: Record<string, Record<string, string>> = {
  ".cm-scroller": {
    fontFamily: `"${settings.fontFamily}", monospace`,
    fontSize: `${scaledFontSize}px`,
    lineHeight: String(settings.lineHeight),
  },
  ".cm-content": {
    padding: `${scaledPaddingV}px 0`,
  },
  ".cm-line": {
    paddingLeft: `${scaledPaddingH}px`,
    paddingRight: `${scaledPaddingH}px`,
  },
};

// Scale gutter padding when line numbers are visible
if (showLineNumbers) {
  themeRules[".cm-gutters"] = {
    paddingTop: `${scaledPaddingV}px`,
  };
}

const editorTheme = EditorView.theme(themeRules);

Integration with a CSS Custom Property

The rest of the application (outside CodeMirror) may use a CSS custom property for scaling:

:root {
  --display-scale: 1.25;
}

.sidebar {
  font-size: calc(13px * var(--display-scale));
}

CodeMirror does not use this CSS property directly. Instead, the JavaScript layer reads the same scale value from the settings object and computes pixel values. This keeps CodeMirror’s internal measurements accurate while the rest of the UI uses the CSS approach.

Recreating vs Reconfiguring

In this pattern, the editor is recreated when the display scale changes. This is the simplest approach because EditorView.theme() generates scoped CSS rules at creation time — they cannot be updated in place.

// React hook example
useEffect(() => {
  const editorTheme = EditorView.theme({
    ".cm-scroller": {
      fontSize: `${settings.fontSize * displayScale}px`,
    },
  });

  const view = new EditorView({
    state: EditorState.create({ doc: content, extensions: [editorTheme] }),
    parent: containerRef.current,
  });

  return () => view.destroy();
}, [settings, displayScale]);

📝 Note

Recreating the editor is appropriate when the display scale changes because it is an infrequent operation (the user adjusts it in settings, not during active typing). The cost of destroying and recreating an EditorView is low compared to the complexity of dynamically updating theme rules.

💡 Tip

When recreating the editor, preserve the document content and scroll position. Pass the current content as the initial doc, and restore scrollDOM.scrollTop after the new view is created.

Revision History