Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f44bda6
docs: fundamentals
hejsztynx Jul 16, 2026
23663fe
Apply suggestions from code review
hejsztynx Jul 16, 2026
043fa68
Update docs/docs/fundamentals/your-first-editor.mdx
hejsztynx Jul 16, 2026
b240fb8
docs: enable eslint
hejsztynx Jul 16, 2026
44ee9fe
fix: proper example code
hejsztynx Jul 16, 2026
d1feece
Apply suggestions from code review
hejsztynx Jul 17, 2026
eb62586
docs: tweaks
hejsztynx Jul 17, 2026
0585c98
docs: deps bump
hejsztynx Jul 17, 2026
fb0a032
docs: tweaks
hejsztynx Jul 17, 2026
ae0c82c
Merge branch 'feat/docs' into @ksienkiewicz/docs-fundamentals
hejsztynx Jul 17, 2026
dcc7034
Update docs/docs/fundamentals/core-concepts.md
hejsztynx Jul 17, 2026
a60f30a
Merge branch 'feat/docs' into @ksienkiewicz/docs-fundamentals
hejsztynx Jul 20, 2026
32e2b6a
docs: clean up
hejsztynx Jul 21, 2026
0590aee
Merge branch 'feat/docs' into @ksienkiewicz/docs-fundamentals
hejsztynx Jul 22, 2026
fdb3c17
docs: web sanitization acknowledgement
hejsztynx Jul 27, 2026
7f9dcb4
Merge branch 'feat/docs' into @ksienkiewicz/docs-fundamentals
hejsztynx Jul 27, 2026
5cfb27d
chore: bump enriched-html
hejsztynx Jul 30, 2026
8105030
refactor: rewording
hejsztynx Aug 10, 2026
e738f1e
refactor: formatting
hejsztynx Aug 10, 2026
740a0df
docs: ref api clarification
hejsztynx Aug 10, 2026
5dbfdb1
docs: unctrolled nature clarification
hejsztynx Aug 10, 2026
9f38b2d
fix: grammar
hejsztynx Aug 10, 2026
a1069d8
fix: misaligned code highlights
hejsztynx Aug 10, 2026
448ee56
docs: punctuation and general clarification
hejsztynx Aug 10, 2026
230d98a
docs: make it more precise
hejsztynx Aug 10, 2026
a73aece
docs: unnecessary comma
hejsztynx Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 83 additions & 1 deletion docs/docs/fundamentals/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,86 @@ sidebar_position: 3

# Core concepts

<!-- TODO: write content for this page -->
You've built an editor. Before going on, it's worth understanding the few
ideas the whole library is built on. They explain why the API looks the way it
does and help you understand further chapters better.

## The input is uncontrolled

`EnrichedTextInput` does not take its content from a prop and does not push
every keystroke back into React state. It owns its content and state on the native side
and you talk to it through a `ref`.

This is deliberate. Rich text changes constantly - every character, selection
move and style toggle - and round-tripping all of that through JavaScript state
would be extremely slow and open to a possible de-synchronization of those states
between the JS and native side. Keeping it in the native makes the editor fast and stable.

In practice this means:

- To **change** the content or formatting, call a method on the ref, e.g. `ref.current?.toggleBold()`, `ref.current?.setValue(html)`, or `ref.current?.setLink(...)`.
- To **observe** changes to the content or formatting, listen to events such as `onChangeState`, `onChangeHtml`, or `onChangeSelection`.

You never set a `value` prop and re-render to make an edit happen.
Comment thread
hejsztynx marked this conversation as resolved.

:::note

We have one value-setting prop - `defaultValue`. This provides the editor's initial value upon creation,
so we don't violate the _uncontrolled_ nature of the component here.

:::

## HTML is the source of truth

The editor's content is HTML. `setValue` and `defaultValue` seeds it with an HTML string,
`getHTML` reads the current content back, and `onChangeHtml` streams it as it
changes. What you store and what you render is a string of HTML.

The library uses a fixed set of standard and custom tags, so the output is
predictable and portable. [Supported tags](/fundamentals/html-format-and-supported-tags)
lists exactly what it produces and accepts.

:::caution

Sanitizing HTML is your responsibility. The library doesn't guarantee safe HTML on mobile, so
sanitize anything you persist, render elsewhere, or accept from untrusted
sources. To learn how sanitization is handled on Web, see [Web support](/core-functionalities/web-support).

:::

## Normalization

HTML can be messy. Whether users paste rich text from applications like Google Docs or Microsoft Word, or you inject external HTML via `defaultValue`, `setValue`, or pass it directly as `children` into `EnrichedText`, the markup often contains additional wrapper elements, inline styles, and structural quirks that may not match the HTML structure expected by the library.

