Editor

Edits rich text with formatting, autosave, and a word count.

import { Editor } from "@usefragments/ui";Source

Preview

<Editor
  label="Release note"
  defaultValue="<p>Version 4 checks every pull request against <strong>one source of truth</strong>: the components and tokens your product is built from.</p><ul><li>Each finding points at one line and one rule</li><li>Fixes open as their own pull request</li></ul>"
/>

Usage

Use when

  • Blog posts and long-form articles
  • Text that needs formatting controls beside it
  • Drafts that autosave and report a word count
  • Markdown or WYSIWYG authoring

Avoid when

  • Single-line text (use Input)
  • Multi-line with no formatting (use Textarea)
  • Chat or AI prompt input (use Prompt)
  • Code editing (use CodeBlock)

Implementation notes

  • Compose Editor.Toolbar and Editor.Content to control toolbar layout; split groups with a vertical Separator
  • Rich text needs @tiptap/react, @tiptap/starter-kit, and @tiptap/extension-link
  • Without TipTap it falls back to a markdown-aware textarea
  • Give it a label (or aria-label); the placeholder is a hint, not a name
  • onAutoSave may return a Promise; the status reads Saving…, Saved or Couldn’t save
  • rows sets the writing area height in lines
  • maxLength drives the counter's warning and over-limit states
  • heading, blockquote, orderedList, undo and redo are opt-in via formats

Installation

pnpm add @usefragments/ui

Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Additional packages

  • @tiptap/react — Optional — enables rich-text mode; without TipTap the editor falls back to a markdown textarea.
  • @tiptap/starter-kit — Optional — TipTap extension bundle used by rich-text mode.
  • @tiptap/extension-link — Optional — link formatting in rich-text mode.
pnpm add @tiptap/react @tiptap/starter-kit @tiptap/extension-link

Anatomy

Import Editor once, then compose its parts with dot notation.

  • Editor.Toolbar
  • Editor.ToolbarGroup
  • Editor.ToolbarButton
  • Editor.StatusIndicator
  • Editor.Content
  • Editor.StatusBar
  • Editor.preload

Examples

With Auto-Save and Publish

Save state sits next to the primary action in the toolbar.

<Editor
  label="Post"
  placeholder="Start typing your post"
  defaultValue="<p>We moved every screen onto the new tokens this week. The checkout flow was the last holdout: <em>three</em> raw colours and a spacing value nobody could trace.</p>"
>
  <Editor.Toolbar>

Save Failed

A failed autosave says Couldn’t save in the danger ink, with an icon.

<Editor
  label="Draft"
  defaultValue="<p>Notes from the design review: the empty states need one shared illustration size, and the settings page still uses the old toggle.</p>"
>
  <Editor.Toolbar>
    <Editor.ToolbarGroup aria-label="Text formatting">

Invalid

The danger edge on the field and the reason below it.

<Editor
  label="Summary"
  placeholder="Say what changed and why"
  rows={4}
  invalid
  errorMessage="Write at least one line"

Disabled

Dimmed and not editable, but the text stays selectable.

<Editor
  label="Post"
  defaultValue="<p>This post is scheduled. Editing opens again once it is published on <strong>Monday at 9:00</strong>.</p>"
  rows={4}
  disabled
/>

Read Only

No toolbar and a dashed edge; text can be selected and copied, but never changed.

<Editor
  label="Published post"
  defaultValue="<p>Our design system now ships as one package: the components and tokens every screen is built from. Select and copy any of this text; it cannot be changed here.</p>"
  rows={4}
  readOnly
/>

Custom Toolbar

A vertical Separator splits the controls into labelled clusters.

<Editor
  label="Blog post"
  placeholder="Write your blog post"
  defaultValue="<p>An inconsistent interface is rarely one big mistake. It is a <strong>hundred small ones</strong>: a hex code pasted in a hurry, a margin nudged by two pixels, a button rebuilt because the real one was hard to find.</p>"
>
  <Editor.Toolbar>

Full Formatting

Every opt-in format switched on, with a taller writing area.

<Editor
  label="Article"
  placeholder="Write an article"
  rows={14}
  defaultValue="<h2>Adopting tokens</h2><p>Start with colour, then spacing. Replace each raw value with the token that already matches it, for example <code>--fui-text-primary</code>.</p><ol><li>Run a check to list every raw value</li><li>Accept the fixes that match a token exactly</li><li>Review the rest by hand</li></ol><blockquote><p>Most teams clear their colour findings in a single afternoon.</p></blockquote>"
