/rich-text
Software Mansion's best practices for rich text in React Native using react-native-enriched and react-native-enriched-markdown. Use when building rich text editors, formatted text inputs, Markdown renderers, or any feature requiring inline styling, mentions, links, structured
$ npx -y skills add software-mansion-labs/skills --skill rich-text --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/rich-text
Context preview
The summary Claude sees to decide when to auto-load this skill.
Software Mansion's best practices for rich text in React Native using react-native-enriched and react-native-enriched-markdown. Use when building rich text editors, formatted text inputs, Markdown renderers, or any feature requiring inline styling, mentions, links, structured
SKILL.md
rich-text.SKILL.mdname: rich-text
description: "Software Mansion's best practices for rich text in React Native using react-native-enriched and react-native-enriched-markdown. Use when building rich text editors, formatted text inputs, Markdown renderers, or any feature requiring inline styling, mentions, links, structured text editing, or Markdown display. Trigger on: 'rich text editor', 'rich text input', 'text editor', 'react-native-enriched', 'react-native-enriched-markdown', 'EnrichedTextInput', 'EnrichedMarkdownText', 'formatted text input', 'WYSIWYG', 'mentions input', 'text formatting toolbar', 'markdown renderer', 'markdown display', 'render markdown', 'display markdown natively', 'LaTeX math', 'GFM tables', or any request to build rich text editing or Markdown rendering in React Native."
Rich Text in React Native
Software Mansion's production patterns for rich text editing and Markdown rendering in React Native.
There are two libraries that cover rich text use cases:
| Library | Component | Purpose | |---------|-----------|---------| | `react-native-enriched` | `EnrichedTextInput` | Rich text **editing** (input) | | `react-native-enriched-markdown` | `EnrichedMarkdownText` | Markdown **rendering** (display) |
Both libraries require the React Native New Architecture (Fabric) and support iOS and Android.
Choosing the right library
- **User needs to type/edit rich text** (bold, italic, mentions, links, inline images): use `react-native-enriched`
- **App needs to display Markdown content** (chat messages, documentation, AI responses): use `react-native-enriched-markdown`
- **Both editing and display**: use both libraries together
react-native-enriched (Editor)
`EnrichedTextInput` is a native, uncontrolled rich text input. It directly interacts with platform-specific components for performance, meaning it does not use React state for its value.
npm install react-native-enriched
Basic usage
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 RichEditor() {
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={stylesState?.bold.isActive ? 'Unbold' : 'Bold'}
color={stylesState?.bold.isActive ? 'green' : 'gray'}
onPress={() => ref.current?.toggleBold()}
/>
</View>
);
}Key concepts
**Toggling styles via ref**: All formatting is applied imperatively through the ref. Call `ref.current?.toggleBold()`, `ref.current?.toggleItalic()`, etc.
**Style detection via onChangeState**: The `onChangeState` callback fires whenever the style state changes (e.g., cursor moves into bold text). Each style reports three properties:
- `isActive`: The style is applied at the current selection (highlight the toolbar button)
- `isBlocking`: The style is blocked by another active style (disable the toolbar button)
- `isConflicting`: The style conflicts with another active style (toggling it removes the conflicting style)
**Inline vs paragraph styles**:
- Inline styles (bold, italic, underline, strikethrough, inline code) apply to the exact character range selected. With no selection, they apply to the next characters typed.
- Paragraph styles (headings, codeblock, blockquote, lists) apply to entire paragraphs (text between newlines). If the selection spans multiple paragraphs, all are affected.
**HTML output**: Get HTML via `ref.current?.getHTML()` (on-demand, returns a Promise) or the `onChangeHtml` callback (continuous, has performance cost for large documents). Prefer `getHTML()` when you only need HTML at save time.
**Setting content**: Use `defaultValue` prop for initial HTML content, or `ref.current?.setValue(html)` to update imperatively.
Supported styles
The `OnChangeStateEvent` key column shows the exact property name on the event object returned by `onChangeState`. Use these keys when reading style state (e.g. `stylesState.strikeThrough.isActive`). Note that casing varies (e.g. `strikeThrough` with capital T, `inlineCode` with capital C).
| Style | Toggle method | `OnChangeStateEvent` key | Type | |-------|--------------|--------------------------|------| | Bold | `toggleBold()` | `bold` | Inline | | Italic | `toggleItalic()` | `italic` | Inline | | Underline | `toggleUnderline()` | `underline` | Inline | | Strikethrough | `toggleStrikeThrough()` | `strikeThrough` | Inline | | Inline code | `toggleInlineCode()` | `inlineCode` | Inline | | H1 | `toggleH1()` | `h1` | Paragraph | | H2 | `toggleH2()` | `h2` | Paragraph | | H3 | `toggleH3()` | `h3` | Paragraph | | H4 | `toggleH4()` | `h4` | Paragraph | | H5 | `toggleH5()` | `h5` | Paragraph | | H6 | `toggleH6()` | `h6` | Paragraph | | Code block | `toggleCodeBlock()` | `codeBlock` | Paragraph | | Block quote | `toggleBlockQuote()` | `blockQuote` | Paragraph | | Ordered list | `toggleOrderedList()` | `orderedList` | Paragraph | | Unordered list | `toggleUnorderedList()` | `unorderedList` | Paragraph | | Checkbox list | `toggleCheckboxList(checked)` | `checkboxList` | Paragraph |
Links
Links are detected automatically (customizable via `linkRegex` prop) or applied manually:
// Set link on selected text
ref.current?.setLink(selection.start, selection.end, selectedText, url);
// Remove link
ref.current?.removeLink(start, end);
Use `onChangeSelection` to get selection position and `onLinkDetected` to detect when the cursor is near a link.
Mentions
Mentions support custom indicators (default: `@`). Set custom indicators via the `mentionIndi
Read more
name: rich-text description: "Software Mansion's best practices for rich text in React Native using react-native-enriched and react-native-enriched-markdown. Use when building rich text editors, formatted text inputs, Markdown renderers, or any feature requiring inline styling, mentions, links, structured text editing, or Markdown display. Trigger on: 'rich text editor', 'rich text input', 'text editor', 'react-native-enriched', 'react-native-enriched-markdown', 'EnrichedTextInput', 'EnrichedMarkdownText', 'formatted text input', 'WYSIWYG', 'mentions input', 'text formatting toolbar', 'markdown renderer', 'markdown display', 'render markdown', 'display markdown natively', 'LaTeX math', 'GFM tables', or any request to build rich text editing or Markdown rendering in React Native."
Rich Text in React Native
Software Mansion's production patterns for rich text editing and Markdown rendering in React Native.
There are two libraries that cover rich text use cases:
| Library | Component | Purpose | |---------|-----------|---------| | `react-native-enriched` | `EnrichedTextInput` | Rich text **editing** (input) | | `react-native-enriched-markdown` | `EnrichedMarkdownText` | Markdown **rendering** (display) |
Both libraries require the React Native New Architecture (Fabric) and support iOS and Android.
Choosing the right library
- **User needs to type/edit rich text** (bold, italic, mentions, links, inline images): use `react-native-enriched`
- **App needs to display Markdown content** (chat messages, documentation, AI responses): use `react-native-enriched-markdown`
- **Both editing and display**: use both libraries together
react-native-enriched (Editor)
`EnrichedTextInput` is a native, uncontrolled rich text input. It directly interacts with platform-specific components for performance, meaning it does not use React state for its value.
npm install react-native-enriched
Basic usage
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 RichEditor() {
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={stylesState?.bold.isActive ? 'Unbold' : 'Bold'}
color={stylesState?.bold.isActive ? 'green' : 'gray'}
onPress={() => ref.current?.toggleBold()}
/>
</View>
);
}Key concepts
**Toggling styles via ref**: All formatting is applied imperatively through the ref. Call `ref.current?.toggleBold()`, `ref.current?.toggleItalic()`, etc.
**Style detection via onChangeState**: The `onChangeState` callback fires whenever the style state changes (e.g., cursor moves into bold text). Each style reports three properties:
- `isActive`: The style is applied at the current selection (highlight the toolbar button)
- `isBlocking`: The style is blocked by another active style (disable the toolbar button)
- `isConflicting`: The style conflicts with another active style (toggling it removes the conflicting style)
**Inline vs paragraph styles**:
- Inline styles (bold, italic, underline, strikethrough, inline code) apply to the exact character range selected. With no selection, they apply to the next characters typed.
- Paragraph styles (headings, codeblock, blockquote, lists) apply to entire paragraphs (text between newlines). If the selection spans multiple paragraphs, all are affected.
**HTML output**: Get HTML via `ref.current?.getHTML()` (on-demand, returns a Promise) or the `onChangeHtml` callback (continuous, has performance cost for large documents). Prefer `getHTML()` when you only need HTML at save time.
**Setting content**: Use `defaultValue` prop for initial HTML content, or `ref.current?.setValue(html)` to update imperatively.
Supported styles
The `OnChangeStateEvent` key column shows the exact property name on the event object returned by `onChangeState`. Use these keys when reading style state (e.g. `stylesState.strikeThrough.isActive`). Note that casing varies (e.g. `strikeThrough` with capital T, `inlineCode` with capital C).
| Style | Toggle method | `OnChangeStateEvent` key | Type | |-------|--------------|--------------------------|------| | Bold | `toggleBold()` | `bold` | Inline | | Italic | `toggleItalic()` | `italic` | Inline | | Underline | `toggleUnderline()` | `underline` | Inline | | Strikethrough | `toggleStrikeThrough()` | `strikeThrough` | Inline | | Inline code | `toggleInlineCode()` | `inlineCode` | Inline | | H1 | `toggleH1()` | `h1` | Paragraph | | H2 | `toggleH2()` | `h2` | Paragraph | | H3 | `toggleH3()` | `h3` | Paragraph | | H4 | `toggleH4()` | `h4` | Paragraph | | H5 | `toggleH5()` | `h5` | Paragraph | | H6 | `toggleH6()` | `h6` | Paragraph | | Code block | `toggleCodeBlock()` | `codeBlock` | Paragraph | | Block quote | `toggleBlockQuote()` | `blockQuote` | Paragraph | | Ordered list | `toggleOrderedList()` | `orderedList` | Paragraph | | Unordered list | `toggleUnorderedList()` | `unorderedList` | Paragraph | | Checkbox list | `toggleCheckboxList(checked)` | `checkboxList` | Paragraph |
Links
Links are detected automatically (customizable via `linkRegex` prop) or applied manually:
// Set link on selected text ref.current?.setLink(selection.start, selection.end, selectedText, url); // Remove link ref.current?.removeLink(start, end);
Use `onChangeSelection` to get selection position and `onLinkDetected` to detect when the cursor is near a link.
Mentions
Mentions support custom indicators (default: `@`). Set custom indicators via the `mentionIndi
Software Mansion's set of skills for AI-assisted React Native development.
Repo: software-mansion-labs/skills
Other skills on software-mansion-labs-skills.
- /detour-onboarding
Complete onboarding guide for developers who are new to Detour, the open-source deferred deep linking SDK by Software Mansion. Use this skill whenever a user asks what Detour is, how to get started with Detour, how to set up deep linking with Detour, how to install the Detour
Open skill - /migrate-to-detour
Use when the user mentions migrating deep links, switching away from Branch or AppsFlyer, replacing their deep linking SDK, setting up Detour deep linking for the first time, or asks how Branch/AppsFlyer concepts map to Detour. Covers the complete migration end to end - Detour
Open skill - /expo-horizon
Software Mansion's guide for migrating Expo SDK apps to Meta Quest using expo-horizon packages. Use when adding Meta Quest or Meta Horizon OS support to an existing Expo or React Native project. Trigger on: Meta Quest, Horizon OS, Quest 2, Quest 3, Quest 3S, VR app,
Open skill - /fishjam
Software Mansion's Fishjam — hosted WebRTC platform for video, audio, and one-to-many livestreaming. MUST USE before writing, reviewing, or debugging ANY code that talks to a Fishjam instance from a backend (Node, Python) or a client (React web, React Native / Expo). Routes to
Open skill - /js-server-sdk
Node.js / TypeScript server SDK for Fishjam — backends that create rooms, mint peer tokens, listen to server notifications, and run agents. Use when writing a Node.js / Express / Fastify / Hono / NestJS backend that talks to Fishjam, sets up a webhook receiver, runs an AI agent,
Open skill - /platform
Fishjam platform fundamentals — domain model and auth shared by all SDKs. Covers glossary (room, peer, track, agent, streamer, viewer), the four room types (conference / audio_only / livestream / audio_only_livestream), two-tier auth (management vs peer tokens), Sandbox vs
Open skill

