From be2cc76bae9964d83b6d8e0969b664e2a05e2323 Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz Date: Thu, 23 Jul 2026 10:54:48 +0200 Subject: [PATCH 01/10] feat: wip --- .../misc/comparison-with-other-libraries.md | 59 +++- docs/docs/misc/compatibility.md | 7 - docs/docs/misc/compatibility.mdx | 88 +++++ docs/docs/misc/contributing.md | 300 +++++++++++++++++- docs/docs/misc/known-limitations.md | 29 +- docs/docs/misc/roadmap.md | 18 +- 6 files changed, 490 insertions(+), 11 deletions(-) delete mode 100644 docs/docs/misc/compatibility.md create mode 100644 docs/docs/misc/compatibility.mdx diff --git a/docs/docs/misc/comparison-with-other-libraries.md b/docs/docs/misc/comparison-with-other-libraries.md index 939389b68..9b8d41e5b 100644 --- a/docs/docs/misc/comparison-with-other-libraries.md +++ b/docs/docs/misc/comparison-with-other-libraries.md @@ -4,4 +4,61 @@ sidebar_position: 2 # Comparison with other libraries - +Most rich text solutions for React Native fall into one of two camps: those +that embed a browser-based editor inside a `WebView`, and those that drive the +platform's native text components directly. React Native Enriched HTML belongs +to the second group, and that choice shapes almost everything about how it +performs and feels. + +## WebView-based editors + +WebView editors load an HTML/JavaScript rich text engine (often something like +Quill, ProseMirror, or a `contenteditable` surface) inside a web view and +bridge messages between it and your React Native code. + +This approach is flexible - it reuses the mature web editing ecosystem - but it +comes with structural costs: + +- **A browser in every editor.** Each editor spins up a full web view, with its + own memory footprint and startup cost. +- **Bridge latency.** Every keystroke, selection change, and style toggle + crosses the React Native ↔ web view boundary as a serialized message, which + adds lag and opens the door to state desynchronization. +- **Foreign look and feel.** Text selection handles, the context menu, the + keyboard, and scrolling are the web view's, not the platform's, so they rarely + match the rest of your app. +- **Styling seams.** Fonts, insets, and system behaviours have to be + re-created in CSS to approximate native components. + +## React Native Enriched HTML + +`EnrichedTextInput` and `EnrichedText` are **fully native** components built on +the New Architecture (Fabric). There is no web view and no HTML engine hidden +inside the editor. + +- **Native performance.** The input styles text live, on the native side, as + you type - there's no bridge round-trip per keystroke and no browser to boot. +- **Uncontrolled by design.** The input owns its content natively and you talk + to it through a `ref`, so heavy rich text state never round-trips through + JavaScript. See [Core concepts](/fundamentals/core-concepts) for the details. +- **Native UX for free.** Selection handles, the context menu, the keyboard, + and accessibility come from the platform, so they behave exactly like every + other input in your app. +- **HTML in, HTML out.** The editor produces a predictable, fixed set of tags, + and the matching `EnrichedText` display component renders that HTML 1:1 - a + shared styling API keeps editing and display visually identical. + +## When a WebView editor might still fit + +A native library trades some flexibility for speed and consistency. If you need +a feature the platform text systems don't offer yet (for example deeply nested +document structures or a plugin ecosystem specific to a web editor), a WebView +solution may still be the pragmatic choice. For the large majority of rich text +needs in a mobile app - comments, chat, notes, posts - a native component is +faster, lighter, and closer to the platform. + + diff --git a/docs/docs/misc/compatibility.md b/docs/docs/misc/compatibility.md deleted file mode 100644 index 326f8ab42..000000000 --- a/docs/docs/misc/compatibility.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Compatibility - - diff --git a/docs/docs/misc/compatibility.mdx b/docs/docs/misc/compatibility.mdx new file mode 100644 index 000000000..8b9702293 --- /dev/null +++ b/docs/docs/misc/compatibility.mdx @@ -0,0 +1,88 @@ +--- +sidebar_position: 1 +--- + +# Compatibility + +This page is the single source of truth for the React Native versions the +library supports and for the small set of behaviours that differ between +platforms. + +:::info + +React Native Enriched HTML works **only** with the +[React Native New Architecture (Fabric)](https://reactnative.dev/architecture/landing-page). +The New Architecture is enabled by default on recent React Native versions. If +your app still runs the old architecture, you'll need to switch before +installing. + +::: + +## Supported React Native versions + +The table below assumes you're on the latest patch of each React Native minor +version. + +| React Native | Android | iOS | Web | +| ------------ | ---------- | ---------- | -------------- | +| 0.85 | | | (exp.) | +| 0.84 | | | (exp.) | +| 0.83 | | | (exp.) | +| 0.82 | | | (exp.) | +| 0.81 | | | (exp.) | + +Android and iOS are fully supported. The Web implementation is still +**experimental** - see the [Web support](/core-functionalities/web-support) +guide for what's covered. + +## Platform differences + +Android, iOS and Web are built on different text systems, so a few APIs +behave differently depending on the platform. These are platform +characteristics, not library bugs, and they're collected here. For limitations that affect +every platform, see [Known limitations](/misc/known-limitations) instead. + +### Justified text alignment (Android) + +On Android, `'justify'` is accepted in the type signature but has no justified +layout effect - the paragraph is shown with its natural alignment, the same as +`'auto'`. This applies both to `setTextAlignment('justify')` and to the `justify` value reported by +`onChangeState`. + +### `ellipsizeMode` with multiple lines (Android) + +On Android, when `numberOfLines` is set to a value higher than `1`, only the +`'tail'` value of `ellipsizeMode` works correctly. + +### Custom context menu (iOS 16+, ordering) + +Custom context-menu items (`contextMenuItems`) require **iOS 16 or newer** on +iOS. On iOS, items appear in array order, before the system items +(Copy/Paste/Cut). On Android there is no guaranteed order, and custom items may +be shown in a submenu depending on the device manufacturer. On the Web the +native editing menu isn't available at all, so the prop is ignored - build your +own UI instead. + +### Return key behaviour + +- **`returnKeyType`** on Android it's accepted but ignored, because + `returnKeyType` doesn't work with multiline inputs. On the Web it maps to the + browser's [`enterkeyhint`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint). +- **`returnKeyLabel`** is supported on Android but not on iOS, and can't be set + inside a browser, so it's ignored on the Web. + +### Cursor / selection color + +The `cursorColor` prop is applied on Android and Web. On iOS the caret and +selection color follow the system tint. + +## HTML is not sanitized (Mobile) + +On iOS and Android, the library does not sanitize HTML. It makes no guarantees that the markup it accepts or produces is safe. You are fully responsible for sanitizing any HTML you persist, render elsewhere, or accept from untrusted sources. + +Sanitization *is* enforced on the web, as injecting unsafe HTML directly into the DOM poses severe security risks (like XSS). + +### React Native layout ref methods + +On Web those methods are no-ops. These include: `measure`, +`measureInWindow`, `measureLayout`, and `setNativeProps`. diff --git a/docs/docs/misc/contributing.md b/docs/docs/misc/contributing.md index cd2d319b8..523437388 100644 --- a/docs/docs/misc/contributing.md +++ b/docs/docs/misc/contributing.md @@ -4,4 +4,302 @@ sidebar_position: 5 # Contributing - +Contributions are always welcome, no matter how large or small. This page will walk you through the development workflow. Before contributing, please read the +[code of conduct](https://github.com/software-mansion/react-native-enriched-html/blob/main/CODE_OF_CONDUCT.md). + +## Development workflow + +The project is a monorepo managed with +[Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains three +packages: + +- the library package, in the root directory; +- an example app, in `apps/example/`; +- this documentation site, in `docs/` (a standalone project with its own + dependencies - see [Documentation](#documentation)). + +Install dependencies for every package by running `yarn` in the root directory: + +```sh +yarn +``` + +:::note + +Because the project relies on Yarn workspaces, you can't use `npm` for +development. + +::: + +The [example app](https://github.com/software-mansion/react-native-enriched-html/tree/main/apps/example) +demonstrates the library and is how you test any changes you make. It's +configured to use the local version of the library, so your source changes are +reflected there: + +- **JavaScript** changes show up without a rebuild. +- **Native** changes (Objective-C, Swift, Java, Kotlin) require rebuilding the + example app. + +### Editing native code + +To edit the native code in an IDE: + +- **iOS** - open `apps/example/ios/EnrichedTextInputExample.xcworkspace` in + Xcode. Find the sources under **Pods > Development Pods > + ReactNativeEnrichedHtml**. +- **Android** - open `apps/example/android` in Android Studio. Find the sources + under **react-native-enriched-html** in the **Android** view. + +### Running the example app + +Start the Metro bundler: + +```sh +yarn example start +``` + +Run the app: + +```sh +# Android +yarn example android + +# iOS +yarn example ios +``` + +To confirm the app is running with the New Architecture, look for a line like +this in the Metro logs: + +```sh +Running "EnrichedTextInputExample" with {"fabric":true,"initialProps":{"concurrentRoot":true},"rootTag":1} +``` + +Note the `"fabric":true` and `"concurrentRoot":true` properties. + +## Linting, types, and tests + +Make sure your code passes TypeScript and ESLint: + +```sh +yarn typecheck +yarn lint +``` + +Fix formatting errors automatically: + +```sh +yarn lint --fix +``` + +Add tests for your change where possible, and run the unit tests: + +```sh +yarn test +``` + +The project uses [TypeScript](https://www.typescriptlang.org/) for type +checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) +for linting and formatting, and [Jest](https://jestjs.io/) for testing. + +:::note + +Pre-commit hooks verify that the linter and typecheck pass when you commit. + +::: + +## End-to-end tests + +### Mobile (Maestro) + +We use [Maestro](https://maestro.mobile.dev/) for mobile end-to-end testing. +Flows live in `.maestro/enrichedInput/flows/` and +`.maestro/enrichedText/flows/`. Shared subflows live in `.maestro/subflows/`, +with component-specific subflows in `.maestro/enrichedInput/subflows/` and +`.maestro/enrichedText/subflows/`. + +**Prerequisites:** + +- **Maestro CLI** (v2.3.0+) - follow the + [Getting Started guide](https://github.com/mobile-dev-inc/maestro?tab=readme-ov-file#getting-started), + then ensure `~/.maestro/bin` is in your `PATH`. +- **iOS** - Xcode, with `xcrun` available (it ships with the Xcode Command Line + Tools). +- **Android** - the Android SDK with SDK Command-line Tools, Platform-Tools, and + Emulator. Set `ANDROID_HOME` (typically `$HOME/Library/Android/sdk` on macOS) + and add these to your `PATH`: + - `$ANDROID_HOME/cmdline-tools/latest/bin` + - `$ANDROID_HOME/platform-tools` + - `$ANDROID_HOME/emulator` + +The target devices are: + +| Platform | Device | OS | +| -------- | --------- | ----------------------------- | +| iOS | iPhone 17 | iOS 26.2 | +| Android | Pixel 9 | API 36 "Baklava" (Android 16) | + +**Running the tests** - start the Metro bundler first, then run a suite. Each +command sets up the device and runs all Maestro flows, building only when +necessary: + +```sh +yarn example start + +# Both platforms sequentially +yarn test:e2e:mobile + +# Single platform +yarn test:e2e:ios +yarn test:e2e:android +``` + +Target a specific flow or force a rebuild: + +```sh +# Run a single flow +yarn test:e2e:ios .maestro/enrichedInput/flows/core_controls_smoke.yaml + +# Force a fresh build even if the app is already installed +yarn test:e2e:android --rebuild +``` + +**Visual regression tests** - some flows compare a screenshot of the component +against a saved baseline in `.maestro/enrichedInput/screenshots/` or +`.maestro/enrichedText/screenshots/`. By default the baseline is asserted; pass +`--update-screenshots` to capture new baselines instead: + +```sh +# Update baselines on both platforms +yarn test:e2e:mobile --update-screenshots + +# Single platform +yarn test:e2e:ios --update-screenshots +yarn test:e2e:android --update-screenshots .maestro/enrichedInput/flows/inline_styles_visual.yaml +``` + +Always review newly saved screenshots before committing them. + +:::note Flaky Android tests on macOS + +macOS may throttle the Android emulator via **App Nap** when its window isn't +visible, which can cause test timeouts. Either keep the emulator window visible +while tests run, or disable App Nap for the emulator: + +```sh +defaults write com.google.android.emulator NSAppSleepDisabled -bool YES +``` + +This requires an emulator restart and may drain your battery, so you may want +to re-enable it afterwards with `-bool NO`. + +::: + +### Web (Playwright) + +We use [Playwright](https://playwright.dev/) for end-to-end testing of the web +example. Tests live in `.playwright/tests/`. Install the browser binaries once: + +```sh +yarn playwright install +``` + +Run the suite from the root directory: + +```sh +yarn test:e2e:web +``` + +**Visual regression tests** - some tests compare a screenshot of the component +against a saved baseline in `.playwright/screenshots/`. By default the baseline is asserted; pass +`--update-screenshots` to capture new baselines instead: + +```sh +yarn test:e2e:web --update-screenshots +``` + +Append `--ui` for Playwright's +[UI mode](https://playwright.dev/docs/test-ui-mode) (pick tests, watch runs, +inspect traces). The Playwright config starts the Vite dev server +automatically, so you don't need to run `yarn example-web dev` separately. + +## Documentation + +The site you're reading is built with [Docusaurus](https://docusaurus.io/) and +lives in the `docs/` directory. It's a standalone project with its own +`yarn.lock`, separate from the monorepo workspace, so install its dependencies +from inside the folder: + +```sh +cd docs +yarn +``` + +Then start the local dev server: + +```sh +yarn start +``` + +A few things worth knowing: + +- Pages are Markdown / MDX files in `docs/docs/`; the navigation is generated + from the folder structure and `_category_.json` files. Filenames are + kebab-case. +- If you make any changes, run `yarn build` before opening a pull request, to make sure the project successfully builds. + +:::info + +When you change the public API or behaviour, please update the documentation in +the same pull request whenever possible. Keeping the docs in sync with the code +is one of the most valuable ways to contribute. + +::: + +## Commit message convention + +We follow the +[conventional commits specification](https://www.conventionalcommits.org/en). +Pre-commit hooks verify the format when you commit: + +- `fix`: bug fixes, e.g. fix crash due to deprecated method. +- `feat`: new features, e.g. add a new method to the module. +- `refactor`: code refactor, e.g. migrate from class components to hooks. +- `docs`: documentation changes, e.g. add a usage example for the module. +- `test`: adding or updating tests, e.g. add integration tests. +- `chore`: tooling changes, e.g. change CI config. + +## Scripts + +The `package.json` file contains scripts for common tasks: + +- `yarn` - set up the project by installing dependencies. +- `yarn typecheck` - type-check files with TypeScript. +- `yarn lint` - lint files with ESLint. +- `yarn test` - run unit tests with Jest. +- `yarn example start` - start the Metro server for the example app. +- `yarn example android` - run the example app on Android. +- `yarn example ios` - run the example app on iOS. +- `yarn test:e2e` - run all E2E tests (mobile + web) sequentially. +- `yarn test:e2e:mobile` - run mobile E2E tests on iOS and Android sequentially. +- `yarn test:e2e:android` - run E2E tests on Android. +- `yarn test:e2e:ios` - run E2E tests on iOS. +- `yarn test:e2e:web` - run E2E tests on the web example with Playwright. + +## Sending a pull request + +:::tip + +Working on your first pull request? Learn how from this free series: +[How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github). + +::: + +When you send a pull request: + +- Prefer small pull requests focused on one change. +- Verify that linters and tests are passing. +- Review the documentation to make sure it looks good. +- Follow the pull request template when opening a pull request. +- For pull requests that change the API or implementation, discuss with the + maintainers first by opening an issue. diff --git a/docs/docs/misc/known-limitations.md b/docs/docs/misc/known-limitations.md index af18c99cd..de53e854f 100644 --- a/docs/docs/misc/known-limitations.md +++ b/docs/docs/misc/known-limitations.md @@ -4,4 +4,31 @@ sidebar_position: 3 # Known limitations - +This page lists limitations that apply to **every** platform. Behaviours that +differ only between iOS, Android, and Web are documented separately under +[Compatibility](/misc/compatibility). + +Some of these are on our [Roadmap](/misc/roadmap); others are deliberate design +choices that keep the library fast and its output predictable. + +## Single-level lists only + +The editor supports a single level of ordered and unordered lists. **Nested +lists are not supported** - you can't indent a list item to create a sublist. +Multi-level list support is planned; see the [Roadmap](/misc/roadmap). + +## Fixed set of HTML tags + +The library intentionally works with a fixed, curated set of standard and +custom HTML tags rather than accepting arbitrary markup. Tags outside that set +are stripped or normalized away when they enter the editor. This is a +deliberate design decision: it keeps the produced HTML portable and guarantees +that the `EnrichedTextInput` and the `EnrichedText` display render identically. See +[Supported tags](/fundamentals/html-format-and-supported-tags) for the full +list. + +## New Architecture required + +The components are built exclusively for the React Native New Architecture +(Fabric). There is no Old Architecture (Paper) fallback. See +[Compatibility](/misc/compatibility) for supported React Native versions. diff --git a/docs/docs/misc/roadmap.md b/docs/docs/misc/roadmap.md index 28978894c..4844e4163 100644 --- a/docs/docs/misc/roadmap.md +++ b/docs/docs/misc/roadmap.md @@ -4,4 +4,20 @@ sidebar_position: 4 # Roadmap - +Here's what we're planning to bring to React Native Enriched HTML in the future. + +## Nested lists + +Support for multi-level ordered and unordered lists, so users can organize +complex content hierarchically. This lifts the current +[single-level list limitation](/misc/known-limitations). + +## RTL support + +Natively integrated right-to-left text direction for RTL languages. + +## Accessibility reader + +Support for screen readers and accessibility APIs, +delivering a rich, inclusive text editing experience while aligning with WCAG +and ACA standards. From 31ecc4e27243836b59e0460ce72ce49adfab14bd Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz Date: Thu, 23 Jul 2026 12:54:34 +0200 Subject: [PATCH 02/10] feat: remove irrelevant content --- .../misc/comparison-with-other-libraries.md | 59 ------------------- docs/docs/misc/compatibility.mdx | 15 +---- 2 files changed, 1 insertion(+), 73 deletions(-) diff --git a/docs/docs/misc/comparison-with-other-libraries.md b/docs/docs/misc/comparison-with-other-libraries.md index 9b8d41e5b..116996032 100644 --- a/docs/docs/misc/comparison-with-other-libraries.md +++ b/docs/docs/misc/comparison-with-other-libraries.md @@ -3,62 +3,3 @@ sidebar_position: 2 --- # Comparison with other libraries - -Most rich text solutions for React Native fall into one of two camps: those -that embed a browser-based editor inside a `WebView`, and those that drive the -platform's native text components directly. React Native Enriched HTML belongs -to the second group, and that choice shapes almost everything about how it -performs and feels. - -## WebView-based editors - -WebView editors load an HTML/JavaScript rich text engine (often something like -Quill, ProseMirror, or a `contenteditable` surface) inside a web view and -bridge messages between it and your React Native code. - -This approach is flexible - it reuses the mature web editing ecosystem - but it -comes with structural costs: - -- **A browser in every editor.** Each editor spins up a full web view, with its - own memory footprint and startup cost. -- **Bridge latency.** Every keystroke, selection change, and style toggle - crosses the React Native ↔ web view boundary as a serialized message, which - adds lag and opens the door to state desynchronization. -- **Foreign look and feel.** Text selection handles, the context menu, the - keyboard, and scrolling are the web view's, not the platform's, so they rarely - match the rest of your app. -- **Styling seams.** Fonts, insets, and system behaviours have to be - re-created in CSS to approximate native components. - -## React Native Enriched HTML - -`EnrichedTextInput` and `EnrichedText` are **fully native** components built on -the New Architecture (Fabric). There is no web view and no HTML engine hidden -inside the editor. - -- **Native performance.** The input styles text live, on the native side, as - you type - there's no bridge round-trip per keystroke and no browser to boot. -- **Uncontrolled by design.** The input owns its content natively and you talk - to it through a `ref`, so heavy rich text state never round-trips through - JavaScript. See [Core concepts](/fundamentals/core-concepts) for the details. -- **Native UX for free.** Selection handles, the context menu, the keyboard, - and accessibility come from the platform, so they behave exactly like every - other input in your app. -- **HTML in, HTML out.** The editor produces a predictable, fixed set of tags, - and the matching `EnrichedText` display component renders that HTML 1:1 - a - shared styling API keeps editing and display visually identical. - -## When a WebView editor might still fit - -A native library trades some flexibility for speed and consistency. If you need -a feature the platform text systems don't offer yet (for example deeply nested -document structures or a plugin ecosystem specific to a web editor), a WebView -solution may still be the pragmatic choice. For the large majority of rich text -needs in a mobile app - comments, chat, notes, posts - a native component is -faster, lighter, and closer to the platform. - - diff --git a/docs/docs/misc/compatibility.mdx b/docs/docs/misc/compatibility.mdx index 8b9702293..e59114f39 100644 --- a/docs/docs/misc/compatibility.mdx +++ b/docs/docs/misc/compatibility.mdx @@ -20,20 +20,7 @@ installing. ## Supported React Native versions -The table below assumes you're on the latest patch of each React Native minor -version. - -| React Native | Android | iOS | Web | -| ------------ | ---------- | ---------- | -------------- | -| 0.85 | | | (exp.) | -| 0.84 | | | (exp.) | -| 0.83 | | | (exp.) | -| 0.82 | | | (exp.) | -| 0.81 | | | (exp.) | - -Android and iOS are fully supported. The Web implementation is still -**experimental** - see the [Web support](/core-functionalities/web-support) -guide for what's covered. +(compatibility table here) ## Platform differences From 9dc732a3df7486b9d7d7c458f742b3920cf0f92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Thu, 23 Jul 2026 13:31:02 +0200 Subject: [PATCH 03/10] docs: add react native compatibility table --- docs/docs/misc/compatibility.mdx | 4 +- .../EnrichedHtmlCompatibility.tsx | 72 +++++++++++++++++++ docs/src/theme/MDXComponents.js | 2 + 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 docs/src/components/Compatibility/EnrichedHtmlCompatibility.tsx diff --git a/docs/docs/misc/compatibility.mdx b/docs/docs/misc/compatibility.mdx index e59114f39..92aa51c37 100644 --- a/docs/docs/misc/compatibility.mdx +++ b/docs/docs/misc/compatibility.mdx @@ -20,7 +20,9 @@ installing. ## Supported React Native versions -(compatibility table here) +The table assumes you're using the latest patch of each library minor version. + + ## Platform differences diff --git a/docs/src/components/Compatibility/EnrichedHtmlCompatibility.tsx b/docs/src/components/Compatibility/EnrichedHtmlCompatibility.tsx new file mode 100644 index 000000000..caaa3ae6c --- /dev/null +++ b/docs/src/components/Compatibility/EnrichedHtmlCompatibility.tsx @@ -0,0 +1,72 @@ +import { No, Version, Yes } from './index'; + +const REACT_NATIVE_VERSIONS = [ + '0.80', + '0.81', + '0.82', + '0.83', + '0.84', + '0.85', + '0.86', + '0.87', +]; + +const LIBRARY_VERSIONS = [ + { version: 'nightly', supportedFrom: '0.81', supportedTo: '0.86' }, + { version: '1.1.x', supportedFrom: '0.81', supportedTo: '0.86' }, + { version: '1.0.x', supportedFrom: '0.81', supportedTo: '0.85' }, +] as const; + +function isSupported( + reactNativeVersion: string, + supportedFrom: string, + supportedTo: string, +) { + return ( + reactNativeVersion.localeCompare(supportedFrom, undefined, { + numeric: true, + }) >= 0 && + reactNativeVersion.localeCompare(supportedTo, undefined, { + numeric: true, + }) <= 0 + ); +} + +export default function EnrichedHtmlCompatibility() { + return ( +
+ + + + + {REACT_NATIVE_VERSIONS.map(version => ( + + ))} + + + + {LIBRARY_VERSIONS.map(({ version, supportedFrom, supportedTo }) => ( + + + {REACT_NATIVE_VERSIONS.map(reactNativeVersion => ( + + ))} + + ))} + +
{version}
+ + + {isSupported( + reactNativeVersion, + supportedFrom, + supportedTo, + ) ? ( + + ) : ( + + )} +
+
+ ); +} diff --git a/docs/src/theme/MDXComponents.js b/docs/src/theme/MDXComponents.js index 2b2796f2a..d53cb3511 100644 --- a/docs/src/theme/MDXComponents.js +++ b/docs/src/theme/MDXComponents.js @@ -9,6 +9,7 @@ import Indent from '@site/src/components/Indent'; import Row from '@site/src/components/Row'; import Grid from '@site/src/components/Grid'; import { Yes, No, Version, Spacer } from '@site/src/components/Compatibility'; +import EnrichedHtmlCompatibility from '@site/src/components/Compatibility/EnrichedHtmlCompatibility'; import { Badges } from '@swmansion/t-rex-ui'; export default { @@ -26,5 +27,6 @@ export default { No, Version, Spacer, + EnrichedHtmlCompatibility, Badges, }; From d3aec98e9f10e887f64732ebd7bdaa686a5e748f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Thu, 23 Jul 2026 14:53:21 +0200 Subject: [PATCH 04/10] docs: add comparison with other libraries --- .../misc/comparison-with-other-libraries.md | 62 +++++++++++++++++++ docs/docs/misc/compatibility.mdx | 4 +- ...tibility.tsx => EnrichedCompatibility.tsx} | 3 +- docs/src/theme/MDXComponents.js | 4 +- 4 files changed, 67 insertions(+), 6 deletions(-) rename docs/src/components/Compatibility/{EnrichedHtmlCompatibility.tsx => EnrichedCompatibility.tsx} (96%) diff --git a/docs/docs/misc/comparison-with-other-libraries.md b/docs/docs/misc/comparison-with-other-libraries.md index 116996032..c2af21289 100644 --- a/docs/docs/misc/comparison-with-other-libraries.md +++ b/docs/docs/misc/comparison-with-other-libraries.md @@ -3,3 +3,65 @@ sidebar_position: 2 --- # Comparison with other libraries + +React Native doesn't ship a built-in rich text component, so a number of +community libraries exist. They fall into two broad categories: **native +implementations** that render directly through the platform's text system, and +**WebView wrappers** that embed a browser-based editor. This page compares +React Native Enriched HTML with the most popular alternatives in each +category. + +## Native solutions + +### React Native Aztec + +[React Native Aztec](https://github.com/wordpress-mobile/react-native-aztec) +was WordPress's native rich text component for Gutenberg Mobile. The repository +was **archived in March 2025** and the code folded into the +[Gutenberg monorepo](https://github.com/WordPress/gutenberg), where activity +is focused on the WordPress editor itself rather than the standalone React +Native wrapper. Aztec **does not support the New Architecture (Fabric)** and is +dual-licensed under **GPL-2.0 / MPL-2.0** - both are copyleft licenses that +may require you to distribute your own source code under the same terms, +which can be a deal-breaker for proprietary apps. + +### React Native Enriched Markdown + +[React Native Enriched Markdown](https://github.com/software-mansion/react-native-enriched-markdown) +is another fully native library from Software Mansion (MIT-licensed, New +Architecture required). It focuses on **Markdown** rather than HTML: + +- **Text component** - renders a wide range of Markdown features natively, + including GFM tables, task lists, LaTeX math and spoiler text. +- **Text input** - provides a rich editing experience with Markdown output. + +The key difference is the **content format**. Enriched Markdown targets apps +that work with Markdown end-to-end, while Enriched HTML is built for apps that +need an **HTML-based editor with advanced, customisable formatting +capabilities** and a matching display component. The two libraries can coexist in one app if you need both formats. + +## WebView-based solutions + +### 10tap Editor + +[10tap Editor](https://github.com/10play/10tap-editor) wraps +[TipTap](https://tiptap.dev) (ProseMirror) inside a React Native WebView. +It offers a keyboard-aware toolbar and a familiar ProseMirror plugin system. + +### React Native Pell Rich Editor + +[React Native Pell Rich Editor](https://github.com/wxik/react-native-rich-editor) +is a lightweight WebView-based editor that uses `contentEditable` and +`document.execCommand`. + +### Why native matters + +All WebView-based editors share a set of inherent trade-offs compared to a +fully native approach: + +| | Native (Enriched HTML) | WebView editors | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Input latency** | Text goes through the platform's native text system - every keystroke, selection change and style toggle is processed without leaving the native layer. | Every interaction crosses the React Native ↔ WebView bridge, adding measurable latency, especially on lower-end devices. | +| **Platform integration** | Full access to native APIs: system context menus, autocorrect / predictive text, VoiceOver / TalkBack, drag-and-drop, hardware keyboards. | Limited by what the WebView exposes; features like custom context-menu items or native accessibility trees require extra workarounds or are simply unavailable. | +| **Memory & startup** | No WebView process to spin up - the component is just another native view in the hierarchy. | Each editor instance spawns a WebView, increasing memory use and adding a visible loading delay on first render. | +| **Debugging** | Standard React Native / native debugging tools. | Requires separate WebView / browser dev-tools; bridge-related bugs can be hard to trace. | diff --git a/docs/docs/misc/compatibility.mdx b/docs/docs/misc/compatibility.mdx index 92aa51c37..ab290ef06 100644 --- a/docs/docs/misc/compatibility.mdx +++ b/docs/docs/misc/compatibility.mdx @@ -22,7 +22,7 @@ installing. The table assumes you're using the latest patch of each library minor version. - + ## Platform differences @@ -69,7 +69,7 @@ selection color follow the system tint. On iOS and Android, the library does not sanitize HTML. It makes no guarantees that the markup it accepts or produces is safe. You are fully responsible for sanitizing any HTML you persist, render elsewhere, or accept from untrusted sources. -Sanitization *is* enforced on the web, as injecting unsafe HTML directly into the DOM poses severe security risks (like XSS). +Sanitization _is_ enforced on the web, as injecting unsafe HTML directly into the DOM poses severe security risks (like XSS). ### React Native layout ref methods diff --git a/docs/src/components/Compatibility/EnrichedHtmlCompatibility.tsx b/docs/src/components/Compatibility/EnrichedCompatibility.tsx similarity index 96% rename from docs/src/components/Compatibility/EnrichedHtmlCompatibility.tsx rename to docs/src/components/Compatibility/EnrichedCompatibility.tsx index caaa3ae6c..54eb50572 100644 --- a/docs/src/components/Compatibility/EnrichedHtmlCompatibility.tsx +++ b/docs/src/components/Compatibility/EnrichedCompatibility.tsx @@ -8,7 +8,6 @@ const REACT_NATIVE_VERSIONS = [ '0.84', '0.85', '0.86', - '0.87', ]; const LIBRARY_VERSIONS = [ @@ -32,7 +31,7 @@ function isSupported( ); } -export default function EnrichedHtmlCompatibility() { +export default function EnrichedCompatibility() { return (
diff --git a/docs/src/theme/MDXComponents.js b/docs/src/theme/MDXComponents.js index d53cb3511..fab0f703d 100644 --- a/docs/src/theme/MDXComponents.js +++ b/docs/src/theme/MDXComponents.js @@ -9,7 +9,7 @@ import Indent from '@site/src/components/Indent'; import Row from '@site/src/components/Row'; import Grid from '@site/src/components/Grid'; import { Yes, No, Version, Spacer } from '@site/src/components/Compatibility'; -import EnrichedHtmlCompatibility from '@site/src/components/Compatibility/EnrichedHtmlCompatibility'; +import EnrichedCompatibility from '@site/src/components/Compatibility/EnrichedCompatibility'; import { Badges } from '@swmansion/t-rex-ui'; export default { @@ -27,6 +27,6 @@ export default { No, Version, Spacer, - EnrichedHtmlCompatibility, + EnrichedCompatibility, Badges, }; From 0bc11a9eb4fad8180f6c578b837abb75576386aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Thu, 23 Jul 2026 17:35:44 +0200 Subject: [PATCH 05/10] fix: review changes --- docs/docs/misc/compatibility.mdx | 2 +- docs/docs/misc/contributing.md | 2 +- docs/docs/misc/roadmap.md | 2 +- .../src/components/Compatibility/EnrichedCompatibility.tsx | 7 +++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/docs/misc/compatibility.mdx b/docs/docs/misc/compatibility.mdx index ab290ef06..66b02ad32 100644 --- a/docs/docs/misc/compatibility.mdx +++ b/docs/docs/misc/compatibility.mdx @@ -65,7 +65,7 @@ own UI instead. The `cursorColor` prop is applied on Android and Web. On iOS the caret and selection color follow the system tint. -## HTML is not sanitized (Mobile) +### HTML is not sanitized (Mobile) On iOS and Android, the library does not sanitize HTML. It makes no guarantees that the markup it accepts or produces is safe. You are fully responsible for sanitizing any HTML you persist, render elsewhere, or accept from untrusted sources. diff --git a/docs/docs/misc/contributing.md b/docs/docs/misc/contributing.md index 523437388..cc6fe9ae8 100644 --- a/docs/docs/misc/contributing.md +++ b/docs/docs/misc/contributing.md @@ -18,7 +18,7 @@ packages: - this documentation site, in `docs/` (a standalone project with its own dependencies - see [Documentation](#documentation)). -Install dependencies for every package by running `yarn` in the root directory: +Install dependencies for the monorepo workspaces by running `yarn` in the root directory: ```sh yarn diff --git a/docs/docs/misc/roadmap.md b/docs/docs/misc/roadmap.md index 4844e4163..f4011ba78 100644 --- a/docs/docs/misc/roadmap.md +++ b/docs/docs/misc/roadmap.md @@ -20,4 +20,4 @@ Natively integrated right-to-left text direction for RTL languages. Support for screen readers and accessibility APIs, delivering a rich, inclusive text editing experience while aligning with WCAG -and ACA standards. +and ADA standards. diff --git a/docs/src/components/Compatibility/EnrichedCompatibility.tsx b/docs/src/components/Compatibility/EnrichedCompatibility.tsx index 54eb50572..476287235 100644 --- a/docs/src/components/Compatibility/EnrichedCompatibility.tsx +++ b/docs/src/components/Compatibility/EnrichedCompatibility.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { No, Version, Yes } from './index'; const REACT_NATIVE_VERSIONS = [ @@ -37,9 +38,11 @@ export default function EnrichedCompatibility() {
- + {REACT_NATIVE_VERSIONS.map(version => ( - + ))} From b0345487a93ca60362b98193c774ee6ec8cf2ca8 Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz <146986839+hejsztynx@users.noreply.github.com> Date: Fri, 24 Jul 2026 15:54:42 +0200 Subject: [PATCH 06/10] Update docs/docs/misc/compatibility.mdx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kacper Żółkiewski <74975508+kacperzolkiewski@users.noreply.github.com> --- docs/docs/misc/compatibility.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/misc/compatibility.mdx b/docs/docs/misc/compatibility.mdx index 66b02ad32..29e71da48 100644 --- a/docs/docs/misc/compatibility.mdx +++ b/docs/docs/misc/compatibility.mdx @@ -12,9 +12,9 @@ platforms. React Native Enriched HTML works **only** with the [React Native New Architecture (Fabric)](https://reactnative.dev/architecture/landing-page). -The New Architecture is enabled by default on recent React Native versions. If -your app still runs the old architecture, you'll need to switch before -installing. +The New Architecture is enabled by default in recent React Native versions. If +your app still uses the old architecture, you'll need to migrate to the New +Architecture before installing the library. ::: From dab6c7d55aef964ee3a0a99429a1a17cbb30bbc9 Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz Date: Fri, 24 Jul 2026 16:02:23 +0200 Subject: [PATCH 07/10] docs: acknowledge exampe-web app --- docs/docs/misc/contributing.md | 38 ++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/docs/docs/misc/contributing.md b/docs/docs/misc/contributing.md index cc6fe9ae8..b936e951a 100644 --- a/docs/docs/misc/contributing.md +++ b/docs/docs/misc/contributing.md @@ -10,11 +10,12 @@ Contributions are always welcome, no matter how large or small. This page will w ## Development workflow The project is a monorepo managed with -[Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains three +[Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains four packages: - the library package, in the root directory; -- an example app, in `apps/example/`; +- a native example app, in `apps/example/`; +- a web example app, in `apps/example-web/`; - this documentation site, in `docs/` (a standalone project with its own dependencies - see [Documentation](#documentation)). @@ -31,14 +32,18 @@ development. ::: -The [example app](https://github.com/software-mansion/react-native-enriched-html/tree/main/apps/example) -demonstrates the library and is how you test any changes you make. It's -configured to use the local version of the library, so your source changes are -reflected there: +The [native example app](https://github.com/software-mansion/react-native-enriched-html/tree/main/apps/example) +demonstrates the library on iOS and Android and is how you test any changes you +make there. The [web example app](https://github.com/software-mansion/react-native-enriched-html/tree/main/apps/example-web) +does the same for the web. Both are configured to use the local version of the +library, so your source changes are reflected there: - **JavaScript** changes show up without a rebuild. - **Native** changes (Objective-C, Swift, Java, Kotlin) require rebuilding the - example app. + native example app. + +The web example is a [Vite](https://vite.dev/) + [React](https://react.dev/) +app; changes to the library's web source are hot-reloaded by its dev server. ### Editing native code @@ -50,7 +55,7 @@ To edit the native code in an IDE: - **Android** - open `apps/example/android` in Android Studio. Find the sources under **react-native-enriched-html** in the **Android** view. -### Running the example app +### Running the native example app Start the Metro bundler: @@ -77,6 +82,16 @@ Running "EnrichedTextInputExample" with {"fabric":true,"initialProps":{"concurre Note the `"fabric":true` and `"concurrentRoot":true` properties. +### Running the web example app + +Start the Vite dev server: + +```sh +yarn example-web dev +``` + +The app is then served at `http://localhost:5173`. + ## Linting, types, and tests Make sure your code passes TypeScript and ESLint: @@ -277,9 +292,10 @@ The `package.json` file contains scripts for common tasks: - `yarn typecheck` - type-check files with TypeScript. - `yarn lint` - lint files with ESLint. - `yarn test` - run unit tests with Jest. -- `yarn example start` - start the Metro server for the example app. -- `yarn example android` - run the example app on Android. -- `yarn example ios` - run the example app on iOS. +- `yarn example start` - start the Metro server for the native example app. +- `yarn example android` - run the native example app on Android. +- `yarn example ios` - run the native example app on iOS. +- `yarn example-web dev` - start the Vite dev server for the web example app. - `yarn test:e2e` - run all E2E tests (mobile + web) sequentially. - `yarn test:e2e:mobile` - run mobile E2E tests on iOS and Android sequentially. - `yarn test:e2e:android` - run E2E tests on Android. From c0dc6997e7feed61b64074b99fe1e2c0b04ab84a Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz Date: Fri, 24 Jul 2026 16:03:14 +0200 Subject: [PATCH 08/10] docs: unnecessary rn new arch note --- docs/docs/misc/contributing.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/docs/misc/contributing.md b/docs/docs/misc/contributing.md index b936e951a..23bf7e80a 100644 --- a/docs/docs/misc/contributing.md +++ b/docs/docs/misc/contributing.md @@ -73,15 +73,6 @@ yarn example android yarn example ios ``` -To confirm the app is running with the New Architecture, look for a line like -this in the Metro logs: - -```sh -Running "EnrichedTextInputExample" with {"fabric":true,"initialProps":{"concurrentRoot":true},"rootTag":1} -``` - -Note the `"fabric":true` and `"concurrentRoot":true` properties. - ### Running the web example app Start the Vite dev server: From ef5f475ad32fc11c9c026c4e4767ef3a5a20c114 Mon Sep 17 00:00:00 2001 From: Krystian Sienkiewicz Date: Fri, 24 Jul 2026 16:07:52 +0200 Subject: [PATCH 09/10] docs: link swm --- docs/docs/misc/comparison-with-other-libraries.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/misc/comparison-with-other-libraries.md b/docs/docs/misc/comparison-with-other-libraries.md index c2af21289..0a4a56a87 100644 --- a/docs/docs/misc/comparison-with-other-libraries.md +++ b/docs/docs/misc/comparison-with-other-libraries.md @@ -28,7 +28,7 @@ which can be a deal-breaker for proprietary apps. ### React Native Enriched Markdown [React Native Enriched Markdown](https://github.com/software-mansion/react-native-enriched-markdown) -is another fully native library from Software Mansion (MIT-licensed, New +is another fully native library from [Software Mansion](https://swmansion.com) (MIT-licensed, New Architecture required). It focuses on **Markdown** rather than HTML: - **Text component** - renders a wide range of Markdown features natively, From 584c241115336d6e03f4e4a556410a7fdd9ce06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 27 Jul 2026 08:48:03 +0200 Subject: [PATCH 10/10] fix: library description --- docs/docs/misc/comparison-with-other-libraries.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/docs/misc/comparison-with-other-libraries.md b/docs/docs/misc/comparison-with-other-libraries.md index 0a4a56a87..1e08befa5 100644 --- a/docs/docs/misc/comparison-with-other-libraries.md +++ b/docs/docs/misc/comparison-with-other-libraries.md @@ -46,13 +46,11 @@ capabilities** and a matching display component. The two libraries can coexist i [10tap Editor](https://github.com/10play/10tap-editor) wraps [TipTap](https://tiptap.dev) (ProseMirror) inside a React Native WebView. -It offers a keyboard-aware toolbar and a familiar ProseMirror plugin system. ### React Native Pell Rich Editor [React Native Pell Rich Editor](https://github.com/wxik/react-native-rich-editor) -is a lightweight WebView-based editor that uses `contentEditable` and -`document.execCommand`. +is a lightweight WebView-based editor. ### Why native matters
{version} + {version} +