Text editor components

Powerful and customizable React text editor components built for modern applications and websites. These text editors are built using TipTap and styled with Tailwind CSS.

Installation

You can add this Text editor component using our CLI or manually:

npx untitledui@latest add text-editor

Text editor examples

Below are examples and variations of this text editor component:

API reference

Import the parts and assemble them:

import { TextEditor } from "@/components/base/text-editor/text-editor";
import { TextEditorBold, TextEditorItalic } from "@/components/base/text-editor/text-editor-extensions";
 
<TextEditor.Root>
    <TextEditor.Label />
    <TextEditor.Toolbar />
    <TextEditor.Content />
    <TextEditor.HintText />
</TextEditor.Root>
 
<TextEditor.Root>
    <TextEditorBold />
    <TextEditorItalic />
    <TextEditor.Content />
</TextEditor.Root>

EditorButton

Import EditorButton from @/components/base/text-editor/text-editor-button. Renders React Aria’s Button — all of its props are supported.

EditorButton props
PropTypeDefaultDescription
isActiveboolean—Whether the formatting this button controls is applied at the cursor, which keeps the button filled and its icon darkened.
Props from React Aria (44)
EditorButton inherited props
PropTypeDefaultDescription
onClick((e: MouseEvent<FocusableElement>) => void)—Not recommended – use onPress instead. onClick is an alias for onPress provided for compatibility with other libraries. onPress provides additional event details for non-mouse interactions.
onPress((e: PressEvent) => void)—Handler that is called when the press is released over the target.
onPressChange((isPressed: boolean) => void)—Handler that is called when the press state changes.
onPressEnd((e: PressEvent) => void)—Handler that is called when a press interaction ends, either over the target or when the pointer leaves the target.
onPressStart((e: PressEvent) => void)—Handler that is called when a press interaction starts.
onPressUp((e: PressEvent) => void)—Handler that is called when a press is released over the target, regardless of whether it started on the target or not.
onHoverChange((isHovering: boolean) => void)—Handler that is called when the hover state changes.
onHoverEnd((e: HoverEvent) => void)—Handler that is called when a hover interaction ends.
onHoverStart((e: HoverEvent) => void)—Handler that is called when a hover interaction starts.
onBlur((e: FocusEvent<Element, Element>) => void)—Handler that is called when the element loses focus.
onFocus((e: FocusEvent<Element, Element>) => void)—Handler that is called when the element receives focus.
onFocusChange((isFocused: boolean) => void)—Handler that is called when the element's focus status changes.
autoFocusboolean—Whether the element should receive focus on render.
excludeFromTabOrderboolean—Whether to exclude the element from the sequential tab order. If true, the element will not be focusable via the keyboard by tabbing. This should be avoided except in rare scenarios where an alternative means of accessing the element or its functionality via the keyboard is available.
onKeyDown((e: KeyboardEvent) => void)—Handler that is called when a key is pressed.
onKeyUp((e: KeyboardEvent) => void)—Handler that is called when a key is released.
formstring—The <form> element to associate the button with. The value of this attribute must be the id of a <form> in the same document.
formActionstring | ((formData: FormData) => void | Promise<void>)—The URL that processes the information submitted by the button. Overrides the action attribute of the button's form owner.
formEncTypestring—Indicates how to encode the form data that is submitted.
formMethodstring—Indicates the HTTP method used to submit the form.
formNoValidateboolean—Indicates that the form is not to be validated when it is submitted.
formTargetstring—Overrides the target attribute of the button's form owner.
idstring—The element's unique identifier.
isDisabledboolean—Whether the button is disabled.
isPendingboolean—Whether the button is in a pending state. This disables press and hover events while retaining focusability, and announces the pending state to screen readers.
namestring—Submitted as a pair with the button's value as part of the form data.
preventFocusOnPressboolean—Whether to prevent focus from moving to the button when pressing it.Caution, this can make the button inaccessible and should only be used when alternative keyboard interaction is provided, such as ComboBox's MenuTrigger or a NumberField's increment/decrement control.
type
"button""submit""reset"
"button"The behavior of the button when used in an HTML form.
valuestring—The value associated with the button's name when it's submitted with the form data.
styleStyleOrFunction<ButtonRenderProps>—The inline style for the element. A function may be provided to compute the style based on component state.
childrenChildrenOrFunction<ButtonRenderProps>—The children of the component. A function may be provided to alter the children based on component state.
slotstring | null—A slot name for the component. Slots allow the component to receive props from a parent component. An explicit null value indicates that the local props completely override all props received from a parent.
classNameClassNameOrFunction<ButtonRenderProps>—The CSS className for the element. A function may be provided to compute the class based on component state.
renderDOMRenderFunction<"button", ButtonRenderProps>—Overrides the default DOM element with a custom render function. This allows rendering existing components with built-in styles and behaviors such as router links, animation libraries, and pre-styled components.Requirements:
  • You must render the expected element type (e.g. if <button> is expected, you cannot render an <a>).
  • Only a single root DOM element can be rendered (no fragments).
  • You must pass through props and ref to the underlying DOM element, merging with your own prop as appropriate.
