Skip to content

NativeRichTextEditor Reference

Jayden Smith edited this page May 5, 2026 · 5 revisions

Props

interface NativeRichTextEditorProps {
  initialContent?: string;
  initialJSON?: DocumentJSON;
  value?: string;
  valueJSON?: DocumentJSON;
  valueJSONRevision?: string | number;
  schema?: SchemaDefinition;
  placeholder?: string;
  editable?: boolean;
  maxLength?: number;
  autoFocus?: boolean;
  heightBehavior?: NativeRichTextEditorHeightBehavior;
  showToolbar?: boolean;
  toolbarPlacement?: NativeRichTextEditorToolbarPlacement;
  toolbarItems?: readonly EditorToolbarItem[];
  onToolbarAction?: (key: string) => void;
  onRequestLink?: (context: LinkRequestContext) => void;
  onRequestImage?: (context: ImageRequestContext) => void;
  autoDetectLinks?: boolean;
  allowBase64Images?: boolean;
  allowImageResizing?: boolean;
  onContentChange?: (html: string) => void;
  onContentChangeJSON?: (json: DocumentJSON) => void;
  onSelectionChange?: (selection: Selection) => void;
  onActiveStateChange?: (state: ActiveState) => void;
  onHistoryStateChange?: (state: HistoryState) => void;
  onFocus?: () => void;
  onBlur?: () => void;
  style?: StyleProp<ViewStyle>;
  containerStyle?: StyleProp<ViewStyle>;
  theme?: EditorTheme;
  addons?: EditorAddons;
  remoteSelections?: readonly RemoteSelectionDecoration[];
}

Prop Table

Prop Type Default Description
initialContent string Initial uncontrolled HTML content.
initialJSON DocumentJSON Initial uncontrolled JSON content. If you pass { type: 'doc', content: [] }, the editor normalizes it to a schema-valid empty text block for the active schema.
value string Controlled HTML content. Highest-priority content source.
valueJSON DocumentJSON Controlled JSON content. Ignored when value is set. Empty root docs are normalized the same way as initialJSON. In collaboration mode, bind this from useYjsCollaboration().editorBindings.valueJSON rather than app-owned document state.
valueJSONRevision string | number Revision hint paired with valueJSON. Pass a stable version or change counter so the editor can skip re-serialization when equivalent documents are recreated on rerender.
schema SchemaDefinition tiptapSchema Schema definition passed to the Rust core.
placeholder string Placeholder text shown when the editor is empty. On native platforms it follows the effective paragraph text style for font family, weight, and size.
editable boolean true Enables or disables editing.
maxLength number Character limit enforced by the Rust core.
autoFocus boolean false Focuses the editor when the native view first mounts.
heightBehavior 'fixed' | 'autoGrow' 'autoGrow' fixed scrolls internally. autoGrow expands the view to fit content; use it inside a parent-managed scroll container.
showToolbar boolean true Shows or hides the built-in toolbar.
toolbarPlacement 'keyboard' | 'inline' 'keyboard' keyboard attaches the toolbar as a native keyboard accessory (iOS) or above-keyboard view (Android). inline renders the toolbar in React above the editor.
toolbarItems readonly EditorToolbarItem[] DEFAULT_EDITOR_TOOLBAR_ITEMS Ordered toolbar button configuration. The default set includes blockquote. Link and image items are supported, but the package does not ship a URL prompt or file picker — you provide those. Use group items to collapse several actions behind one button.
onToolbarAction (key: string) => void Callback for action-type toolbar items.
onRequestLink (context: LinkRequestContext) => void Called when a toolbar link item is pressed. Collect, edit, or clear the URL via the supplied context.
onRequestImage (context: ImageRequestContext) => void Called when a toolbar image item is pressed. Run your picker/upload flow, then call insertImage(...) with the chosen URL or base64 data URI.
autoDetectLinks boolean false When enabled, typed or pasted plain URLs such as https://example.com or www.example.com are converted into real link marks automatically.
allowBase64Images boolean false Opt-in support for data:image/... sources when inserting images imperatively or parsing HTML. Mirrors Tiptap's allowBase64 behavior.
allowImageResizing boolean true When true, selected images expose native drag handles on iOS and Android. Setting false disables the resize interaction; images still render and insert normally.
onContentChange (html: string) => void Called when the document HTML changes.
onContentChangeJSON (json: DocumentJSON) => void Called when the document JSON changes.
onSelectionChange (selection: Selection) => void Called when the selection changes.
onActiveStateChange (state: ActiveState) => void Called when active marks, nodes, commands, or schema availability change.
onHistoryStateChange (state: HistoryState) => void Called when undo or redo availability changes. Useful when driving a standalone EditorToolbar.
onFocus () => void Called when the editor gains focus.
onBlur () => void Called when the editor loses focus.
style StyleProp<ViewStyle> Style applied to the native editor view itself. Does not affect internal content styling.
containerStyle StyleProp<ViewStyle> Style applied to the outer React container that wraps the editor and any inline toolbar.
theme EditorTheme Theme object for content, mentions, and toolbar styling. See EditorTheme Reference.
addons EditorAddons Optional addon configuration. Currently supports the mentions addon (query/selection callbacks and selection-time attr resolution). See Mentions Guide.
remoteSelections readonly RemoteSelectionDecoration[] Remote awareness selections rendered as native overlays. Used by the collaboration module.

