Text editors

Text editors capture formatted, long-form input: a compound family built on Tiptap that composes a toolbar, an editable surface, and an optional label, hint, and counter from one namespace.

Demo

Change the props and copy the result. The toolbar, floating, and content picks swap fixed children; the other controls are real props. Select some of the seeded text to see the sheet’s Text highlight: the brand selection tint with the bubble menu above it. Insert an image from an advanced strip and drag its corner handles to resize it; the size persists in the document. The bare EditorButton keeps its live states in the States section below.

toolbar
content
limit
import { TextEditor } from "@/components/base/text-editor/text-editor"; <TextEditor.Root content="<p>Design like you mean it. <strong>DrumKit</strong> documents every Untitled UI component with <em>working code</em>. Select some of this text and try the toolbar.</p>" limit={275} placeholder="Write a few sentences..." className="w-150 max-w-full" inputClassName="min-h-40 resize-y" > <TextEditor.Label>Description</TextEditor.Label> <TextEditor.Toolbar type="simple" /> <div className="flex w-full flex-col gap-2"> <TextEditor.Content /> <TextEditor.HintText /> </div> <TextEditor.Tooltip /> </TextEditor.Root>

The composition in this demo is fixed

The toolbar, floating, and content controls swap fixed children rather than setting props, so the demo always renders the same composition: Label, Toolbar, Content with HintText, and Tooltip. The copied snippet carries that whole composition, not just the Root element, so it reproduces what you see.

The family

One namespace plus the standalone toolbar controls. Root owns the editor instance; everything else reads it from context and throws outside Root.

The components in this family, with their descriptions and import paths
ComponentDescriptionImport from
TextEditor.RootCreates the Tiptap editor (StarterKit plus text style, alignment, image, placeholder, and character count) and shares it via context.@/​components/​base/​text-editor/​text-editor
TextEditor.ToolbarThe prebuilt control strip: simple or advanced, docked or floating.@/​components/​base/​text-editor/​text-editor
TextEditor.ContentThe contenteditable surface bound to the context editor.@/​components/​base/​text-editor/​text-editor
TextEditor.LabelThe field label, wired to the editor id; clicking it focuses the editor. The only way to name the editor.@/​components/​base/​text-editor/​text-editor
TextEditor.HintTextThe hint line; with limit set and no children it renders a characters-left counter instead.@/​components/​base/​text-editor/​text-editor
TextEditor.TooltipThe selection bubble menu: 6 fixed controls over the selected text, dark by default.@/​components/​base/​text-editor/​text-editor
TextEditorBold / Italic / …14 standalone toolbar controls (marks, alignment, list, link, image, color, font selects, generate) for composing custom strips.@/​components/​base/​text-editor/​text-editor-extensions
EditorButtonThe bare 32px toolbar button with isActive and isDisabled; every extension control renders one.@/​components/​base/​text-editor/​text-editor-button

Toolbars

The sheet’s 3 toolbar variants, docked and floating: the simple strip carries marks, color, alignment, and list; the advanced strip adds font selects, link, image, and generate, and drops the font selects when floating. hideGenerate renders the sheet’s Advanced w/o Generate form. All 5 are live and drive a headless editor each.

simple
advanced
advanced, no generate
simple floating
advanced floating

Both strips are compositions of exported parts

Every control above is its own export from text-editor-extensions, so a custom strip is the same parts in a flex row; the Code tab shows one. The generate button is the one exception to working out of the box: it renders and disables with the rest but is wired to no command, which is why the sheet names an Advanced w/o Generate form, rendered by hideGenerate.

The composed field

Root arranges whatever parts it is given: label, strip, surface, and hint compose in any order. The counter appears when limit is set and HintText has no children; a hint sentence replaces it. The placeholder shows in an empty document only.

simple toolbar, label, counter (limit 275)
advanced toolbar
hint instead of counter (children win over limit)
empty, placeholder
selection bubble menu: select some text below

The counter renders once

The characters-left line is computed at mount and does not refresh while typing, measured with a real browser: the number above stays put as you edit, and it carries no live-region wiring either. Treat limit as advisory and validate length where the value is saved; details on the Guidelines tab.

States

Every treatment below is painted by the kit through real props: isInvalid and isDisabled on Root, isActive and isDisabled on EditorButton. Hover and focus have no prop and no exported style object to re-apply, so they are not forced here; interact with the live examples instead.

rest
invalid
disabled
button, rest
button, active
button, disabled

Focus looks different on the two layers

The surface swaps its 1px ring for a 2px brand shadow ring on any focus, mouse clicks included; the buttons draw a real outline on keyboard focus only. Both are measured on the Code tab under Accessibility.