Rich Text Editor
A WYSIWYG editor for formatted prose: headings, bold, italic, underline, links and lists. Built on Tiptap for selection, undo and paste, with every pixel of UI owned as a Koala tv recipe.
Installation#
Usage#
import {
RichTextEditor,
RichTextEditorToolbar,
RichTextEditorContent,
} from "@/components/ui/rich-text-editor"
export function Example() {
return (
<RichTextEditor
defaultValue="<p>Hello, world.</p>"
onChange={(html) => console.log(html)}
>
<RichTextEditorToolbar />
<RichTextEditorContent />
</RichTextEditor>
)
}Placeholder#
An empty editor shows muted ghost text. Set it with the placeholder prop.
A complete composer#
Everything together: a seeded document, the full toolbar, the on-selection bubble, a scrollable writing area, and a status bar that lives inside the editor surface: live word/character counts (tabular-nums, so they never reflow) and a save action with transient confirmation. The status bar is just a child that reads the editor via useRichTextEditor.
Selection bubble menu#
Drop in RichTextEditorBubbleMenu and a floating toolbar surfaces over the current selection. Double-click a word or drag across a phrase and it appears above it (Medium / Notion style). It pairs with the top toolbar or replaces it entirely. Its link control expands inline so the selection-driven bubble never dismisses while you type the URL.
Or go chromeless: omit the top toolbar and let the bubble be the only formatting UI.
Density#
Like the rest of Koala, the editor honors density. compact tightens the writing area for dense app surfaces. The toolbar sits outside that axis: its 8px padding is what keeps the controls' 12px corners concentric inside the 20px frame, and a format bar that shrank with form density would get hard to hit, so the controls hold 32px either way. Set it per instance or once with a DensityProvider.
Read-only#
Pass editable={false} and omit the toolbar to render stored content as styled prose, useful for previews and feeds.
Custom toolbar#
With no children the toolbar renders the essential controls. For a custom set, pass RichTextEditorButtons and reach the live editor with the useRichTextEditor hook (pair it with Tiptap's useEditorState to reflect active formatting).
Prose scale#
Inside the editor, blocks render on a document scale tuned for reading: the heading steps mirror Tailwind's @tailwindcss/typography prose-sm proportions, scaled to the 14px body (no plugin dependency). Headings run text-3xl → text-sm in the strong foreground, while body copy stays the muted secondary color, so a document reads calm with only its headings and emphasis lifted. These sizes are fixed by the component. For the raw scale they draw from, see the Typography foundation.
H1
text-3xl · 30px
Heading one
semibold · tight
H2
text-xl · 20px
Heading two
semibold · tight
H3
text-lg · 18px
Heading three
semibold
H4
text-sm · 14px
Heading four
semibold
Body
text-sm · 14px
Body copy reads in the secondary text color, so only headings and emphasis carry the strong foreground.
relaxed · muted