aria-describedbystring—Identifies the element (or elements) that describes the object.
aria-detailsstring—Identifies the element (or elements) that provide a detailed, extended description for the object.
aria-labelstring—Defines a string value that labels the current element.
aria-labelledbystring—Identifies the element (or elements) that labels the current element.
aria-controlsstring—Identifies the element (or elements) whose contents or presence are controlled by the current element.
aria-current
falsetrue"true""false""page""step""location""date""time"
—Indicates whether this element represents the current item within a container or set of related elements.
aria-disabled
falsetrue"true""false"
—Indicates whether the element is disabled to users of assistive technology.
aria-expanded
falsetrue"true""false"
—Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed.
aria-haspopup
falsetrue"true""false""menu""listbox""tree""grid""dialog"
—Indicates the availability and type of interactive popup element, such as menu or dialog, that can be triggered by an element.
aria-pressed
falsetrue"true""false""mixed"
—Indicates the current "pressed" state of toggle buttons.

Also accepts the global HTML attributes (dir, lang, hidden, inert, translate), all standard React DOM event handlers.

TextEditor.Content

Renders EditorContent.

TextEditor.Content props
PropTypeDefaultDescription
innerRefForwardedRef<HTMLDivElement | null>——

Also accepts all standard React DOM event handlers, all standard HTML attributes, all aria-* attributes.

TextEditor.HintText

Renders HintText.

Also accepts all standard HTML attributes, all aria-* attributes, all standard React DOM event handlers.

TextEditor.Label

Renders Label.

TextEditor.Label props
PropTypeDefaultDescription
children* (required)ReactNode—The label text, rendered before the required asterisk and the tooltip trigger.
isInvalidboolean—Whether the required asterisk is tinted with the error color. When left undefined, it follows the invalid state of the surrounding field instead.
isRequiredboolean—Whether the required asterisk is shown. When left undefined, it appears only if the surrounding field is marked required.
tooltipstring—Title text for a help tooltip. Setting it renders a help icon after the label that stays interactive even when the field is disabled.
tooltipDescriptionstring—Supporting text shown under the tooltip title. Only rendered when tooltip is set.
Props from React Aria Components (4)
TextEditor.Label inherited props
PropTypeDefaultDescription
elementTypestring——
formstring——
htmlForstring——
renderDOMRenderFunction<"label", undefined>—Overrides the default DOM element with a custom render function. This allows rendering existing components with built-in styles and behaviors such as router links, animation libraries, and pre-styled components.Requirements:
  • You must render the expected element type (e.g. if <button> is expected, you cannot render an <a>).
  • Only a single root DOM element can be rendered (no fragments).
  • You must pass through props and ref to the underlying DOM element, merging with your own prop as appropriate.

Also accepts all standard HTML attributes, all aria-* attributes, all standard React DOM event handlers.

TextEditor.Root

Renders a <div> element.

TextEditor.Root props
PropTypeDefaultDescription
classNamestring—Class name applied to the wrapper that stacks the label, toolbar, content and hint text.
isDisabledboolean—Whether editing is blocked. Sets TipTap's editable option to false and dims the content area.
limitnumber—Character budget for the content. Read by TextEditor.HintText to show how many characters are left and to flag the hint once the count goes over — it does not stop typing.
placeholderstring"Write something..."Text shown while the editor is empty, through TipTap's Placeholder extension. It is suppressed inside bullet and ordered list items.
isInvalidboolean—Whether to show the editor in an error state, which puts an error ring on the content area and turns the hint text red.
childrenReactNode—Editor composition, typically TextEditor.Label, TextEditor.Toolbar, TextEditor.Content and TextEditor.HintText. They read the editor instance from context.
inputClassNamestring—Class name merged into the editable content area itself, through TipTap's editorProps.attributes.
Props from @tiptap/core (31)
TextEditor.Root inherited props
PropTypeDefaultDescription
autofocusFocusPosition—The editor's initial focus position.
contentContent—The content of the editor (HTML, JSON, or a JSON array).
coreExtensionOptions{ clipboardTextSerializer?: { blockSeparator?: string; }; /** * Options for the `tabindex` core extension. */ tabindex?: { /** * The value for the `tabindex` attribute on the editor element. */ value?: string; }; delete?: { /** * Whether the `delete` extension should be called asynchronously to avoid blocking the editor while processing deletions * @default true deletion events are called asynchronously */ async?: boolean; /** * Allows filtering the transactions that are processed by the `delete` extension. * If the function returns `true`, the transaction will be ignored. */ filterTransaction?: (transaction: Transaction) => boolean; }; }—The editor's core extension options.
editableboolean—Whether the editor is editable.
editorPropsEditorProps<any>—The editor's props.
elementElement | { mount: HTMLElement; } | ((editor: HTMLElement) => void) | null—The element to bind the editor to:
  • If an Element is passed, the editor will be mounted appended to that element.
  • If null is passed, the editor will not be mounted automatically.
  • If an object with a mount property is passed, the editor will be mounted to that element.
  • If a function is passed, it will be called with the editor's element, which should place the editor within the document.
