Rich text editor

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

inputsnew
Preview
Rich text editor preview

When to use

  • Comments, notes, and posts where people expect markdown shortcuts and a selection toolbar.
  • Forms that need Markdown or clean HTML out without pulling in a full editor framework.
  • Short to medium documents with headings, lists, quotes, and code blocks.

When not to use

  • Use textarea for plain text, or mention-input when the only rich part is tagging people.
  • Use inline-edit for a single line edited in place.
  • Use code-block to display code, not to write prose.

shadcn CLI

Also installs (1)

  • manicat-foundation
Manual install(+)
registry.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "rich-text-editor",
  "type": "registry:ui",
  "title": "Rich text editor",
  "description": "A lightweight editor with markdown shortcuts, a floating toolbar, a slash menu, and HTML and markdown output.",
  "categories": [
    "inputs"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://manicat.viraza.net/r/manicat-foundation.json"
  ],
  "files": [
    {
      "path": "registry/components/rich-text-editor/rich-text-editor.tsx",
      "type": "registry:component",
      "target": "@components/manicat/rich-text-editor/rich-text-editor.tsx"
    },
    {
      "path": "registry/components/rich-text-editor/rich-text-editor.module.css",
      "type": "registry:component",
      "target": "@components/manicat/rich-text-editor/rich-text-editor.module.css"
    }
  ],
  "docs": "Docs and live preview: https://uiarc.dev/components/rich-text-editor",
  "meta": {
    "tier": "free",
    "kind": "component",
    "docs": "https://manicat.viraza.net/components/rich-text-editor",
    "markdown": "https://uiarc.dev/components/rich-text-editor/markdown",
    "tags": [
      "inputs",
      "new"
    ]
  }
}

Usage

Install the component, then copy this into your app. Change the content and the props to fit your screen.

example.tsxtsx
"use client";

import { RichTextEditor } from "@/components/manicat/rich-text-editor/rich-text-editor";

export function RichTextEditorExample() {
  return (
    <RichTextEditor
      defaultMarkdown={`# Rich text, out of the box

## Type, then press **Enter** for a new block

- An unordered list
- With a *second* item

> "A quote block keeps the pace of a paragraph."
`}
      placeholder="Start typing…"
      blockHint="Type / for a block"
    />
  );
}

Examples

Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

Release notes editor Markdown in, markdown out; the “/” hint shows the available blocks and the output is kept in state.

Release notes editor Markdown in, markdown out; the “/” hint shows the available blocks and the output is kept in state. preview

0 characters of markdown

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

  • The editing surface has role textbox with aria-multiline and aria-label, and aria-readonly when read-only.
  • The slash menu is a listbox wired to the textbox with aria-controls and aria-activedescendant.
  • The floating toolbar has role toolbar with toggle buttons using aria-pressed; titles show shortcuts.
  • A polite live region announces Formatted, Link added, Undone, and similar results.
  • Toolbar buttons prevent mousedown so the text selection survives clicks.

Motion

  • The toolbar glides with the selection and its surface springs between the formatting face and the link face, which slide and blur across each other.
  • The slash menu pops from the slash, its highlight glides between rows, and its height springs as results filter.
  • Reduced motion jumps the toolbar position and size, crossfades the faces, and fades menus without scale or offset.

Responsive behavior

  • The root is an inline-size container; under 330px wide the toolbar hides its heading and quote buttons and keeps the inline marks.
  • The toolbar and slash menu flip above or below based on the nearest scrolling or clipping ancestor, and the toolbar is clamped inside the editor width.
  • Enter, Backspace, and undo go through beforeinput, so mobile keyboards and IME composition behave the same as hardware keys.

Performance

  • Each selection change is batched to one requestAnimationFrame; each edit clones the DOM once to produce HTML, Markdown, and text for onChange.
  • Undo keeps up to 300 HTML snapshots, merging typing within one second; very long documents make each snapshot and the per-edit clone more costly.
  • No editor framework: only motion and lucide-react icons ship with it.

Notes for AI

  • Choose it for comments, notes, posts, and descriptions that need headings, lists, quotes, code, and links. Use textarea for plain text and mention-input when the only structure is @ mentions.
  • Store onChange markdown or html; both are clean. The value prop is HTML and only rewrites the DOM when it differs from the last emitted HTML, so feeding onChange html back is safe.
  • Supported blocks are p, h1 to h3, ul, ol, blockquote, pre, and hr; inline marks are strong, em, s, code, and a. Images, tables, and underline are not supported.
  • Paste runs through sanitizeHtml, and markdown-looking plain text is converted, so content from docs and other editors stays tidy.
  • Build your own undo buttons with onHistoryChange and ref.undo and ref.redo.