import { CodeEditor } from "@elabs-ai/components-editor";npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-editorPaste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.
Add the brand-ui CodeEditor component (from @elabs-ai/components-editor) to this project. 1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`. 2. Look CodeEditor up before writing code: `npx -y @elabs-ai/components-cli docs CodeEditor` prints its real props, variants, what it pairs with and what to avoid. 3. If @elabs-ai/components-editor is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-editor` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-editor`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`. 4. Use it here: [the screen or file, and what it should show] 5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop. 6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.
Avoid
- Passing Monaco a hardcoded theme — the bridge reads the active data-theme tokens; never override it.
- Using CodeEditor for read-only display — use CodeBlock (@elabs-ai/components-ai, Shiki) for non-editable code.
- Forgetting the worker setup (import "@elabs-ai/components-editor/monaco-environment") — IntelliSense is off without it.
Works with
Used inside
Pairs with
Examples
With Toolbar
JSON (read-only)
Locks #90: Monaco's `vs`/`vs-dark` base themes ship LANGUAGE-SUFFIXED JSON scopes (`string.key.json`, `string.value.json`, `keyword.json`) that beat the bridge's shorter, unsuffixed rules in Monaco's token-theme trie (deepest scope wins) — so JSON rendered in stock VS Code colors instead of brand colors. The play function reads REAL computed colors off the rendered `.mtk*` token spans and asserts none of them is a stock JSON color, in whichever theme this story is rendered under (`globals=theme:light|dark`).
Context menu + minimap
The minimap (right-edge "mini navigation view") enabled + themed, and the brand-ui context menu (right-click) that replaces Monaco's built-in one. The play test right-clicks the editor and asserts the brand menu items render.
Accessibility (aria forwarding)
Accessibility: the editor forwards `ariaLabel` / `ariaInvalid` / `ariaDescribedBy` onto Monaco's inner screen-reader `<textarea>` (its real focusable surface) — NOT the wrapper div. This is the locking test for #34, asserted against REAL Monaco in a browser (a jsdom mock that swaps in a `<textarea>` cannot prove the real surface).
Touch keyboard proxy
Locks #554: Monaco's built-in `iPadShowKeyboard` contribution injects a bare `<textarea class="iPadShowKeyboard">` — a touch-keyboard trigger proxy, not a real input — into the editor's DOM on touch-capable (iOS/iPadOS-class) devices, with no accessible name: axe `label` (critical) on every touch-device visitor. It can't be triggered here the way it happens on a real device: Monaco gates it on `isIOS` (a UA/platform check), and this Storybook Chromium context has no touch emulation to flip that (same constraint noted in `edit-layer.stories.tsx`'s "no hasTouch" comment) — so this injects the exact node Monaco's contribution would create, straight into the REAL (non-mocked) Monaco DOM, and asserts `CodeEditor`'s `MutationObserver` hides it from the accessibility tree, same proof style as the `Accessibility` story above (a jsdom mock can't stand in for the real DOM here either).
Declarative actions
Demonstrates the `actions` prop: a declarative array of Monaco editor actions that register commands in the keybinding system and the command palette. Press `Ctrl+K` / `Cmd+K` (or open the command palette and search "Say Hi") to trigger the action. The action is memoized to avoid re-registering on every render.
API
CodeEditor
| Prop | Type | Description |
|---|---|---|
value | string | Controlled content. Pair with `onChange`. |
defaultValue | string | Initial content for uncontrolled use. |
onChange | (value: string) => void | Fires on every edit with the full document text. |
language | string | Monaco language id (e.g. "typescript", "json"). Defaults to "typescript". |
path | string | Model path/URI — drives per-file language services + diagnostics. |
readOnly | boolean | Render the editor read-only. |
height | number | string | Editor height. Number → px. Defaults to "100%" (size via the parent). |
options | monaco.editor.IStandaloneEditorConstructionOptions | Passthrough Monaco construction options (merged over the defaults). |
ariaLabel | string | Accessible name for the editor. Maps onto Monaco's `ariaLabel` construction option AND the inner screen-reader `<textarea>` (`aria-label`), so assistive tech announces a name. Spreading `aria-label` via `...props` only lands on the wrapper div and never reaches Monaco's focusable textarea — use this instead. |
ariaInvalid | boolean | Sets `aria-invalid` on Monaco's inner `<textarea>` to convey validity. |
ariaDescribedBy | string | Sets `aria-describedby` on Monaco's inner `<textarea>` (e.g. an error id). |
contextMenu | "brand" | "monaco" | "none" | Right-click menu. `"brand"` (default) replaces Monaco's built-in menu with brand-ui's `ContextMenu`; `"monaco"` keeps Monaco's themed menu; `"none"` disables it. |
onMount | (editor: MonacoCodeEditor, monacoApi: MonacoNamespace) => void | Called once the editor instance + monaco namespace are ready. |
actions | EditorAction[] | Declarative Monaco editor actions — each registers a command (run on its `keybindings`, in the command palette, and optionally the context menu via `contextMenuGroupId`). Wraps `editor.addAction`; re-registered when the array identity changes, disposed on unmount. Build keybindings with the re-exported `monaco` namespace, e.g. `keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Slash]`. Note: a context-menu entry only shows when `contextMenu="monaco"`, but the keybinding and command palette work regardless. Memoize `actions` to avoid re-registering on every render. |