To handle this, both components provide a `useHtmlNormalizer` prop that normalizes incoming HTML. The normalizer cleans and restructures the input into the predictable format the library relies on (e.g., it maps `<strong>` to `<b>`, unwraps `<div>` containers into `<p>` tags, and strips unsupported tags). The `useHtmlNormalizer` prop defaults to `true`.

All supported and canonical tags are listed in [Supported tags](/fundamentals/html-format-and-supported-tags).

## Two components, one HTML format

The library is split into an editor and a display:

- **`EnrichedTextInput`** - an interactive editor that emits HTML.
- **`EnrichedText`** - a read-only display component that renders the input's
HTML.

The HTML format that both components expect is identical, what allows you to integrate them seamlessly. A common setup edits in `EnrichedTextInput`, stores the `getHTML` output, and later displays it with `EnrichedText`.

## The style state model

Not every style can be combined with every other. For example, a paragraph can't be both a heading and a list item, code blocks don't support inline formatting such as bold or italic. The editor tracks this and reports it through `onChangeState`, which gives each style three booleans:

- **`isActive`** - whether the style is applied at the current selection. Use it to
highlight a toolbar button.
- **`isBlocking`** - whether another active style forbids this one entirely, so toggling
it would do nothing. For example, bold is blocked inside a code block. Use it
to disable a toolbar button.
- **`isConflicting`** - whether this style would replace an active one if toggled on.
For example, switching a blockquote paragraph to a heading removes the
blockquote. Use it to hint that the toggle is a swap, not an addition.

Driving your toolbar from these three flags keeps the UI honest: buttons light
up and grey out according to the editor's state.

For the comprehensive list of which styles block or conflict with each other, see
[Supported tags](/fundamentals/html-format-and-supported-tags).
125 changes: 124 additions & 1 deletion docs/docs/fundamentals/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,129 @@ slug: /
sidebar_position: 1
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Getting started

<!-- TODO: write content for this page -->
The goal of the _Fundamentals_ section is to get you from an empty project to a
working rich text editor and to give you the mental model you need to build on
your own. Let's dive in.

## What is React Native Enriched HTML?

