CodeEditor
An editable code surface that is visually the same component as CodeSnippet and reuses its header, tabs, actions and show-more — reach for CodeSnippet whenever the code is only read, since a page that never renders CodeEditorContent never loads the editor engine. Colours come from the same CodeSnippetAdapterProvider (plain text without one), so a read-only editor matches the snippet except for a native thin scrollbar, a caret and selection drawn by the editor, and an HTTP JSON body that stays coloured when Content-Type has parameters or the body has blank lines. The adapter re-tokenizes the whole document after each pause in typing, which stays within a frame up to about 2 000 lines with Prism; longer documents still edit smoothly, their colours just settle a moment later. Like the snippet, the editor sits inside a display: contents wrapper that keeps it mounted across fullscreen, so space editors with their own className (for example mt-16) or a gap on the parent — not space-*, divide-* or child selectors on the parent. lines, folds, completions and schema are compared by identity, so memoise them or hoist them to module constants — a new value on every render reconfigures the editor. Autocomplete suggests language keywords and words already in the document for every language; HTTP methods and headers and JSON Schema values have their own contextual suggestions. Pass completions to add application-specific results. Syntax errors are best-effort: JSON reports the JSON.parse message, JavaScript and TypeScript use Babel, Lua uses luaparse (first error only), YAML and Python use their Lezer parsers, which still flag a few valid Python forms such as lambda a, /, b and parenthesised with items. lines and folds stay on their line numbers while typing; to keep a decoration on a piece of text, recompute it from value (for example useMemo(() => linesFor(value), [value]), or a folds function such as getHttpFolds). The editor injects <style> tags, so under a strict Content Security Policy pass cspNonce. Tab indents, so keyboard users leave with Escape, then Tab, or press Ctrl+M (Alt+Shift+M on macOS) to switch Tab between indenting and moving focus — point aria-describedby at text holding CODE_EDITOR_KEYBOARD_HINT to tell screen-reader users.
Usage
Examples
With header
The CodeSnippet header works unchanged: a title and the copy, wrap and fullscreen buttons.
Copy puts the edited value on the clipboard, and fullscreen keeps the undo history.
Floating actions
Without a header, CodeSnippetActions as a direct child floats over the top-right of the
editor, as it does over a snippet.
Line numbers and starting line
lineNumbers on CodeEditorContent is the equivalent of CodeSnippetLineNumbers, and
startingLineNumber renumbers the gutter for a fragment taken from a longer file.
Line colors and prefixes
lines uses the same LineConfig as CodeSnippet: seven semantic colours, text prefixes for a
hand-made diff, and ReactNode prefixes such as a tooltipped icon. Lines are static and tied
to line numbers, so they stay on their number while you type.
Ranges
ranges colours character offsets inside a line (start inclusive, end exclusive). The line
keeps its background, and only the range gets the colour.
Http request with prism
language='http' under Prism. The colours come from the adapter and the structure (start
line, headers, JSON body) comes from the editor's own HTTP grammar.
Http response with shiki
The response side under Shiki. After an edit, highlighting is repainted in the background, so for a moment you may see the previous colours mapped through your change.
Http folds
folds as a function of the value: getHttpFolds runs again after edits, so adding a header
grows the headers fold. Collapsed state is kept by fold id. The second editor starts at
line 100, and the fold function gets the same offset.
Show more
maxLines limits the height, and the editor scrolls inside it (no rows are removed). The
button appears on its own when at least three lines are hidden. Render
CodeSnippetShowMoreButton yourself only to pass props to the real button.
Wrap
Wrapping can be uncontrolled (defaultWrapLines, driven by the wrap button) or controlled
(wrapLines plus onWrapLinesChange). Colour sticks and prefixes stay on the first visual row.
Sizes
The same three sizes as CodeSnippet: sm (12px, default), md (14px) and lg (16px), each
on 20px rows.
Read only
readOnly lets you select, search and copy without focusing the code or showing a caret. Use it when the value
can change elsewhere but not here. If it never changes, use CodeSnippet.
Tabs
Tabs swap value, and documentId swaps the editor's whole state with it, so each tab keeps
its own undo history, selection and collapsed folds.
Parity default
Parity check: the same code and props in CodeSnippet and in a read-only CodeEditor should look identical. Accepted differences: a native thin scrollbar, and CodeMirror's caret and selection layer (shown only while focused).
Parity line colors
Parity check with line colours, text prefixes and a tooltipped icon prefix: same stick, same tint, same prefix column.
Parity folds collapsed
Parity check with the headers fold collapsed: same chevrons and the same summary row in place of the hidden lines.
Parity http prism
Parity check for an HTTP request with a JSON body, both under the same Prism provider: token colours come from the same adapter.
Parity ranges
Parity check for ranges: the same bold, coloured spans inside the same tinted lines.
Parity wrap
Parity check for wrapped lines: long lines break at the same points and line numbers stay on the first row of each wrapped line.
Parity sizes
Parity check for the three sizes: the same font size, line height and gutter padding per size.
Parity frame
Parity check for the shared frame: a header with tabs and actions, and floating actions without a header, sit in the same place with the same spacing.
Parity show more
Parity check for maxLines: the same clamped height and the same "Show N more lines" button.
Languages
Colours come from the adapter (Shiki here); JavaScript, TypeScript, Python and Lua parsers load on first use.
Syntax errors
Syntax errors for every language with a structure parser: JSON and an HTTP JSON body use the
precise JSON.parse message, JavaScript and TypeScript use Babel, Lua uses luaparse (first error only), YAML and Python report
Lezer error nodes. Hover an underline to read the message; the count below each editor comes from
onDiagnosticsChange. Bash and plain text have no syntax diagnostics.
Diff
original turns on diff mode: added and changed lines read as success lines with a +,
removed lines as danger rows with a - and no line number. The rows stay editable, and the
diff updates as you type.
Diff wrap lines
With wrapping on, a removed line longer than the frame wraps inside its danger row and its
- marker stays on the first visual row, beside where the line starts.
Analytics attributes
Where analytics attributes land. data-* / aria-* / id on CodeEditorContent
reach the typing surface (role="textbox"), chrome buttons take them directly, and
fold buttons take them through toggleProps / summaryProps. Popups drawn by the
editor (autocomplete, tooltips, search panel) are not attributable — see
CodeEditor/ANALYTICS_GAPS.md.
Editing workflow
A controlled HTTP request with header and body folds and a JSON Schema on the body —
retries breaks the schema's maximum on purpose, so the underline and its hover message
are part of the example. The editor's description points at CODE_EDITOR_KEYBOARD_HINT.
Tabs keep history
Each tab passes its own documentId, so switching swaps in that document's undo history,
selection and collapsed folds instead of replacing the text of one shared document.
Long document
2 000 read-only lines clamped by maxLines — the top of the envelope the colour painter is
built for, where readOnly still lets the text be selected, searched and copied.
Fullscreen inside drawer
Fullscreen opened from a modal Drawer stacks above it: the editor and its buttons take
pointer and keyboard input, and Escape leaves fullscreen while the drawer stays open.