Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Forms

Rich Text Editor

Forms

Rich Text Editor React component for Tailwind CSS v4

Invoice line itemsSchema Builder
Live props
Behavior
format
updateDebounce150ms
bubbleMenu
markdownShortcuts
characterLimitNo limit
imageAccept
maxImageSize10485760B
autofocus
spellCheck
locale
name
Content
placeholder
label
description
Layout
toolbar
toolbarPosition
stickyToolbar
minHeight160px
maxHeightGrow
Appearance
showCount
size
variant
radius
tone
color
State
error
required
invalid
disabled
readOnly
Code
import { RichTextEditor } from "@/components/ui/rich-text-editor"

<RichTextEditor />
  1. Home
  2. Components
  3. Forms
  4. Rich Text Editor
Pro

Rich Text Editor React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React rich text editor on Tiptap with a toolbar, Markdown shortcuts, mentions, images and counts that outputs HTML, JSON or Markdown.

Type in the box; Markdown shortcuts format as you write (# and a space for a heading, - for a list, [] for a checklist, > for a quote, three backticks for code, **word** for bold), and pasted Markdown formats too. The toolbar has undo and redo, a text style menu (Paragraph, Heading 1 to 3), bold, italic, underline, strikethrough, inline code, link, the three lists, quote, code block, divider and image; the pressed state follows the caret and each button's tooltip shows its shortcut (⌘B on a Mac, Ctrl+B elsewhere). Below 640px the bar keeps undo, redo, style, bold, italic, link and the two lists and moves the rest into a ⋯ menu. Select text with a mouse and a small menu floats above it with bold, italic, strikethrough, code and link. Keys inside the editor: mod+b, mod+i, mod+u, mod+shift+s, mod+e, mod+alt+1 to 3 for headings, mod+alt+0 for a paragraph, mod+shift+7 / 8 / 9 for lists, mod+z and shift+mod+z, mod+k opens the link editor at the caret (Enter applies, an empty address removes the link, addresses without a scheme get https:// and javascript: is refused), Tab and Shift+Tab indent list items, alt+F10 jumps to the toolbar where Left and Right move between buttons, Home and End jump, and Escape returns to the text. Type @ to mention a colleague: the list follows the caret, arrows move, Enter or Tab picks, Escape closes, and the person becomes a chip. The image button opens a file picker (or asks for an address without an upload handler); pasted and dropped images upload with a progress bar in place and in the footer, and a failure removes the placeholder and is announced. With a character limit the counter turns amber at 90% and red at the limit, and typing stops there. On a phone the text is 16px so iOS never zooms, and while you type the toolbar docks above the keyboard as a horizontally scrolling rail of 44px buttons; the style menu, the ⋯ menu and the link editor open as bottom sheets. Read-only shows the same styles with no toolbar, links open in a new tab and checkboxes are locked.

Rich Text Editor is a Pro React component in the React form components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add rich-text-editor, together with Avatar, Button, Dropdown Menu, Field, Input and 12 more and the npm packages @tiptap/react, @tiptap/pm, @tiptap/core, @tiptap/starter-kit, @tiptap/extensions, @tiptap/extension-list, @tiptap/extension-image, @tiptap/extension-mention, @tiptap/suggestion, @tiptap/markdown, @radix-ui/react-popover, motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add rich-text-editor

Rich Text Editor is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import RichTextEditor from @/components/ui/rich-text-editor.

import { useState } from "react"
import { RichTextEditor, type RichTextValue } from "@/components/ui/rich-text-editor"

const team = [
  { id: "mihai-ionescu", label: "Michael Reed", description: "Sales" },
  { id: "ioana-dumitrescu", label: "Julia Davis", description: "Accounting" },
]

export function ClientNote() {
  const [note, setNote] = useState<RichTextValue>("<h2>Meeting, Sep 24, 2026</h2><p>The quote for <strong>Tech Solutions LLC</strong>.</p>")
  return (
    <RichTextEditor
      label="Client note"
      value={note}
      onValueChange={setNote}
      placeholder="Write a note…"
      showCount="both"
      characterLimit={2000}
      mentions={{ items: (q) => team.filter((p) => p.label.toLowerCase().includes(q.toLowerCase())) }}
      onImageUpload={async (file) => URL.createObjectURL(file)}
    />
  )
}

Props

  • value
    string | JSONContent

    The controlled content in `format`: an HTML string, a Markdown string, or Tiptap JSON. An empty editor is "" (HTML and Markdown). A value that differs from the last one the editor emitted (a reset, a load) replaces the content; echoing the emitted value back never resets the caret.

  • defaultValuedefault ""
    string | JSONContent

    The starting content while uncontrolled. A form reset puts it back.

  • onValueChange
    (value: string | JSONContent, editor: Editor) => void

    Fires updateDebounce ms after the last change (and at once when the editor loses focus), with the content serialized in `format`.

  • formatdefault "html"
    "html" | "json" | "markdown"

    What value, defaultValue and onValueChange speak. markdown parses and writes Markdown through @tiptap/markdown; switching at runtime carries the current document over.

  • updateDebouncedefault 150
    number

    Milliseconds between the last keystroke and onValueChange, so serializing a long document never slows typing. 0 fires on every change.

  • placeholderdefault "Write something…"
    string

    Shown while the editor is empty. An empty heading shows labels.headingPlaceholder instead.

  • toolbardefault true
    boolean | RichTextToolbarItem[]

    true shows DEFAULT_TOOLBAR, false hides it (keys and Markdown shortcuts still work), or pass the items in order with "|" between groups: "undo", "redo", "heading", "paragraph", "bold", "italic", "underline", "strike", "code", "bulletList", "orderedList", "taskList", "link", "blockquote", "codeBlock", "horizontalRule", "image".

  • toolbarPositiondefault "top"
    "top" | "bottom"

    bottom puts the toolbar under the text, the comment box look.

  • stickyToolbardefault true
    boolean

    The top toolbar sticks under the page's top bar (top: var(--topbar-offset, 0px)) while a long document scrolls with the page, and turns to frosted glass once stuck.

  • bubbleMenudefault true
    boolean

    A small menu above selected text (bold, italic, strikethrough, code, link) on mouse and trackpad screens. Phones keep their own selection menus.

  • markdownShortcutsdefault true
    boolean

    Formats as you type (# , - , [] , > , ```, **bold**) and when Markdown-looking text is pasted. Off: every character stays literal.

  • headingLevelsdefault "[1, 2, 3]"
    (1 | 2 | 3 | 4)[]

    The headings the text style menu offers and the schema allows.

  • characterLimitdefault 0
    number

    Stops input at this many characters and drives the counter's warning colors. 0 is no limit.

  • showCountdefault "none"
    "none" | "characters" | "words" | "both"

    The footer counter, in the locale's numbers ("1.240 caractere · 212 cuvinte"); with a limit it reads "1.240 / 2.000" and turns amber at 90% and red at 100%, and a screen reader hears it only when it crosses those two marks.

  • minHeightdefault 160
    number

    Minimum height of the text area in px. Before the editor mounts, the box already has this height, so nothing jumps.

  • maxHeightdefault 0
    number

    Past this height in px the text scrolls inside the box. 0 grows with the content and the page scrolls.

  • onImageUpload
    (file: File, options: { onProgress: (progress: number) => void; signal: AbortSignal }) => Promise<string>

    Uploads a picked, pasted or dropped image and resolves its URL. A dimmed preview with a progress bar holds the spot meanwhile; a rejection removes it and announces labels.uploadFailed. Without it the image button asks for an image address.

  • imageAcceptdefault "image/*"
    string

    Accepted image types, in the file input's accept syntax; pasted and dropped files are checked against it too.

  • maxImageSizedefault 10485760
    number

    Largest image in bytes (10 MB); bigger files are refused with labels.imageTooLarge. 0 accepts any size.

  • mentions
    RichTextMentions ({ items: (query: string) => MentionItem[] | Promise<MentionItem[]>; trigger?: string; onMention?: (item: MentionItem) => void })

    Turns on @mentions. items gets the text after the trigger ("@" by default); a promise is awaited with a 120ms pause between keystrokes. Rows show an Avatar, the name and a description; picked people become chips with data-id, styled by the exported MENTION classes.

  • extensionsdefault "[]"
    AnyExtension[]

    Extra Tiptap extensions, appended after the built-in ones. Read when the editor is created; a change in their names recreates it.

  • autofocusdefault false
    boolean | "start" | "end"

    Focuses the editor once it mounts, with the caret at the start or the end.

  • spellCheckdefault true
    boolean

    The browser's spell checking on the text.

  • setApi
    (api: RichTextEditorApi) => void

    Called after mount with { editor, getHTML, getJSON, getMarkdown, getText, focus(position?), clear(), insertContent(content), isEmpty() }. editor is the Tiptap instance for anything else.

  • labeldefault ""
    ReactNode

    The field label above the box; a click on it focuses the text.

  • descriptiondefault ""
    ReactNode

    Helper text under the box, linked with aria-describedby.

  • errordefault ""
    ReactNode

    Replaces the description and marks the field invalid.

  • requireddefault false
    boolean

    The asterisk after the label and aria-required on the text.

  • invaliddefault false
    boolean

    The invalid ring and aria-invalid without a message.

  • disableddefault false
    boolean

    Dims the box; the text cannot be edited or focused.

  • readOnlydefault false
    boolean

    Renders the content with the same styles and no toolbar or bubble menu: links open in a new tab, checkboxes are locked, the box keeps its variant so a note still reads as a document. To show saved HTML without an editor, put the exported PROSE classes on its wrapper.

  • sizedefault "md"
    "sm" | "md" | "lg"

    Text 14 / 15 / 16px (16px on phones at every size) with matching padding and toolbar buttons (28 / 32 / 40px).

  • variantdefault "outline"
    "soft" | "outline" | "ghost"

    outline draws a hairline (a document editor reads best framed), soft is the filled field well, ghost is borderless for a page that is the document.

  • radiusdefault "md"
    "none" | "sm" | "md" | "lg" | "full"

    Corner radius of the box as a surface: 0, 12, 16 or 24px; full falls back to lg.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    Colors the pressed toolbar buttons, mention chips, checked boxes, the upload bar, the selection and the caret.

  • colordefault ""
    string

    Any CSS color; replaces the tone.

  • localedefault ""
    string

    Locale of the counter's numbers (1,240 in en-US). Empty uses the UIProvider's, then the browser's.

  • namedefault ""
    string

    Adds a hidden input with the value in `format` (JSON stringified) for plain form posts.

  • id
    string

    The id of the editable element, which the label points to.

  • fieldProps
    Partial<FieldProps>

    Anything else for the Field wrapper: optional, labelAction, orientation, hideLabel.

  • labels
    Partial<RichTextEditorLabels>

    Every string, English by default (DEFAULT_RICH_TEXT_EDITOR_LABELS): button names (bold "Bold", heading "Text style", paragraph "Normal text", heading1 to heading4, …), link editor (linkPlaceholder, openLink, removeLink, apply, invalidLink, imageUrl), uploads (uploading, uploaded, uploadFailed, imageTooLarge, imageType), counts as functions (characters(count, formatted), words(count, formatted), limit(count, max, formatted, formattedMax), limitNear(remaining, formatted)), limitReached, mentions, noMentions, searching, toolbar, moreFormatting, content (the text's name without a label).

  • classNames
    Partial<Record<"field" | "label" | "description" | "error" | "counter" | "box" | "toolbar" | "button" | "heading" | "content" | "bubble" | "link" | "mentions" | "footer" | "count" | "docked", string>>

    Extra classes for the parts.

  • className
    string

    Extra classes for the box (the Field wrapper when a label, description or error is set).

Parts

  • DEFAULT_RICH_TEXT_EDITOR_LABELS

    constant

    Every English string the editor shows or announces: button names, the link editor, upload states, character and word counts, mentions. Spread it and override what you translate.

  • DEFAULT_TOOLBAR

    constant

    The toolbar that toolbar={true} shows: undo, redo | heading | bold, italic, underline, strike, code, link | lists and tasks | blockquote, code block, rule, image. Copy it to add or drop items.

  • PROSE

    constant

    The Tailwind classes that style the content (headings, lists, quotes, code, links, images). Put them on the wrapper of saved HTML to render it exactly like the editor, without loading Tiptap.

  • MENTION

    constant

    The classes of a mention chip, tinted by the tone. Use them on mentions in saved HTML so they match the editor.

  • normalizeUrl

    function

    Turns what people type into a safe link: keeps a scheme, makes emails mailto:, adds https:// to bare domains, and returns null for javascript:, data: and other unsafe or invalid input.

    • input*
      string

      The typed address.

    • returns
      string | null

      The link to store, or null when it is refused.

  • RichTextToolbarItem

    type

    A toolbar entry: "bold", "link", "image" and the rest, or "|" for a group divider.

  • RichTextEditorLabels

    type

    The shape of labels; counts and limits are functions that get the number and its formatted text.

Examples

Markdown in and out

format="markdown": the value is Markdown, typed shortcuts write it for you.

Email template

A reduced toolbar on the borderless ghost variant, like a mail composer.

Comment box

Toolbar at the bottom, @mentions from the team and mod+enter to post.

Read-only article

The same content styles with no toolbar; links open in a new tab.

Character limit

280 characters: the counter turns amber at 90% and red at the limit.

On a phone

While you type on a touch screen the toolbar docks above the keyboard as a scrolling rail.

Dependencies

@tiptap/react@tiptap/pm@tiptap/core@tiptap/starter-kit@tiptap/extensions@tiptap/extension-list@tiptap/extension-image@tiptap/extension-mention@tiptap/suggestion@tiptap/markdown@radix-ui/react-popovermotionlucide-reactavatarbuttondropdown-menufieldinputpopoverseparatortoggletooltipuse-controllable-stateuse-keyboard-insetuse-localeuse-media-querymotionnumber-formattonesui-config

Files

  • components/ui/rich-text-editor.tsx

FAQ

Is the React Rich Text Editor component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Rich Text Editor?

Run npx wingo-ui@latest add rich-text-editor. It also adds the Wingo UI items it builds on (Avatar, Button, Dropdown Menu, Field, Input and 12 more) and the npm packages @tiptap/react, @tiptap/pm, @tiptap/core, @tiptap/starter-kit, @tiptap/extensions, @tiptap/extension-list, @tiptap/extension-image, @tiptap/extension-mention, @tiptap/suggestion, @tiptap/markdown, @radix-ui/react-popover, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/rich-text-editor.

How does Rich Text Editor behave on phones?

On a phone the text is 16px so iOS never zooms, and while you type the toolbar docks above the keyboard as a horizontally scrolling rail of 44px buttons; the style menu, the ⋯ menu and the link editor open as bottom sheets.

See also

TextareaMention InputMarkdownPresenceComment Thread

Used in

CRM Contact Page

More React form components

Schema BuilderAddress InputBooking WidgetCSV ImportFieldForm Wizard
All 9 React form components

Articles using this component

  • React Mentions: @People and #Tags That Survive Renames

    Component guides · Oct 19, 2026 · 9 min read

  • React Rich Text Editor: Tiptap, Markdown and Mentions

    Component guides · Oct 19, 2026 · 9 min read

PreviousInvoice line itemsNext Schema Builder
Output
 

format="html" · placeholder="Write something…" · updateDebounce={150} · markdownShortcuts={true} · bubbleMenu={true} · stickyToolbar={true} · autofocus={false} · imageAccept="image/*" · maxImageSize={10485760}