Xzero UI
Pro library

Rich text editor

A lightweight editor with markdown shortcuts, a floating toolbar, a slash menu, and HTML and markdown output.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui/rich-text-editor
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui/rich-text-editor

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { useState } from "react";
import { RichTextEditor, type RichTextValue } from "@/components/xzero/rich-text-editor/rich-text-editor";

export function PostEditor() {
  const [doc, setDoc] = useState<RichTextValue | null>(null);
  return (
    <>
      <RichTextEditor
        aria-label="Post body"
        placeholder="Write your update"
        defaultMarkdown={"# Release notes\n\n- Faster search\n- New **dark** theme"}
        onChange={setDoc}
      />
      <button type="button" disabled={!doc || doc.empty} onClick={() => save(doc!.markdown)}>
        Publish
      </button>
    </>
  );
}

declare function save(markdown: string): void;

Examples

Controlled HTML with custom undo buttons

const editor = useRef<RichTextEditorHandle>(null);
const [html, setHtml] = useState("<p>Draft</p>");
const [history, setHistory] = useState({ canUndo: false, canRedo: false });

<>
  <button type="button" disabled={!history.canUndo} onClick={() => editor.current?.undo()}>Undo</button>
  <button type="button" disabled={!history.canRedo} onClick={() => editor.current?.redo()}>Redo</button>
  <RichTextEditor ref={editor} value={html} onChange={value => setHtml(value.html)} onHistoryChange={setHistory} />
</>

API reference

RichTextEditor

A light rich text editor on contentEditable with markdown shortcuts, a floating selection toolbar that morphs into a link field, a slash menu for blocks, its own undo history, and HTML plus Markdown output.

PropTypeDefaultDescription
valuestring–Controlled HTML. The editor only rewrites its content when this differs from what it last emitted.
defaultValuestring–Initial HTML when uncontrolled. Sanitized on load.
defaultMarkdownstring–Initial content as Markdown, used when no HTML value is given.
onChange(value: RichTextValue) => void–Called after each edit with { html, markdown, text, empty }. HTML is clean: canonical tags and only href attributes.
onHistoryChange(state: { canUndo: boolean; canRedo: boolean }) => void–Reports whether undo and redo are available, for your own toolbar.
placeholderstring"Start writing"Shown when the editor is empty.
blockHintstring"Type / for blocks"The hint on an empty line while the caret is in it.
readOnlybooleanfalseTurns off editing, the toolbar, and shortcuts.
autoFocusbooleanfalseFocuses the editor on mount with the caret at the end.
aria-labelstring"Editor"Accessible name of the textbox.
classNamestring–Extra class on the root.
styleCSSProperties–Inline style on the root.

RichTextEditorHandle

Methods on the ref.

PropTypeDefaultDescription
focus() => void–Focuses the editor.
getHTML() => string–Clean HTML of the current content.
getMarkdown() => string–Current content as Markdown.
getText() => string–Plain text, one block per line.
setHTML(html: string) => void–Replaces the content with sanitized HTML as one undo step.
setMarkdown(markdown: string) => void–Replaces the content from Markdown as one undo step.
clear() => void–Empties the editor as one undo step.
undo() => void–Steps back in the editor's own history.
redo() => void–Steps forward in the editor's own history.
elementHTMLDivElement | null–The contentEditable element.

markdownToHtml

Converts Markdown to the editor's HTML: headings (levels four to six become h3), lists with nesting, quotes, fenced code, rules, and inline bold, italic, strike, code, and links.

PropTypeDefaultDescription
markdown (required)string–Markdown source.

htmlToMarkdown

Converts editor HTML, or an element, to Markdown. Browser only.

PropTypeDefaultDescription
input (required)string | HTMLElement–HTML string or element.

sanitizeHtml

Keeps only the tags the editor understands and drops every attribute except safe link hrefs (http, https, mailto, tel, relative, hash). Bold and italic spans from other editors become marks. Browser only.

PropTypeDefaultDescription
html (required)string–Untrusted HTML.

normalizeUrl

Adds a scheme to bare input: example.com/docs becomes https://example.com/docs and an email becomes a mailto link.

PropTypeDefaultDescription
input (required)string–What the person typed.

Keyboard interactions

KeysAction
Cmd/Ctrl+BToggles bold.
Cmd/Ctrl+IToggles italic.
Cmd/Ctrl+Shift+XToggles strikethrough.
Cmd/Ctrl+EToggles inline code within one block.
Cmd/Ctrl+KOpens the link field for the selection or the link under the caret.
Cmd/Ctrl+Z / Cmd/Ctrl+Shift+Z / Cmd/Ctrl+YUndo and redo in the editor's own history. Underline (Cmd/Ctrl+U) is blocked.
/After a space or at line start, opens the block menu. Type to filter; Arrow Up and Down move, Enter or Tab chooses, Escape closes.
Tab / Shift+TabIndents or outdents a list item; in a code block Tab inserts two spaces.
Enter / Shift+EnterNew block or a line break. Enter on an empty list item or quote leaves it; two Enters at the end of a code block exit it.
BackspaceAt the start of a heading, quote, code block, or list item, turns it back into a paragraph or outdents it; after a divider, removes the divider.
Alt+F10Moves focus into the floating toolbar while it is open. Arrow Left and Right move between buttons; Escape returns to the text.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in editors

Guidance for AI tools

Rich text editor: A lightweight editor with markdown shortcuts, a floating toolbar, a slash menu, and HTML and markdown output. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components