>

With Character Limit

The counter warns near 280 characters and says how many over past it.

<Editor
  label="Bio"
  placeholder="Say who you are in a sentence or two"
  defaultValue="<p>Design engineer on the checkout team. I look after our component library, our tokens and the standards every screen is checked against, and I spend most Fridays turning the week’s findings into small fixes long before any of them can ever reach a customer release.</p>"
  maxLength={280}
  rows={4}

API reference

Component props
Prop
Type
Default
Description
autoSaveIntervalnumber | undefined30000Auto-save interval in milliseconds
childrenReact.ReactNodeNot set—
Content.classNamestring | undefinedNot set—
defaultValuestring | undefinedDefault value for uncontrolled usage
disabledboolean | undefinedfalseDisable the editor; the text stays selectable and copyable
errorMessagestring | undefinedNot setWords shown under the field while invalid, with a warning icon
formatsEditorFormat[] | undefined["bold", "italic", "strikethrough", "link", "code", "bulletList"]Which format buttons the default toolbar shows (one heading step: `heading`)
helperTextstring | undefinedNot setHelper text below the field; describes the text box
invalidboolean | undefinedfalseMarks the value invalid: the danger edge, aria-invalid and the error message
labelstring | undefinedNot setVisible label above the field; names the text box
maxLengthnumber | undefinedNot setMaximum character count. The status bar shows count / limit, warns at 90% and says how many over past it
onAutoSave((value: string) => void | Promise<void>) | undefinedNot setAuto-save callback, called at autoSaveInterval (may be async)
onValueChange((value: string) => void) | undefinedNot setCalled when content changes
placeholderstring | undefinedStart typing…Placeholder text shown when empty; never the accessible name
readOnlyboolean | undefinedfalseRead-only: the toolbar hides and the edge turns dashed
rowsnumber | undefined8Height of the writing area in text lines, as Textarea counts them
statusBarboolean | undefinedNot setShow the default status bar. On by default only when maxLength or onAutoSave is set
StatusBar.childrenReact.ReactNodeNot set—
StatusBar.classNamestring | undefinedNot set—
StatusBar.showCharCountboolean | undefinedNot setShow character count
StatusBar.showWordCountboolean | undefinedNot setShow word count
StatusIndicator.classNamestring | undefinedNot set—
StatusIndicator.labelsPartial<Record<EditorSaveStatus, string>> | undefinedNot setCustom labels per status
StatusIndicator.statusEditorSaveStatus | undefinedidleerrorsavingsavedNot setOverride the save status from context
toolbarboolean | undefinedtrueShow default toolbar
Toolbar.aria-labelstring | undefinedNot set—
Toolbar.childrenRequiredReact.ReactNodeNot set—
Toolbar.classNamestring | undefinedNot set—
ToolbarButton.classNamestring | undefinedNot set—
ToolbarButton.formatRequiredEditorFormatblockquotecodelinkheadingbolditalicstrikethroughbulletListorderedListundoredoNot setWhich format this button toggles
ToolbarGroup.aria-labelstring | undefinedNot set—
ToolbarGroup.childrenRequiredReact.ReactNodeNot set—
ToolbarGroup.classNamestring | undefinedNot set—
valuestring | undefinedNot setControlled editor value

Accessibility

  • Toolbar has role="toolbar" with one tab stop; arrow keys, Home and End move between its buttons
  • Format buttons use aria-pressed to indicate active state; undo and redo omit it
  • The label names the text box (aria-labelledby); helper and error text describe it; invalid sets aria-invalid
  • Status indicator uses aria-live="polite" for save status announcements
  • Keyboard shortcuts match standard text editor conventions (Ctrl+B, Ctrl+I, etc.)
  • Global shortcuts (e.g., Sidebar Ctrl+B) automatically yield when focus is inside the Editor
  • The field rings when the writing area has focus; a focused toolbar button rings itself
  • Disabled text stays selectable; read-only hides the toolbar
  • PromptsiblingPrompt is for AI/chat input; Editor is for long-form content editing
  • TextareaalternativeUse Textarea for simple multi-line input without formatting toolbar
  • InputalternativeUse Input for single-line text input

Next steps