Placeholder Behavior

  • The native placeholder uses the resolved paragraph text style from theme, so font family, weight, and size stay aligned with normal empty-paragraph rendering.
  • The placeholder still uses the platform hint color rather than the paragraph text color.
  • On Android, heightBehavior="autoGrow" measures wrapped placeholder lines while the editor is empty, so a multiline placeholder can expand the view before any content is entered.

JSON Normalization

  • Whole-document JSON entry points normalize { type: 'doc', content: [] } to a schema-valid empty document for the active schema. This applies to initialJSON, controlled valueJSON, and imperative replacements such as setContentJson().
  • Fragment insertion APIs like insertContentJson() are not rewritten — they insert exactly what you pass in.
  • Pair valueJSON with valueJSONRevision to skip redundant serialization when the parent recreates equivalent documents on rerender.

RemoteSelectionDecoration

interface RemoteSelectionDecoration {
  clientId: number;
  anchor: number;
  head: number;
  color: string;
  name?: string;
  avatarUrl?: string;
  isFocused?: boolean;
}
Field Type Meaning
clientId number Unique client identifier for this remote peer.
anchor number Anchor position of the remote selection in the document.
head number Head position of the remote selection in the document.
color string Color used to render the remote selection highlight and caret.
name string | undefined Display name shown alongside the remote caret.
avatarUrl string | undefined URL of the remote user's avatar image.
isFocused boolean | undefined Whether the remote user's editor is currently focused.

Hyperlinks

  • Link application is host-driven. Add a toolbar item with { type: 'link', ... } and handle URL entry in onRequestLink.
  • LinkRequestContext reports the current link state, including the active href when the selection sits inside a link.
  • You can also apply or remove links imperatively via the editor ref: setLink(href) and unsetLink().
  • autoDetectLinks is a separate opt-in, off by default. When enabled, the editor detects http(s)://… and www.… text near a collapsed cursor and applies a normal link mark with the detected href (www. is normalized to https://www.…). Detection is skipped if the current schema does not allow the link mark at that position.
  • Autolink detection only runs on live editing updates (typing, paste). It does not rewrite controlled value or valueJSON updates coming from the host.
import { useState } from 'react';
import {
  NativeRichTextEditor,
  type LinkRequestContext,
} from '@apollohg/react-native-prose-editor';

export function EditorWithLinks() {
  const [pendingLink, setPendingLink] = useState<LinkRequestContext | null>(null);
  const [linkDraft, setLinkDraft] = useState('');

  const toolbarItems = [
    { type: 'mark', mark: 'bold', label: 'Bold', icon: { type: 'default', id: 'bold' } },
    { type: 'link', label: 'Link', icon: { type: 'default', id: 'link' } },
  ] as const;

  return (
    <>
      <NativeRichTextEditor
        showToolbar
        toolbarItems={toolbarItems}
        onRequestLink={(context) => {
          setPendingLink(context);
          setLinkDraft(context.href ?? 'https://');
        }}
      />
      <LinkEditorModal
        visible={pendingLink != null}
        value={linkDraft}
        onChangeText={setLinkDraft}
        onCancel={() => setPendingLink(null)}
        onSubmit={() => {
          if (!pendingLink) return;
          const nextHref = linkDraft.trim();
          if (nextHref === '') {
            pendingLink.unsetLink();
          } else {
            pendingLink.setLink(nextHref);
          }
          setPendingLink(null);
        }}
      />
    </>
  );
}