emitContentErrorbooleanfalseIf true, the editor will emit the contentError event if invalid content is encountered but enableContentCheck is false. This lets you preserve the invalid editor content while still showing a warning or error message to the user.
enableContentCheckbooleanfalseIf true, the editor will check the content for errors on initialization. Emitting the contentError event if the content is invalid. Which can be used to show a warning or error message to the user.
enableCoreExtensionsboolean | Partial<Record<'editable' | 'clipboardTextSerializer' | 'commands' | 'focusEvents' | 'keymap' | 'tabindex' | 'drop' | 'paste' | 'delete' | 'textDirection', false>>trueDetermines whether core extensions are enabled.If set to false, all core extensions will be disabled. To disable specific core extensions, provide an object where the keys are the extension names and the values are false. Extensions not listed in the object will remain enabled.
enableExtensionDispatchTransactionbooleantrueWhether to enable extension-level dispatching of transactions. If false, extensions cannot define their own dispatchTransaction hook.
enableInputRulesEnableRules—Whether to enable input rules behavior.
enablePasteRulesEnableRules—Whether to enable paste rules behavior.
extensionsExtensions—The extensions to use.
injectCSSboolean—Whether to inject base CSS styles.
injectNoncestring—A nonce to use for CSP while injecting styles.
onBeforeCreate((props: EditorEvents["beforeCreate"]) => void)—Called before the editor is constructed.
onBlur((props: EditorEvents["blur"]) => void)—Called on blur events.
onContentError((props: EditorEvents["contentError"]) => void)—Called when the editor encounters an error while parsing the content. Only enabled if enableContentCheck is true.
onCreate((props: EditorEvents["create"]) => void)—Called after the editor is constructed.
onDelete((props: EditorEvents["delete"]) => void)—Called when content is deleted from the editor.
onDestroy((props: EditorEvents["destroy"]) => void)—Called when the editor is destroyed.
onFocus((props: EditorEvents["focus"]) => void)—Called on focus events.
onMount((props: EditorEvents["mount"]) => void)—Called when the editor is mounted.
onSelectionUpdate((props: EditorEvents["selectionUpdate"]) => void)—Called when the editor's selection is updated.
onTransaction((props: EditorEvents["transaction"]) => void)—Called after a transaction is applied to the editor.
onUnmount((props: EditorEvents["unmount"]) => void)—Called when the editor is unmounted.
onUpdate((props: EditorEvents["update"]) => void)—Called when the editor's content is updated.
parseOptionsParseOptions—The editor's content parser options.
textDirection
"ltr""rtl""auto"
undefinedThe default text direction for all content in the editor. When set to 'ltr' or 'rtl', all nodes will have the corresponding dir attribute. When set to 'auto', the dir attribute will be set based on content detection. When undefined, no dir attribute will be added.
onDrop((e: DragEvent, slice: Slice, moved: boolean) => void)—Called when content is dropped into the editor.
onPaste((e: ClipboardEvent, slice: Slice) => void)—Called when content is pasted into the editor.

TextEditor.Toolbar

Import TextEditorToolbar from @/components/base/text-editor/text-editor-toolbar. Renders a <div> element.

TextEditor.Toolbar props
PropTypeDefaultDescription
classNamestring—Class name applied to the toolbar container that holds the control buttons.
type
"simple""advanced"
"simple"Which set of controls to render. simple shows bold, italic, underline, color, left and center align, and bullet list; advanced adds font family and size selects plus link, image and generate. Right and justify align are available as standalone components but are not in either toolbar.
floatingbooleanfalseWhether to style the toolbar as a detached floating bar with its own background, padding and shadow. In the advanced toolbar it also drops the font family and size selects.
hideFontSizeboolean—Whether to hide the font size select. Only applies to the non-floating advanced toolbar, the only place that select is rendered.

TextEditor.Tooltip

Import TextEditorTooltip from @/components/base/text-editor/text-editor-tooltip. Renders BubbleMenu.

