
react-native-enriched
is a powerful React Native library that exposes a rich text editor component:
- ⚡ Fully native text input component
- 🕹️ Synchronous text styling
- 🔍 Live styling detection and HTML parsing
- 🎨 Customizable styles
- 📱 Mobile platforms support
- 🏛 Supports only the New Architecture
EnrichedTextInput
, the rich text editor component is an uncontrolled input. This means that it doesn't use any state or props to store its value, but instead directly interacts with the underlying platform-specific components. Thanks to this, the component is really performant and simple to use while offering complex and advanced features no other solution has.
Built by Software Mansion and sponsored by Filament.


Since 2012 Software Mansion is a software agency with experience in building web and mobile apps. We are Core React Native Contributors and experts in dealing with all kinds of React Native issues.
We can help you build your next dream product –
Hire us.
- Prerequisites
- Installation
- Usage
- Non Parametrized Styles
- Links
- Mentions
- Inline Images
- Style Detection
- Other Events
- Customizing <EnrichedTextInput /> styles
- API Reference
- Future Plans
- Contributing
- License
react-native-enriched
currently supports only Android and iOS platforms- It works only with the React Native New Architecture (Fabric) and supports the 3 latest stable React Native releases, currently
0.79
,0.80
and0.81
yarn add react-native-enriched
The library includes native code so you will need to re-build the native app to use it.
cd ios && bundler install && bundler exec pod install
npx expo install react-native-enriched
The library includes native code so you will need to re-build the native app to use it.
npx expo prebuild
Note
The library won't work in Expo Go as it needs native changes.
Here's a simple example of an input that lets you toggle bold on its text and shows whether bold is currently active via the button color.
import { EnrichedTextInput } from 'react-native-enriched';
import type {
EnrichedTextInputInstance,
OnChangeStateEvent,
} from 'react-native-enriched';
import { useState, useRef } from 'react';
import { View, Button, StyleSheet } from 'react-native';
export default function App() {
const ref = useRef<EnrichedTextInputInstance>(null);
const [stylesState, setStylesState] = useState<OnChangeStateEvent | null>();
return (
<View style={styles.container}>
<EnrichedTextInput
ref={ref}
onChangeState={(e) => setStylesState(e.nativeEvent)}
style={styles.input}
/>
<Button
title="Toggle bold"
color={stylesState?.isBold ? 'green' : 'gray'}
onPress={() => ref.current?.toggleBold()}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
input: {
width: '100%',
fontSize: 20,
padding: 10,
maxHeight: 200,
backgroundColor: 'lightgray',
},
});
Summary of what happens here:
- Any methods imperatively called on the input to e.g. toggle some style must be used through a
ref
ofEnrichedTextInputInstance
type. Here,toggleBold
method that is called on the button press callsref.current?.toggleBold()
, which toggles the bold styling within the current selection. - All the active styles info is emitted by
onChangeState
event. Set up a proper callback that accepts aNativeSyntheticEvent<OnChangeStateEvent>
argument and you can access an object with boolean properties indicating which styles are active, such asisBold
in the example. Here, this info is stored in a react state and used to change colors on the button.
Supported styles:
- bold
- italic
- underline
strikethroughinline code
- H1 heading
- H2 heading
- H3 heading
codeblock
-
blockquote
- ordered list
- unordered list
Note
The iOS doesn't support codeblocks just yet but it's planned in the near future!
Each of the styles can be toggled the same way as in the example from usage section; call a proper toggle
function on the component ref.
Each call toggles the style within the current text selection. We can still divide styles into two categories based on how they treat the selection:
-
Inline styles (bold, italic, underline, strikethrough, inline code). They are being toggled on exactly the character range that is currently selected. When toggling the style with just the cursor in place (no selection), the style is ready to be used and will be applied to the next characters that the user inputs.
-
Paragraph styles (headings, codeblock, blockquote, lists). They are being toggled on the entire paragraph that the selection is in. By paragraph we mean a part of the text between two newlines (enters) or the text's beginning/ending. If the selection spans more than one paragraph, logically more of them will be affected by the toggle. Toggling these styles with the cursor in place (no selection) makes changes to the very paragraph the cursor is in.
The links are here, just like in any other editor, a piece of text with a URL attributed to it. They can be added in two ways: automatically or manually.
react-native-enriched
automatically detects words that appear to be some URLs and makes them links. Currently we are using pretty naive approach to detect whether text can be treated as a link or not. On iOS it's a pretty simple regex, on Android we are using URL regex provided by the system.
Links can also be added by calling setLink method on the input ref:
The start
, end
and text
arguments for the method can be easily taken from onChangeSelection event payload as it returns exact start
and end
of the selection and the text
it spans. This way, you just set the underlying URL to whatever is selected in there.
Passing a different text
than the one in the selection will properly replace it before applying the link.
A complete example of a setup that supports both setting links on the selected text, as well as putting them in the place of cursor and editing existing links can be found in the example app code.
Mentions are meant to be a customisable style that lets you put mentioning phrases in the input, e.g. @someone
or #some_channel
or [any_character_you_like]something
.
There is a mentionIndicators prop that lets you define what characters can start a mention. By default it is set to [ @ ]
, meaning that typing a @
character in the input will start the creation of a mention.
There are two ways in which a mention can be started; either by typing one of the mentionIndicators
set or by calling a startMention method on the input ref.
react-native-enriched
emits 3 different events that help handling mentions' editing:
- onStartMention is emitted whenever mention is started in one of the ways from the previous section or the user has come back (moved selection) to some unfinished mention they have started. It can be used for opening proper tools you use in the app to edit a mention (e.g. a list for choosing from users or channels that the mention will affect).
- onChangeMention is emitted whenever user put or removed some characters after a mention indicator. This way you can react to active mention editing by, for example, filtering users in your displayed list based on the typed text.
- onEndMention is emitted whenever user is no longer editing a mention: they might have put a space or changed the cursor position to be no longer near the indicator. You can use it to hide appropriate tools that were used for mention editing.
Whenever you feel ready with the currently edited mention (so most likely user chooses something from your additional mention editor), you can complete it by calling setMention ref method.
You can insert an image into the input using setImage ref method.
The image will be put into a single line in the input and will affects the line's height as well as input's height. Keep in mind, that image will replace currently selected text or insert into the cursor position if there is no text selection.
Note
The iOS doesn't support inline images just yet but it's planned in the near future!
All of the above styles can be detected with the use of onChangeState event payload.
You can find some examples in the usage section or in the example app.
react-native-enriched
emits a few more events that may be of use:
- onFocus - emits whenever input focuses.
- onBlur - emits whenever input blurs.
- onChangeText - returns the input's text anytime it changes.
- onChangeHtml - returns HTML string parsed from current input text and styles anytime it would change. As parsing the HTML on each input change is a pretty expensive operation, not assigning the event's callback will speed up iOS input a bit. We are considering adding some API to improve it, see future plans.
- onChangeSelection - returns all the data needed for working with selections (as of now it's mainly useful for links).
- onLinkDetected - returns link's detailed info whenever user selection is near one.
- onMentionDetected - returns mention's detailed info whenever user selection is near one.
react-native-enriched
allows customizing styles of the <EnrichedTextInput />
component. See htmlStyle prop.
If true
, focuses the input.
Type | Default Value | Platform |
---|---|---|
bool |
false |
Both |
Tells input to automatically capitalize certain characters.
characters
: all characters.words
: first letter of each word.sentences
: first letter of each sentence.none
: don't auto capitalize anything.
Type | Default Value | Platform |
---|---|---|
'none' | 'sentences' | 'words' | 'characters' |
'sentences' |
Both |
When provided it will set the color of the cursor (or "caret") in the component.
Type | Default Value | Platform |
---|---|---|
color |
system default | Android |
Provides an initial value for the input. If the string is a valid HTML output of the EnrichedTextInput
component (or other HTML that the parser will accept), proper styles will be applied.
Type | Default Value | Platform |
---|---|---|
string |
- | Both |
If false
, text is not editable.
Type | Default Value | Platform |
---|---|---|
bool |
true |
Both |
A prop for customizing styles' appearances.
Type | Default Value | Platform |
---|---|---|
HtmlStyle |
default values from HtmlStyle |
Both |
The recognized mention indicators. Each item needs to be a 1 character long string.
Type | Default Value | Platform |
---|---|---|
array of string |
['@'] |
Both |
Callback that's called whenever the input loses focused (is blurred).
Type | Default Value | Platform |
---|---|---|
() => void |
- | Both |
Callback that is called when input's HTML changes.
Payload interface:
interface OnChangeHtmlEvent {
value: string;
}
value
is the new HTML.
Type | Default Value | Platform |
---|---|---|
(NativeSyntheticEvent\<OnChangeHtmlEvent>) => void |
- | Both |
Callback that gets called anytime user makes some changes to a mention that is being edited.
Payload interface:
interface OnChangeMentionEvent {
indicator: string;
text: string;
}
indicator
is the indicator of the currently edited mention.text
contains whole text that has been typed after the indicator.
Type | Default Value | Platform |
---|---|---|
(OnChangeMentionEvent) => void |
- | Both |
Callback that is called each time user changes selection or moves the cursor in the input.
Payload interface:
OnChangeSelectionEvent {
start: Int32;
end: Int32;
text: string;
}
start
is the index of the selection's beginning.end
is the first index after the selection's ending. For just a cursor in place (no selection),start
equalsend
.text
is the input's text in the current selection.
Type | Default Value | Platform |
---|---|---|
(NativeSyntheticEvent\<OnChangeSelectionEvent>) => void |
- | Both |
Callback that gets called when any of the styles within the selection changes.
Payload has a bool flag for each style:
interface OnChangeStateEvent {
isBold: boolean;
isItalic: boolean;
isUnderline: boolean;
isStrikeThrough: boolean;
isInlineCode: boolean;
isH1: boolean;
isH2: boolean;
isH3: boolean;
isCodeBlock: boolean;
isBlockQuote: boolean;
isOrderedList: boolean;
isUnorderedList: boolean;
isLink: boolean;
isImage: boolean;
isMention: boolean;
}
Type | Default Value | Platform |
---|---|---|
(NativeSyntheticEvent\<OnChangeStateEvent>) => void |
- | Both |
Callback called when any text changes occur in the input.
Payload interface:
interface OnChangeTextEvent {
value: string;
}
value
is the new text value of the input.
Type | Default Value | Platform |
---|---|---|
(NativeSyntheticEvent\<OnChangeTextEvent>) => void |
- | Both |
Callback that is called when the user no longer edits a mention actively - has moved the cursor somewhere else or put a space and the cursor isn't within the edited mention.
indicator
is the indicator of the mention that was being edited.
Type | Default Value | Platform |
---|---|---|
(indicator: string) => void |
- | Both |
Callback that's called whenever the input is focused.
Type | Default Value | Platform |
---|---|---|
() => void |
- | Both |
Callback that gets called when either a new link has been added or the user has moved the cursor/selection to some link.
Payload interface contains all the useful link data:
interface OnLinkDetected {
text: string;
url: string;
start: Int32;
end: Int32;
}
text
is the link's displayed text.url
is the underlying link's URL.start
is the starting index of the link.end
is the first index after the ending index of the link.
Type | Default Value | Platform |
---|---|---|
(OnLinkDetected) => void |
- | Both |
Callback called when mention has been detected - either a new mention has been added or the user has moved the cursor/selection to some mention.
Payload interface contains all the useful mention data:
OnMentionDetected {
text: string;
indicator: string;
attributes: Record<string, string>;
}
text
is the mention's displayed text.indicator
is the indicator of the mention.attributes
are the additional user-defined attributes that are being stored with the mention.
Type | Default Value | Platform |
---|---|---|
(OnMentionDetected) => void |
- | Both |
Callback that gets called whenever a mention editing starts (after placing the indicator).
indicator
is the indicator of the mention that begins editing.
Type | Default Value | Platform |
---|---|---|
(indicator: string) => void |
- | Both |
The placeholder text that is displayed in the input if nothing has been typed yet. Disappears when something is typed.
Type | Default Value | Platform |
---|---|---|
string |
'' |
Both |
Input placeholder's text color.
Type | Default Value | Platform |
---|---|---|
color |
input's color | Both |
A React ref that lets you call any ref methods on the input.
Type | Default Value | Platform |
---|---|---|
RefObject<EnrichedTextInputInstance | null> |
- | Both |
Color of the selection rectangle that gets drawn over the selected text. On iOS, the cursor (caret) also gets set to this color.
Type | Default Value | Platform |
---|---|---|
color |
system default | Both |
Accepts most ViewStyle props, but keep in mind that some of them might not be supported.
Additionally following TextStyle props are supported
- color
- fontFamily
- fontSize
- fontWeight
- fontStyle only on Android
Type | Default Value | Platform |
---|---|---|
View Style | Text Style |
- | Both |
The input inherits ViewProps, but keep in mind that some of the props may not be supported.
If true, Android will use experimental synchronous events. This will prevent from input flickering when updating component size. However, this is an experimental feature, which has not been thoroughly tested. We may decide to enable it by default in a future release.
Type | Default Value | Platform |
---|---|---|
bool |
false |
Android |
All of the methods should be called on the input's ref.
blur: () => void
Blurs the input.
focus: () => void;
Focuses the input.
Note
This function is Android only as iOS doesn't support inline images just yet.
setImage: (src: string) => void;
Sets the inline image at the current selection.
src: string
- the absolute path to the image.
setLink: (
start: number,
end: number,
text: string,
url: string
) => void;
Sets the link at the given place with a given displayed text and URL. Link will replace any text if there was some between start
and end
indexes. Setting a link with start
equal to end
will just insert it in place.
start: number
- the starting index where the link should be.end: number
- first index behind the new link's ending index.text: string
- displayed text of the link.url: string
- URL of the link.
setMention: (
indicator: string,
text: string,
attributes?: Record<string, string>
) => void;
Sets the currently edited mention with a given indicator, displayed text and custom attributes.
indicator: string
- the indicator of the set mention.text: string
- the text that should be displayed for the mention. Anything the user typed gets replaced by that text. The mention indicator isn't added to that text.attributes?: Record<string, string>
- additional, custom attributes for the mention that can be passed as a typescript record. They are properly preserved through parsing from and to the HTML format.
setValue: (value: string) => void;
Sets the input's value.
value: string
- value to set, it can either bereact-native-enriched
supported HTML string or raw text.
startMention: (indicator: string) => void;
Starts a mention with the given indicator. It gets put at the cursor/selection.
indicator: string
- the indicator that starts the new mention.
toggleBlockQuote: () => void;
Toggles blockquote style at the current selection.
toggleBold: () => void;
Toggles bold formatting at the current selection.
Note
This function is Android only as iOS doesn't support codeblocks just yet.
toggleCodeBlock: () => void;
Toggles codeblock formatting at the current selection.
toggleH1: () => void;
Toggles heading 1 (h1) style at the current selection.
toggleH2: () => void;
Toggles heading 2 (h2) style at the current selection.
toggleH3: () => void;
Toggles heading 3 (h3) style at the current selection.
toggleInlineCode: () => void;
Applies inline code formatting to the current selection.
toggleItalic: () => void;
Toggles italic formatting at the current selection.
toggleOrderedList: () => void;
Converts current selection into an ordered list.
toggleStrikeThrough: () => void;
Applies strikethrough formatting to the current selection.
toggleUnderline: () => void;
Applies underline formatting to the current selection.
toggleUnorderedList: () => void;
Converts current selection into an unordered list.
Allows customizing HTML styles.
interface HtmlStyle {
h1?: {
fontSize?: number;
bold?: boolean;
};
h2?: {
fontSize?: number;
bold?: boolean;
};
h3?: {
fontSize?: number;
bold?: boolean;
};
blockquote?: {
borderColor?: ColorValue;
borderWidth?: number;
gapWidth?: number;
color?: ColorValue;
};
codeblock?: {
color?: ColorValue;
borderRadius?: number;
backgroundColor?: ColorValue;
};
code?: {
color?: ColorValue;
backgroundColor?: ColorValue;
};
a?: {
color?: ColorValue;
textDecorationLine?: 'underline' | 'none';
};
mention?: Record<string, MentionStyleProperties> | MentionStyleProperties;
img?: {
width?: number;
height?: number;
};
ol?: {
gapWidth?: number;
marginLeft?: number;
markerFontWeight?: TextStyle['fontWeight'];
markerColor?: ColorValue;
};
ul?: {
bulletColor?: ColorValue;
bulletSize?: number;
marginLeft?: number;
gapWidth?: number;
};
}
interface MentionStyleProperties {
color?: ColorValue;
backgroundColor?: ColorValue;
textDecorationLine?: 'underline' | 'none';
}
fontSize
is the size of the heading's font, defaults to32
/24
/20
for h1/h2/h3.bold
defines whether the heading should be bolded, defaults tofalse
.
borderColor
defines the color of the rectangular border drawn to the left of blockquote text. Takes color value, defaults todarkgray
.borderWidth
sets the width of the said border, defaults to4
.gapWidth
sets the width of the gap between the border and the blockquote text, defaults to16
.color
defines the color of blockquote's text. Takes color value, if not set makes the blockquote text the same color as the input's color prop.
color
defines the color of codeblock text, takes color value and defaults toblack
.borderRadius
sets the radius of codeblock's border, defaults to 8.backgroundColor
is the codeblock's background color, takes color value and defaults todarkgray
.
color
defines the color of inline code's text, takes color value and defaults tored
.backgroundColor
is the inline code's background color, takes color value and defaults todarkgray
.
color
defines the color of link's text, takes color value and defaults toblue
.textDecorationLine
decides if the links are underlined or not, takes eitherunderline
ornone
and defaults tounderline
If only a single config is given, the style applies to all mention types. You can also set a different config for each mentionIndicator that has been defined, then the prop should be a record with indicators as a keys and configs as their values.
color
defines the color of mention's text, takes color value and defaults toblue
.backgroundColor
is the mention's background color, takes color value and defaults toyellow
.textDecorationLine
decides if the mentions are underlined or not, takes eitherunderline
ornone
and defaults tounderline
.
width
is the width of the inline image, defaults to80
.height
is the height of the inline image, defaults to80
.
By marker we mean the number that denotes next lines of the list.
gapWidth
sets the gap between the marker and the list item's text, defaults to16
.marginLeft
sets the margin to the left of the marker (between the marker and input's left edge), defaults to16
.markerFontWeight
defines the font weight of the marker, takes a fontWeight value and if not set, defaults to the same font weight as input's fontWeight prop.markerColor
sets the text color of the marker, takes color value and if not set, defaults to the same color as input's color prop.
By bullet we mean the dot that begins each line of the list.
bulletColor
defines the color of the bullet, takes color value and defaults toblack
.bulletSize
sets both the height and the width of the bullet, defaults to8
.marginLeft
is the margin to the left of the bullet (between the bullet and input's left edge), defaults to16
.gapWidth
sets the gap between the bullet and the list item's text, defaults to16
.
- Adding Codeblocks and Inline Images to iOS input.
- Making some optimizations around
onChangeHtml
event, maybe some imperative API to get the HTML output. - Creating
EnrichedText
text component that supports our HTML output format with all additional interactions like pressing links or mentions. - Adding API for custom link detection regex.
- Web library implementation via
react-native-web
.
See the contributing guide to learn how to contribute to the repository and the development workflow.
react-native-enriched
library is licensed under The MIT License.