LinkEditorModal is app-owned UI. The editor supplies only the request context and the setLink/unsetLink methods.

LinkRequestContext

interface LinkRequestContext {
  href?: string;
  isActive: boolean;
  selection: Selection;
  setLink: (href: string) => void;
  unsetLink: () => void;
}
Field Type Meaning
href string | undefined The current link target when the selection is inside an active link.
isActive boolean Whether the current selection is already linked.
selection Selection Current editor selection when the link request is triggered.
setLink (href: string) => void Apply or update the link on the current selection.
unsetLink () => void Remove the link from the current selection.

Images

  • Add a toolbar item with { type: 'image', ... } and handle picking or uploading in onRequestImage.
  • Finish the host flow by calling insertImage(src, attrs?).
  • src can be a remote URL, a local file URL, or a data:image/... URI when allowBase64Images is enabled.
  • The built-in schemas include a block image node with src, alt, title, width, and height attrs.
  • Selected images expose native drag handles on iOS and Android; resizing writes the new size back to width and height. Set allowImageResizing={false} to lock the size after insertion.
  • You can also insert images imperatively via the editor ref: insertImage(src, attrs?).
const toolbarItems = [
  { type: 'mark', mark: 'bold', label: 'Bold', icon: { type: 'default', id: 'bold' } },
  { type: 'image', label: 'Image', icon: { type: 'default', id: 'image' } },
] as const;

<NativeRichTextEditor
  showToolbar
  toolbarItems={toolbarItems}
  allowBase64Images={false}
  onRequestImage={({ insertImage }) => {
    const uploadedUrl = 'https://cdn.example.com/cat.png';
    insertImage(uploadedUrl, { alt: 'Cat', title: 'Hero image' });
  }}
/>;

ImageRequestContext

interface ImageRequestContext {
  selection: Selection;
  allowBase64: boolean;
  insertImage: (
    src: string,
    attrs?: {
      alt?: string | null;
      title?: string | null;
      width?: number | null;
      height?: number | null;
    }
  ) => void;
}
Field Type Meaning
selection Selection Current editor selection when the image request is triggered.
allowBase64 boolean Whether this editor instance accepts data:image/... sources.
insertImage (src: string, attrs?: { alt?: string | null; title?: string | null; width?: number | null; height?: number | null }) => void Inserts a block image at the captured selection. width and height seed the rendered size and are updated on native resize.

Collaboration Usage

  • NativeRichTextEditor is the editor component used in collaboration mode.
  • Wire valueJSON, onContentChangeJSON, selection/focus callbacks, and remoteSelections straight from useYjsCollaboration().editorBindings.
  • Do not layer a second app-owned controlled valueJSON over the collaboration bindings on the same editor.
  • Render remote awareness cursors through remoteSelections — do not map them onto the local selection manually.

See the Collaboration Guide for the full pattern.

Height Behavior

type NativeRichTextEditorHeightBehavior = 'fixed' | 'autoGrow';
Value Behavior
fixed The editor has a fixed height and scrolls internally.
autoGrow The editor grows vertically to fit its content. Use this when the editor is inside a parent ScrollView. This is the default.

Keyboard Avoidance Notes

  • autoGrow is for parent-managed scroll containers. Even inside a ScrollView or FlatList, you still need screen-level keyboard avoidance (e.g. KeyboardAvoidingView).
  • While empty, autoGrow sizes to the larger of the content height and placeholder height. This matters most for multiline placeholders on Android.
  • fixed scrolls inside the native editor. It handles its own viewport and caret visibility, but the outer screen still needs to respond to the keyboard when the editor sits low on the page.
  • On Android with toolbarPlacement="keyboard", the built-in toolbar renders above the keyboard. The editor reserves the obscured bottom space internally, but that does not replace outer layout avoidance for the rest of the screen.
  • When combining KeyboardAvoidingView with toolbarPlacement="keyboard" on Android, set a keyboardVerticalOffset that accounts for any space you reserve above the keyboard.

Caret Geometry

getCaretRect() measures the current caret, or the end edge of a non-collapsed selection, in editor-local React Native layout units.

interface NativeRichTextEditorCaretRect {
  x: number;
  y: number;
  width: number;
  height: number;
  editorWidth: number;
  editorHeight: number;
}

