-
Notifications
You must be signed in to change notification settings - Fork 2
NativeRichTextEditor Reference
Jayden Smith edited this page May 5, 2026
·
5 revisions
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 | 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. |
- The native placeholder uses the resolved
paragraphtext style fromtheme, 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.
- Whole-document JSON entry points normalize
{ type: 'doc', content: [] }to a schema-valid empty document for the active schema. This applies toinitialJSON, controlledvalueJSON, and imperative replacements such assetContentJson(). - Fragment insertion APIs like
insertContentJson()are not rewritten — they insert exactly what you pass in. - Pair
valueJSONwithvalueJSONRevisionto skip redundant serialization when the parent recreates equivalent documents on rerender.
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. |
- Link application is host-driven. Add a toolbar item with
{ type: 'link', ... }and handle URL entry inonRequestLink. -
LinkRequestContextreports the current link state, including the activehrefwhen the selection sits inside a link. - You can also apply or remove links imperatively via the editor ref:
setLink(href)andunsetLink(). -
autoDetectLinksis a separate opt-in, off by default. When enabled, the editor detectshttp(s)://…andwww.…text near a collapsed cursor and applies a normallinkmark with the detectedhref(www.is normalized tohttps://www.…). Detection is skipped if the current schema does not allow thelinkmark at that position. - Autolink detection only runs on live editing updates (typing, paste). It does not rewrite controlled
valueorvalueJSONupdates 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.
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. |
- Add a toolbar item with
{ type: 'image', ... }and handle picking or uploading inonRequestImage. - Finish the host flow by calling
insertImage(src, attrs?). -
srccan be a remote URL, a local file URL, or adata:image/...URI whenallowBase64Imagesis enabled. - The built-in schemas include a block
imagenode withsrc,alt,title,width, andheightattrs. - Selected images expose native drag handles on iOS and Android; resizing writes the new size back to
widthandheight. SetallowImageResizing={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' });
}}
/>;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. |
-
NativeRichTextEditoris the editor component used in collaboration mode. - Wire
valueJSON,onContentChangeJSON, selection/focus callbacks, andremoteSelectionsstraight fromuseYjsCollaboration().editorBindings. - Do not layer a second app-owned controlled
valueJSONover 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.
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. |
-
autoGrowis for parent-managed scroll containers. Even inside aScrollVieworFlatList, you still need screen-level keyboard avoidance (e.g.KeyboardAvoidingView). - While empty,
autoGrowsizes to the larger of the content height and placeholder height. This matters most for multiline placeholders on Android. -
fixedscrolls 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
KeyboardAvoidingViewwithtoolbarPlacement="keyboard"on Android, set akeyboardVerticalOffsetthat accounts for any space you reserve above the keyboard.
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 });
}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. |
-
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.
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. |
Copyright © 2026 Apollo Health Group Pty. Ltd.