import { MarkdownEditor } 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 MarkdownEditor 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 MarkdownEditor up before writing code: `npx -y @elabs-ai/components-cli docs MarkdownEditor` 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.
Examples
Controlled
Read-only
Live directives (inline-editable)
The brand `:::` directives render as the REAL @brand components inside the editor (a live `Card`, `Alert`, `MetricBlock`), with the card/callout title and the metric label/value editable inline — not the old token-styled `toDOM` chrome. Switch the Storybook theme to confirm they track every theme.
Paste / drop image embed (onEmbedAsset)
Demonstrates `onEmbedAsset` — the paste/drop image-embed hook. The mock hook simulates a 600 ms upload delay and resolves with a data-URL so no real server is needed. To exercise it manually: 1. Copy an image file to your clipboard (or drag one onto the editor). 2. Paste / drop into the editor surface. 3. You will see the "Uploading…" chip appear, then the image render inline. 4. The markdown output below shows ``. The error variant is toggled by the "Simulate failure" button — it wires a hook that always rejects so you can observe the inline error chip + toast. Theme sweep: switch the Storybook theme (toolbar) to verify the upload chip and error chip use only semantic tokens across light/dark.
Editable table (wysiwyg-tables)
Demonstrates the table node-view feature: - Click inside a table cell → the **Table controls** toolbar appears at the bottom of the editor surface. - Toolbar buttons: Add row above/below, Delete row, Add col left/right, Delete column — all aria-labelled, keyboard-operable. - Tab moves to the next cell; Shift+Tab to the previous. - The serialized markdown (shown below the editor) stays lossless GFM. Cross-theme sweep: switch the Storybook theme toolbar to verify the table borders (`border-border-strong`), header tint (`bg-surface-muted`), and toolbar chrome use only semantic tokens across every theme.
Fills container (editor-fill-height)
Fill height (editor-fill-height): in a definite-height container, the editable must fill the whole pane — not just its content. A short (1-line) document still produces a full-height clickable surface, and the caret-anchored `/` menu has room to open near the bottom edge. Before the fix the host was content-sized (`min-height: 9rem` ≈ 144px), leaving a large dead area below the editable into which the slash menu clipped.
Code-block exit (Tab / Mod-Enter)
Code-block exit (A3): the caret can escape a fenced code block — including the trailing ```calc fence — with **Tab** or **Mod-Enter**. A code block that is the document's last child no longer traps the caret; exiting inserts a new paragraph below it. The behavior is unit-locked in `exit-keymap.test.ts`; this story is the manual playground — click inside the block and press Tab.
Iteration node menu (⋯ dropdown)
The iteration node-view's menu (#223): a `⋯` button opens "Edit iteration…" (the guided re-edit), "Change layout" (a submenu of radio items — stacked / grid / bento for `iterate` — that rewrites the `layout` attribute directly, no dialog), and "Convert to static" (replaces the directive with its populated markdown; no ProseMirror dependency). `IterationBuilderProvider` wires the `⋯`/right-click affordance on; without it neither menu renders. The `ContextMenu` story below opens the SAME item set via right-click — both read one shared list (`IterationMenuItems` in directive-views.tsx) so they can never diverge.
Iteration node menu (right-click)
The SAME iteration node menu opens on right-click — the AC's "never right-click-only" cuts both ways: the `⋯` button (story above) and the context menu (here) must expose IDENTICAL actions. Uses a `:::pivot` with real row/column values so "Transpose" (pivot-only) has data to swap.
Keyboard focus
#309 — the editable's own compound focus indicator. Resolves the governing theme element FROM THE SUBJECT (`el.closest("[data-theme]")`, never a guessed ancestor — quality-gates.md § Theme-safe), reads RESOLVED computed values (not the class string — a class-string assertion passed on the previous, invisible wrapper ring, which is exactly how this shipped), and asserts at least one of the indicator's two layers clears 3:1 against the ground the editable sits on. `markdown-editor.focus.test.ts` locks the stylesheet TEXT; this is the rendered-surface proof.
API
MarkdownEditor
| Prop | Type | Description |
|---|---|---|
value | string | Controlled markdown value. Pair with `onChange`. |
defaultValue | string | Initial markdown for uncontrolled use. |
onChange | (markdown: string) => void | Fires on every edit with the full markdown document. |
readOnly | boolean | Render the editor read-only (still selectable, not editable). |
ariaLabel | string | Accessible name for the editable surface. Milkdown/ProseMirror renders an ARIA `textbox` (`contenteditable`); without a name, screen readers announce an unlabeled field. Set onto the ProseMirror view's `attributes`. Default `"Markdown editor"`. |
slashMenu | boolean | { commands?: SlashCommand[]; trigger?: string; shortcut?: string } | Enable the `/` command menu (insert brand `:::` directives + basic blocks live at the caret). `true` (default) uses the built-in {@link BRAND_SLASH_COMMANDS}; pass a `commands` array to extend/replace them, or `false` to disable. The `trigger` defaults to `"/"`. Pass `shortcut` (default `"Mod-Shift-O"`) to control the keyboard shortcut that opens the menu at the caret without typing the trigger character — works in BOTH the WYSIWYG pane and the Monaco source pane (#271). |
calc | CalcEditorHooks | Opt-in calc authoring inside ```calc fences (off by default). Supply the consumer's hooks — `tokenize` (highlight), `evaluate` (result inlays), and/or `complete` (autocomplete; Monaco surface). The library DECORATES, the consumer COMPUTES — no calc engine is bundled. Mirrors `MarkdownPreview`'s `evaluate`. |
completions | EditorCompletionProvider[] | Declarative completion providers (#283) — e.g. `[[wikilink]]` autocomplete. Off by default; mirrors the `slashMenu`/`calc` opt-in pattern. Each provider registers a `triggerCharacters` set + a `provide(ctx)` that returns candidates; the library owns detecting the trigger and rendering/inserting. This is a DELIBERATELY MINIMAL, best-effort mirror of the Monaco source-pane behavior (see `markdown-editor/completions/completions-prose.ts` for the exact gaps — no real cross-block line/column, a plain listbox instead of a native suggest widget). Forwarded from `MarkdownWorkspace`'s `completions` prop, which also wires the FULL Monaco/source-pane path. |
onEmbedAsset | EmbedAssetFn | Host-provided callback for image paste/drop embedding. When set, the editor intercepts paste and drop events that contain image files, shows an inline "uploading…" placeholder, calls this function with the `File`, and on resolve inserts `` in the document. On reject the placeholder is removed and an inline error chip (+ toast) is shown — the document never contains a broken image. If not set, the editor's default paste/drop behavior is unchanged. The library NEVER stores assets — all persistence is the host's responsibility. |