The method resolves null when native layout or a caret position is not available yet. For an auto-grow editor inside a parent ScrollView, compare y + height with editorHeight to decide whether the caret is near the bottom of the editor.

const caret = await editorRef.current?.getCaretRect();

if (caret && caret.y + caret.height >= caret.editorHeight - 24) {
  scrollViewRef.current?.scrollToEnd({ animated: true });
}

Toolbar Placement

type NativeRichTextEditorToolbarPlacement = 'keyboard' | 'inline';
Value Behavior
keyboard The toolbar is attached as a native keyboard accessory (iOS) or rendered above the keyboard (Android). This is the default.
inline The toolbar is rendered in React above the editor view. Use this when you need the toolbar visible without the keyboard.

Layout Responsibility

  • toolbarPlacement="keyboard" only controls where the formatting toolbar sits. It does not make the surrounding screen keyboard-aware.
  • When the editor lives inside a longer form or settings page, treat keyboard avoidance as a screen concern and editor scrolling/caret visibility as an editor concern.
  • On Android, screen-level keyboard avoidance must account for both the IME and the native toolbar. Without an extra offset, the layout can avoid the keyboard while the toolbar still overlaps the focused editor area.

Ref Methods

interface NativeRichTextEditorRef {
  focus(): void;
  blur(): void;
  toggleMark(markType: string): void;
  setLink(href: string): void;
  unsetLink(): void;
  toggleBlockquote(): void;
  toggleHeading(level: 1 | 2 | 3 | 4 | 5 | 6): void;
  toggleList(listType: 'bulletList' | 'orderedList'): void;
  indentListItem(): void;
  outdentListItem(): void;
  insertNode(nodeType: string): void;
  insertImage(
    src: string,
    attrs?: {
      alt?: string | null;
      title?: string | null;
      width?: number | null;
      height?: number | null;
    }
  ): void;
  insertText(text: string): void;
  insertContentHtml(html: string): void;
  insertContentJson(doc: DocumentJSON): void;
  setContent(html: string): void;
  setContentJson(doc: DocumentJSON): void;
  getContent(): string;
  getContentJson(): DocumentJSON;
  getTextContent(): string;
  getCaretRect(): Promise<NativeRichTextEditorCaretRect | null>;
  undo(): void;
  redo(): void;
  canUndo(): boolean;
  canRedo(): boolean;
}
Method Arguments Returns Description
focus() void Focuses the editor.
blur() void Blurs the editor.
toggleMark(markType) markType: string void Toggles a mark by schema name.
setLink(href) href: string void Apply or update a hyperlink on the current selection.
unsetLink() void Remove a hyperlink from the current selection.
toggleBlockquote() void Wrap or unwrap the current block selection in a blockquote.
toggleHeading(level) level: 1 | 2 | 3 | 4 | 5 | 6 void Toggles the current text block selection between the requested heading and paragraph.
toggleList(listType) listType: 'bulletList' | 'orderedList' void Toggles a bullet or ordered list.
indentListItem() void Indents the current list item.
outdentListItem() void Outdents the current list item.
insertNode(nodeType) nodeType: string void Inserts a node by schema name.
insertImage(src, attrs?) src: string, attrs?: { alt?: string | null; title?: string | null; width?: number | null; height?: number | null } void Inserts a block image node. Base64 data URIs require allowBase64Images={true}. width and height let hosts seed a preferred size.
insertText(text) text: string void Inserts plain text at the current selection.
insertContentHtml(html) html: string void Inserts parsed HTML at the current selection.
insertContentJson(doc) doc: DocumentJSON void Inserts JSON content at the current selection.
setContent(html) html: string void Replaces the entire document with HTML.
setContentJson(doc) doc: DocumentJSON void Replaces the entire document with JSON.
getContent() string Returns the current HTML.
getContentJson() DocumentJSON Returns the current JSON.
getTextContent() string Returns the current plain text content.
getCaretRect() Promise<NativeRichTextEditorCaretRect | null> Resolves the current caret rectangle in editor-local layout coordinates, or null if native layout or a caret position is unavailable.
undo() void Performs an undo.
redo() void Performs a redo.
canUndo() boolean Whether undo is available.
canRedo() boolean Whether redo is available.

Related Docs

Clone this wiki locally