Claude Code for CodeMirror 6: Modular Code Editor — Claude Skills 360 Blog
Blog / Frontend / Claude Code for CodeMirror 6: Modular Code Editor
Frontend

Claude Code for CodeMirror 6: Modular Code Editor

Published: June 22, 2027
Read time: 6 min read
By: Claude Skills 360

CodeMirror 6 is a modular, mobile-ready code editor — EditorState.create({ doc, extensions: [javascript(), oneDark, keymap.of(defaultKeymap)] }) creates editor state. new EditorView({ state, parent: containerEl }) mounts the editor. EditorView.updateListener.of((update) => { if (update.docChanged) onChange(update.state.doc.toString()) }) fires on changes. Language packs: javascript({ typescript: true }), python(), sql(), markdown(), json(). Themes: oneDark, @codemirror/theme-github-light (third-party). keymap.of([{ key: "Mod-Enter", run: handleRun }]) adds keyboard shortcuts. StateEffect.define() + StateField.define() creates custom state. Decoration.mark({ attributes: { class } }) highlights ranges. autocompletion({ override: [myCompletions] }) adds custom autocomplete. linter(async (view) => diagnostics) adds live linting. compartment.reconfigure(language) hot-swaps extensions. Claude Code generates CodeMirror editors, SQL consoles, note editors, and inline code fields.

CLAUDE.md for CodeMirror 6

## CodeMirror 6 Stack
- Version: @codemirror/view >= 6.35, @codemirror/state >= 6.5
- Bundles: @codemirror/basic-setup OR individual packages
- React: const editorRef = useRef<HTMLDivElement>(null); const viewRef = useRef<EditorView>(); useEffect(() => { const view = new EditorView({ state: EditorState.create({ doc, extensions }), parent: editorRef.current! }); viewRef.current = view; return () => view.destroy() }, [])
- Change value: viewRef.current?.dispatch({ changes: { from: 0, to: view.state.doc.length, insert: newValue } })
- Language: import { javascript } from "@codemirror/lang-javascript"; use in extensions array
- Compartment: const langCompartment = new Compartment(); extensions: [langCompartment.of(javascript())]; swap: view.dispatch({ effects: langCompartment.reconfigure(python()) })

useCodeMirror Hook

// lib/hooks/useCodeMirror.ts — CodeMirror 6 React integration
import { useEffect, useRef, type RefObject } from "react"
import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine } from "@codemirror/view"
import { EditorState, Compartment } from "@codemirror/state"
import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands"
import { indentOnInput, bracketMatching, foldGutter, foldKeymap } from "@codemirror/language"
import { closeBrackets, closeBracketsKeymap, autocompletion, completionKeymap } from "@codemirror/autocomplete"
import { searchKeymap, highlightSelectionMatches } from "@codemirror/search"
import { oneDark } from "@codemirror/theme-one-dark"
import type { Extension, Transaction } from "@codemirror/state"
import type { LanguageSupport } from "@codemirror/language"

type UseCodeMirrorOptions = {
  value: string
  onChange?: (value: string) => void
  language?: LanguageSupport
  theme?: "dark" | "light"
  readOnly?: boolean
  placeholder?: string
  extraExtensions?: Extension[]
}

