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
Live · keyboard ready
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
Live · keyboard ready
0 characters of markdown
release-notes-editor-markdown-in-markdown-out-the-hint-shows-the-available-blocks-and-the-output-is-kept-in-state-.tsxtsx
"use client";
import { useState } from "react";
import { RichTextEditor } from "@/components/manicat/rich-text-editor/rich-text-editor";
export function RichTextEditorNotesExample() {
const [markdown, setMarkdown] = useState("");
return (
<div className="grid w-full max-w-xl gap-3">
<RichTextEditor
defaultMarkdown={"# v2.4.0\n\n- Faster builds\n- New date range picker\n"}
placeholder="Start typing…"
blockHint="Type / for a block"
onChange={(value) => setMarkdown(value.markdown)}
/>
<p className="text-xs text-(color:--text-muted)">{markdown.length} characters of markdown</p>
</div>
);
}
Keyboard interactions
| Keys | Action |
|---|---|
| Cmd/Ctrl+B | Toggles bold. |
| Cmd/Ctrl+I | Toggles italic. |
| Cmd/Ctrl+Shift+X | Toggles strikethrough. |
| Cmd/Ctrl+E | Toggles inline code within one block. |
| Cmd/Ctrl+K | Opens the link field for the selection or the link under the caret. |
| Cmd/Ctrl+Z / Cmd/Ctrl+Shift+Z / Cmd/Ctrl+Y | Undo 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+Tab | Indents or outdents a list item; in a code block Tab inserts two spaces. |
| Enter / Shift+Enter | New 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. |
| Backspace | At 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+F10 | Moves 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.