TextEditor.Tooltip props
PropTypeDefaultDescription
classNamestring—Class name applied to TipTap's bubble menu — the floating bar that appears above the current text selection.
theme
"dark""light"
"dark"Color scheme of the floating bar. dark applies the dark-mode class so it renders inverted against a light page.

TextEditorAlignCenter

Text editor button that toggles center alignment.

Renders EditorButton.

TextEditorAlignCenter props
PropTypeDefaultDescription
classNamestring—Class name applied to the button that centers the current heading or paragraph, via TipTap's TextAlign extension.

TextEditorAlignJustify

Text editor button that toggles justify alignment.

Renders EditorButton.

TextEditorAlignJustify props
PropTypeDefaultDescription
classNamestring—Class name applied to the button that justifies the current heading or paragraph, via TipTap's TextAlign extension.

TextEditorAlignLeft

Text editor button that toggles left alignment.

Renders EditorButton.

TextEditorAlignLeft props
PropTypeDefaultDescription
classNamestring—Class name applied to the button that sets left alignment on the current heading or paragraph, via TipTap's TextAlign extension.

TextEditorAlignRight

Text editor button that toggles right alignment.

Renders EditorButton.

TextEditorAlignRight props
PropTypeDefaultDescription
classNamestring—Class name applied to the button that right-aligns the current heading or paragraph, via TipTap's TextAlign extension.

TextEditorBold

Text editor button that toggles bold text.

Renders EditorButton.

TextEditorBold props
PropTypeDefaultDescription
classNamestring—Class name applied to the toggle button that adds or removes TipTap's bold mark.

TextEditorBulletList

Text editor button that toggles bullet list.

Renders EditorButton.

TextEditorBulletList props
PropTypeDefaultDescription
classNamestring—Class name applied to the button that turns the current block into a bullet list, or back into a paragraph.

TextEditorColor

Text editor button for changing the text color.

Renders React Aria’s DialogTrigger — all of its props are supported.

TextEditorColor props
PropTypeDefaultDescription
classNamestring—Class name applied to the swatch button that opens the color popover. The swatch itself is tinted with the color of the current selection.

TextEditorFontFamily

Text editor button for changing the text font family.

Renders Select.

TextEditorFontFamily props
PropTypeDefaultDescription
classNamestring—Class name merged into the font family select, after the classes that set its default width.

TextEditorFontSize

Text editor button for changing the text font size.

Renders Select.

TextEditorFontSize props
PropTypeDefaultDescription
classNamestring—Class name merged into the font size select, after the classes that set its default width.

TextEditorImage

Text editor button for inserting an image.

Renders a <input> element.

TextEditorImage props
PropTypeDefaultDescription
classNamestring"hidden"Class name applied to the button that opens the file picker and inserts the chosen file as a TipTap image node, sourced from an object URL.

TextEditorItalic

Text editor button that toggles italic text.

Renders EditorButton.

TextEditorItalic props
PropTypeDefaultDescription
classNamestring—Class name applied to the toggle button that adds or removes TipTap's italic mark.

TextEditorUnderline

Text editor button that toggles underline text.

Renders EditorButton.

TextEditorUnderline props
PropTypeDefaultDescription
classNamestring—Class name applied to the toggle button that adds or removes TipTap's underline mark.

FAQs

Please refer to our frequently asked questions page for more.

No, the text editor is a PRO component. Sign in with npx untitledui@latest login, or pass your key with --license, before running npx untitledui@latest add text-editor.

It's built on Tiptap, so it needs @tiptap/core, @tiptap/react, @tiptap/pm, @tiptap/starter-kit, @tiptap/extension-text-style, @tiptap/extension-image, @tiptap/extension-placeholder, @tiptap/extension-text-align and @floating-ui/dom. The manual installation steps above include the full install command.

TextEditor.Root accepts Tiptap editor options, so pass the initial HTML with content and read changes in onUpdate, for example onUpdate={({ editor }) => setContent(editor.getHTML())} as in the examples. Set isDisabled to true to make the editor read-only.

Set limit on TextEditor.Root and render TextEditor.HintText without children to show how many characters are left. The limit isn't enforced: users can keep typing, and the hint switches to the error style once they go over, so check the length before saving.

TextEditor.Toolbar defaults to type="simple", with bold, italic, underline, text color, alignment and bullet list buttons. type="advanced" adds font family and font size selects plus link, image and generate buttons. Set floating to give the toolbar a card style; the font selects are hidden when it floats, and hideFontSize hides only the size select.

Add TextEditor.Tooltip inside TextEditor.Root. It uses Tiptap's bubble menu to show bold, italic, underline, alignment and link buttons next to the selection. Set theme to "dark" (default) or "light".

Yes. The image button inserts the chosen file as a temporary local blob URL, so upload the file to your own storage and replace the URL before saving. The generate button has no handler, so connect it to your own logic.