export function useCodeMirror(options: UseCodeMirrorOptions): RefObject<HTMLDivElement | null> {
  const {
    value,
    onChange,
    language,
    theme = "dark",
    readOnly = false,
    extraExtensions = [],
  } = options

  const containerRef = useRef<HTMLDivElement | null>(null)
  const viewRef = useRef<EditorView | null>(null)
  const onChangeRef = useRef(onChange)
  onChangeRef.current = onChange

  // Track external value changes without recreating the editor
  const externalValueRef = useRef(value)

  useEffect(() => {
    if (!containerRef.current) return

    const updateListener = EditorView.updateListener.of((update: { docChanged: boolean; state: { doc: { toString: () => string } } }) => {
      if (update.docChanged) {
        const newValue = update.state.doc.toString()
        externalValueRef.current = newValue
        onChangeRef.current?.(newValue)
      }
    })

    const baseExtensions: Extension[] = [
      lineNumbers(),
      highlightActiveLineGutter(),
      highlightSpecialChars(),
      history(),
      foldGutter(),
      drawSelection(),
      dropCursor(),
      EditorState.allowMultipleSelections.of(true),
      indentOnInput(),
      bracketMatching(),
      closeBrackets(),
      autocompletion(),
      rectangularSelection(),
      crosshairCursor(),
      highlightActiveLine(),
      highlightSelectionMatches(),
      keymap.of([
        ...closeBracketsKeymap,
        ...defaultKeymap,
        ...searchKeymap,
        ...historyKeymap,
        ...foldKeymap,
        ...completionKeymap,
        indentWithTab,
      ]),
      EditorState.readOnly.of(readOnly),
      updateListener,
      ...(theme === "dark" ? [oneDark] : []),
      ...(language ? [language] : []),
      ...extraExtensions,
    ]

    const state = EditorState.create({ doc: value, extensions: baseExtensions })
    const view = new EditorView({ state, parent: containerRef.current })
    viewRef.current = view

    return () => {
      view.destroy()
      viewRef.current = null
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [theme, readOnly, language]) // Recreate only when theme/readOnly/language changes

  // Sync external value changes without recreating the editor
  useEffect(() => {
    const view = viewRef.current
    if (!view || value === externalValueRef.current) return

    externalValueRef.current = value
    view.dispatch({
      changes: { from: 0, to: view.state.doc.length, insert: value },
    })
  }, [value])

  return containerRef
}

Code Editor Component

// components/editor/CodeEditor.tsx — multi-language CodeMirror editor
"use client"
import { useState, useMemo } from "react"
import { javascript } from "@codemirror/lang-javascript"
import { python } from "@codemirror/lang-python"
import { sql } from "@codemirror/lang-sql"
import { json } from "@codemirror/lang-json"
import { markdown } from "@codemirror/lang-markdown"
import { css } from "@codemirror/lang-css"
import { html } from "@codemirror/lang-html"
import type { LanguageSupport } from "@codemirror/language"
import { useCodeMirror } from "@/lib/hooks/useCodeMirror"

type Lang = "typescript" | "javascript" | "python" | "sql" | "json" | "markdown" | "css" | "html"

const LANG_MAP: Record<Lang, () => LanguageSupport> = {
  typescript: () => javascript({ typescript: true, jsx: true }),
  javascript: () => javascript({ jsx: true }),
  python,
  sql,
  json,
  markdown,
  css,
  html,
}

const LANG_PLACEHOLDERS: Partial<Record<Lang, string>> = {
  sql: "SELECT * FROM users WHERE active = true;",
  json: '{ "key": "value" }',
  python: "def hello():\n    print('Hello, World!')",
}

interface CodeEditorProps {
  value: string
  onChange: (value: string) => void
  language?: Lang
  readOnly?: boolean
  theme?: "dark" | "light"
  height?: string
  showLanguagePicker?: boolean
}

export function CodeEditor({
  value,
  onChange,
  language: initialLang = "typescript",
  readOnly = false,
  theme = "dark",
  height = "300px",
  showLanguagePicker = true,
}: CodeEditorProps) {
  const [language, setLanguage] = useState<Lang>(initialLang)

  const languageExt = useMemo(() => LANG_MAP[language](), [language])

  const containerRef = useCodeMirror({
    value,
    onChange,
    language: languageExt,
    theme,
    readOnly,
  })

  const bgColor = theme === "dark" ? "#282c34" : "#fafafa"

  return (
    <div className="rounded-xl overflow-hidden border">
      {showLanguagePicker && (
        <div
          className="flex items-center gap-2 px-3 py-2 border-b text-xs"
          style={{ backgroundColor: bgColor, borderColor: theme === "dark" ? "#3d3d3d" : "#e5e7eb" }}
        >
          <span style={{ color: theme === "dark" ? "#abb2bf" : "#6b7280" }}>Language:</span>
          <select
            value={language}
            onChange={(e) => setLanguage(e.target.value as Lang)}
            className="text-xs border rounded px-1.5 py-0.5 focus:outline-none"
            style={{
              backgroundColor: theme === "dark" ? "#3d3d3d" : "#f3f4f6",
              color: theme === "dark" ? "#abb2bf" : "#374151",
              borderColor: theme === "dark" ? "#555" : "#d1d5db",
            }}
          >
            {(Object.keys(LANG_MAP) as Lang[]).map((l) => (
              <option key={l} value={l}>{l}</option>
            ))}
          </select>

          {readOnly && (
            <span style={{ color: theme === "dark" ? "#6b7280" : "#9ca3af" }} className="ml-auto">
              Read only
            </span>
          )}
        </div>
      )}

      <div
        ref={containerRef}
        style={{
          height,
          overflowY: "auto",
          fontSize: "13px",
        }}
      />
    </div>
  )
}

Linting Extension

// lib/editor/linting.ts — CodeMirror linter with diagnostics
import { linter, type Diagnostic } from "@codemirror/lint"
import type { EditorView } from "@codemirror/view"

// JSON linter that highlights parse errors
export const jsonLinter = linter((view: EditorView): Diagnostic[] => {
  const text = view.state.doc.toString()
  if (!text.trim()) return []

  try {
    JSON.parse(text)
    return []
  } catch (e: unknown) {
    const message = e instanceof SyntaxError ? e.message : "JSON parse error"
    // Parse position from error message like "Unexpected token } in JSON at position 42"
    const posMatch = message.match(/position (\d+)/)
    const pos = posMatch ? parseInt(posMatch[1]) : 0

    return [{
      from: Math.min(pos, text.length - 1),
      to: Math.min(pos + 1, text.length),
      severity: "error",
      message,
    }]
  }
})

For the Monaco Editor alternative when full VS Code feature parity, TypeScript IntelliSense with type-checking in the browser, a richer built-in diff editor, or language server protocol support is required — Monaco is heavier (~2MB) but provides the most complete IDE editing experience, while CodeMirror 6 is lighter (tree-shakeable to ~100KB), better on mobile, and more extensible at the core architecture level, see the Monaco Editor guide. For the Ace Editor alternative when supporting legacy browsers without ES modules is required or an older codebase already depends on Ace — Ace offers broad browser compatibility while CodeMirror 6 requires modern environments with good module support, see the Ace guide. The Claude Skills 360 bundle includes CodeMirror 6 skill sets covering language packs, custom extensions, and linting. Start with the free tier to try lightweight editor generation.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free