React Native Enriched HTML is a rich text solution for React Native built by
[Software Mansion](https://swmansion.com/). It supports iOS, Android and Web.

It ships two fully native components: `EnrichedTextInput`, a rich text editor
that styles text live as you type, and `EnrichedText`, a read-only display component that
renders the input's output. Both speak HTML - the input
produces it, the display consumes it.

Not only does this library allow you to apply basic rich text styles you know well,
like _bold_ or _italic_, but also provides more powerful tools like _mentions_, something
you will learn more about in the next chapters.
Comment thread
hejsztynx marked this conversation as resolved.

## Prerequisites

The library works only with the
[React Native New Architecture (Fabric)](https://reactnative.dev/architecture/landing-page).
It's enabled by default on recent React Native versions; if your app still runs
the old architecture you'll need to switch before installing.

For the exact React Native versions we support, see
[Compatibility](/misc/compatibility).

## Installation

### Bare React Native

Install the package:

<Tabs groupId="package-managers">
<TabItem value="npm" label="NPM">
```bash
npm install react-native-enriched-html
```
</TabItem>

<TabItem value="yarn" label="YARN">
```bash
yarn add react-native-enriched-html
```
</TabItem>
</Tabs>

The library contains native code, so rebuild your app after installing. On iOS,
install the pods first:

```sh
cd ios && pod install
```

### Expo

Install with the Expo CLI so the correct version is picked for your SDK:

```sh
npx expo install react-native-enriched-html
```

Then regenerate the native projects:

```sh
npx expo prebuild
```

:::note

The library needs native code, so it won't run in Expo Go. Use a
[development build](https://docs.expo.dev/develop/development-builds/introduction/)
instead.

:::

### Web

Install the package:

<Tabs groupId="package-managers">
<TabItem value="npm" label="NPM">
```bash
npm install react-native-enriched-html
```
</TabItem>

<TabItem value="yarn" label="YARN">
```bash
yarn add react-native-enriched-html
```
</TabItem>
</Tabs>

The web version should work right out of the box. Check the details in the Web Support section.

## Nightly builds

To try features before they land in a stable release, install the nightly
build:

<Tabs groupId="package-managers">
<TabItem value="npm" label="NPM">
```bash
npm install react-native-enriched-html@nightly
```
</TabItem>

<TabItem value="yarn" label="YARN">
```bash
yarn add react-native-enriched-html@nightly
```
</TabItem>
</Tabs>

Nightlies are published to npm automatically and may contain breaking changes.

---

You're set up. Next, let's [build your first editor](/fundamentals/your-first-editor).
74 changes: 73 additions & 1 deletion docs/docs/fundamentals/html-format-and-supported-tags.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,76 @@ sidebar_position: 4

# HTML format and supported tags

<!-- TODO: write content for this page -->
The provided components work with a fixed set of standard and custom HTML tags - the editor produces them and they can be used as input to both the editor and the display component. This page is the reference for that set.

Styles fall into two groups: **inline** tags that wrap a range of characters
and **paragraph** tags that apply to whole paragraphs (**a paragraph might span a few lines!**). Not all of them combine
freely, and there are two kinds of restriction:

- **Conflicting** - toggling a style that conflicts with an active one replaces
it. Toggling `<h2>` on a `<blockquote>` paragraph removes the blockquote and
applies the heading.
- **Blocking** - a blocked style can't be toggled at all while the blocking
style is active. `<b>` is blocked inside `<codeblock>`, so bold can't be
applied there.

Both show up in the [`onChangeState`](/fundamentals/core-concepts#the-style-state-model) payload as
`isConflicting` and `isBlocking`.

## Inline tags

| Style | HTML tag | Conflicts with | Blocked by |
| ------------- | ----------- | ---------------------------- | ---------------------- |
| Bold | `<b>` | -- | `<codeblock>` |
| Italic | `<i>` | -- | `<codeblock>` |
| Underline | `<u>` | -- | `<codeblock>` |
| Strikethrough | `<s>` | -- | `<codeblock>` |
| Inline code | `<code>` | `<a>`, `<mention>` | `<codeblock>`, `<img>` |
| Link | `<a>` | `<code>`, `<a>`, `<mention>` | `<codeblock>`, `<img>` |
| Mention | `<mention>` | `<code>`, `<a>` | `<codeblock>`, `<img>` |
| Image | `<img>` | `<a>`, `<mention>` | `<code>` |

:::note

Headings also block bold when `bold: true` is set on the heading in the
`htmlStyle` prop. The heading already renders as bold, so toggling bold on top
of it is redundant and therefore blocked.

:::

## Paragraph tags

Only one paragraph-level style can be active per paragraph - they all conflict
with each other.

Some paragraph styles are containers that wrap each line inside them with an
**inner content tag**. Each line of a `<ul>` is wrapped in `<li>`, and each line
of a `<codeblock>` is wrapped in `<p>`.

| Style | HTML tag | Inner content tag | Conflicts with | Blocked by |
| -------------- | --------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| Heading 1 | `<h1>` | -- | `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>`, `<ul>`, `<ol>`, `<ul data-type="checkbox">`, `<blockquote>`, `<codeblock>` | -- |
| Heading 2 | `<h2>` | -- | `<h1>`, `<h3>`, `<h4>`, `<h5>`, `<h6>`, `<ul>`, `<ol>`, `<ul data-type="checkbox">`, `<blockquote>`, `<codeblock>` | -- |
| Heading 3 | `<h3>` | -- | `<h1>`, `<h2>`, `<h4>`, `<h5>`, `<h6>`, `<ul>`, `<ol>`, `<ul data-type="checkbox">`, `<blockquote>`, `<codeblock>` | -- |
| Heading 4 | `<h4>` | -- | `<h1>`, `<h2>`, `<h3>`, `<h5>`, `<h6>`, `<ul>`, `<ol>`, `<ul data-type="checkbox">`, `<blockquote>`, `<codeblock>` | -- |
| Heading 5 | `<h5>` | -- | `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h6>`, `<ul>`, `<ol>`, `<ul data-type="checkbox">`, `<blockquote>`, `<codeblock>` | -- |
| Heading 6 | `<h6>` | -- | `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<ul>`, `<ol>`, `<ul data-type="checkbox">`, `<blockquote>`, `<codeblock>` | -- |
| Unordered list | `<ul>` | `<li>` | `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>`, `<ol>`, `<ul data-type="checkbox">`, `<blockquote>`, `<codeblock>` | -- |
| Ordered list | `<ol>` | `<li>` | `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>`, `<ul>`, `<ul data-type="checkbox">`, `<blockquote>`, `<codeblock>` | -- |
| Checkbox list | `<ul data-type="checkbox">` | `<li>` / `<li checked>` | `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>`, `<ul>`, `<ol>`, `<blockquote>`, `<codeblock>` | -- |
| Blockquote | `<blockquote>` | `<p>` | `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>`, `<ul>`, `<ol>`, `<ul data-type="checkbox">`, `<codeblock>` | -- |
| Codeblock | `<codeblock>` | `<p>` | `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>`, `<h6>`, `<b>`, `<u>`, `<i>`, `<s>`, `<ul>`, `<ol>`, `<ul data-type="checkbox">`, `<blockquote>`, `<code>`, `<mention>`, `<a>` | -- |

## Plain and empty paragraphs

A plain line of text with no paragraph style is wrapped in `<p>`. An empty line
is represented by `<br>`. So a document with a heading, a blank line and a
paragraph comes out like this:

```html
<html>
<h1>Title</h1>
<br>
<p>Some body text.</p>
</html>
```
7 changes: 0 additions & 7 deletions docs/docs/fundamentals/your-first-editor.md

This file was deleted.

Loading
Loading