Known limitations
This section covers the rough edges worth knowing about before you hit them. Deliberate behaviours, constraints imposed by the underlying text stacks, and a few things that are simply not wired up yet.
Most of this page covers react-native-enriched-markdown 1.1.1, including its
web and macOS targets. The standalone native packages have their own lists at
the end: iOS package (0.4.0) and
Android package (0.3.0).
It describes the state today. For what is being worked on and what is merely planned, see the Roadmap; for the full per-feature matrix, Feature support.
Rendering
- Text selection cannot span segments. With
flavor="github"the document is split into independent segments (tables, fenced code blocks, block math, blockquotes and admonitions each become their own view). This enables features like horizontal table scrolling and the block context menu. The tradeoff is that selection starts and ends inside one segment.flavor="commonmark"renders a single text view and selects across the whole document. - Clamping text with
numberOfLinesis CommonMark-only. Underflavor="github"the content is laid out as independent block segments that cannot honor a document-wide line cap, so the prop is ignored, and so isellipsizeMode. - [Android] A clamped view is neither selectable nor tappable. While
numberOfLines > 0, text selection and link taps are off regardless ofselectable. This is a platform constraint: Android draws the truncation ellipsis only throughStaticLayout, but enabling selection or a link movement method promotes the text toDynamicLayout, which has nomaxLinessupport - React Native's ownTextbehaves the same way. Both are restored once the clamp is removed, and iOS keeps selection and links while clamped. - Raw HTML is not rendered. Inline HTML is disabled and HTML tags in the
source are ignored. The one allowlisted exception is a block-level
<video>tag, of which onlysrcis read - all video styling comes frommarkdownStyle.video. <br>does not force a line break. It is raw HTML like any other tag: an inline<br>stays in the output as literal text, and one on its own line is dropped. Use a hard break (two trailing spaces or a backslash) orhardSoftBreaks. Parser-level support is planned, but nothing in the parser touches it today.- There is no
colorSchemeprop. The library ships light-mode color defaults and leaves theming to you, exactly like React Native'sText- swapmarkdownStyleobjects onuseColorScheme(). See Dark mode.
The editor
- Block elements are limited.
EnrichedMarkdownTextInputsupports headings and lists; anything else - code blocks, blockquotes, tables - needs the read-only renderer. - The input is uncontrolled by design. The value lives in the native
component rather than in React state - read it through
onChangeMarkdownorgetMarkdown(), and write it withsetValue(). There is novalueprop to drive it from a render. - The placeholder ignores
writingDirection. It follows the host view's layout direction instead, so an RTL placeholder needs<View style={{ direction: 'rtl' }}>(orI18nManager.forceRTL(true)) around the input. - A fresh paragraph briefly inherits the previous direction. The first characters typed into an empty paragraph take the preceding paragraph's base direction; the first-strong pass corrects it on the next input event.
Accessibility
- Inline formatting is not split into separate elements. Bold, italic, underline, strikethrough, inline code, and spoiler are read as part of the surrounding paragraph - deliberate, since screen readers ignore visual emphasis by default. Only links and images become their own elements.
- The rotor is an iOS concept.
accessibilityLabels.rotor.*names the categories VoiceOver's rotor cycles through; TalkBack has no equivalent control, so those fields apply on iOS only and the rest ofaccessibilityLabelsworks the same on both platforms. - Task list items have no accessibility handling on either platform - a checkbox is read as plain list text, with no state and no toggle action.
- The editor is a single VoiceOver element on iOS. It reads its full plain text, is not announced with the native "text field" role, has no in-field cursor navigation or per-character echo, and refreshes its spoken value on re-focus rather than live.
- iOS blockquote backgrounds may break at link boundaries instead of spanning the full line. Visual only - it does not affect what is announced.
The full announcement model is in Accessibility.
Copy and clipboard
- Copy-as-HTML carries one direction for the whole document, and the two platforms derive it differently - iOS from the first paragraph, Android from the view's layout direction (and its table export carries none at all). A mixed-direction document will not paste with the per-paragraph layout you see in-app. Plain text and Markdown round-trip cleanly - see the copy-as-HTML caveat.
- The system Copy item cannot be hidden, only relabeled, through
selectionMenuConfig. onCopyPressdoes not fire for Copy as Markdown. It covers code copied from a fenced block - the header copy button, the context-menu Copy action, and the VoiceOver copy action.
Images
- Header-bearing image requests are not disk-cached. The disk layer is
managed by the HTTP stack (OkHttp /
NSURLCache) and keys on URL alone, so requests carryingimageRequestHeadersbypass it and are fetched again after a relaunch. Memory tiers and request deduplication still fold the headers into the cache identity. See Request headers. - The iOS disk cache ignores HTTP freshness. Its session runs with
NSURLRequestReturnCacheDataElseLoad, so a cached response is served regardless of its age ormax-age. Android's OkHttp cache behaves normally. - The image caches are process-global and cannot be cleared. There is no public API to inspect, clear, or invalidate them.
imageRequestHeadershas no effect on web - browsers do not allow custom headers on<img>requests.
Installation and build
- Yarn PnP is not supported. PnP stores dependencies as read-only archives,
so the postinstall cannot write the vendored native assets into the package.
Use the
node-moduleslinker, which is the norm for React Native projects anyway. - pnpm blocks the postinstall by default. Recent pnpm skips dependency
lifecycle scripts unless the package is listed in
onlyBuiltDependencies; without it the native build fails until the assets are downloaded manually. --ignore-scriptsand offline installs skip the asset download. Nothing fails at install time - it surfaces later as a native build error. See Requirements and Recovering a failed download.- Expo Go cannot change compiled-in features. Code highlighting and LaTeX
math are compiled into the binary, and Expo Go ships a fixed prebuilt one -
use a development build or
expo prebuild. See Expo. - iOS needs
pod installplus a clean build after changing theenriched-markdownblock. The podspec readspackage.jsonat install time, and Xcode's incremental build does not reliably relink the pod's static library when only its source list changes. Android reconfigures on every build. See Reducing binary size.
Web
The web editor is experimental and does not yet cover the native editor's surface - see Web editor. The rest of this section is about the renderer.
Spoilers lose their content on web. The parser emits a spoiler node, the web
renderer has none, and an unhandled node is dropped together with its subtree, so
a ||secret|| b renders as a b. The concealed text is gone, not concealed.
Also missing there: code-block syntax highlighting (fenced blocks render as plain
monospaced text, with no header or copy button) and any clipboard integration at
all. Every link opens in a new tab; target is not configurable. There is no
flavor prop: GFM is always on. LaTeX math needs the optional katex peer
dependency, falling back to raw $...$ without it. The accessibility strings are
hard-coded English and accessibilityLabels is stripped, as are a number of
other native-only props. See
Ignored props and
Not supported on web.
macOS
Block math is silently dropped under flavor="github" - the equation
produces no segment and no fallback text. Inline math renders. Beyond that: the
tail fade-in animation falls back to an instant reveal, system font-scale
changes are not observed, VoiceOver is stubbed, and selectionColor tints only
the selection background - AppKit's NSTextView does not expose caret and
handle tinting via tintColor. See
macOS support.
Testing
The Jest mock does not parse or render Markdown - it mostly stores and echoes raw text, so it is meant for testing your components' wiring rather than the library's rendering. Its ref-method surface is type-checked against the real component; its props are not, and a few display props are already unforwarded. See Testing.
iOS package
These apply to the standalone Swift package,
enriched-markdown-ios 0.4.0.
- Read-only. There is no editor, only the renderer - see the Roadmap.
- No container styling - no background, padding or border around the whole view.
- No image tap callback, no link previews, no flavor selector, no public streaming API, and no per-URL link variants or mentions.
- Block context menus cannot be extended. Code blocks, tables and block math have one (Copy, Copy as Markdown), but you cannot add your own items.
- Block images always take the available width. The theme sets only the
box height (
height,maxHeightoraspectRatio) and how the bitmap fills it (contentMode). .font(custom:size:)on inline elements is not applied. It compiles onLink,Strong,Emphasis,Strikethrough,UnderlineandHighlight, but those runs keep the surrounding block's font. Set custom fonts on block elements.- Some defaults do not adapt to dark mode. The default
Blockquotebackground andCodeBlockborder are fixed colors; set them in your theme if you support both appearances. - An image at the start of a paragraph is laid out as a block image, even when text follows it on the same line.
Android package
These apply to the standalone Android library,
enriched-markdown-android 0.3.0.
- Read-only. There is no editor, only the renderer - see the Roadmap.
- Code-block highlighting is opt-in and fixed. Fenced code is colored only
with the optional
code-highlightartifact andCodeHighlightPlugin(see Code highlighting); without them it renders in one color. The 14 bundled grammars cannot be trimmed or extended, the default token palette is made for a dark code block and does not follow the system theme. - Nested tables are not rendered. A table inside a list item or blockquote is dropped; only top-level tables are drawn.
- An HTML
<video>block makes the whole document render empty. The shared parser emits a video node that the Android parser has no node type for, so parsing fails and the view shows nothing. Do not feed it one. - The Compose wrapper does not expose selection colors, font scaling, the copy actions, label localization or break strategy.
- Accessibility labels are hard-coded English, with no override at any layer.
- Code blocks have a header, copy button and long-press menu only when they
scroll. With the default
CodeBlockLayout.Wrappinga code block is plain text in the selection;Scrollablegives each top-level block a language header, a copy button and a Copy / Copy as Markdown menu, but a code block inside a list item or quote still wraps. There is no per-block line-wrap toggle. - Block context menus cannot be extended. Tables, block math and scrollable code blocks have one, but you cannot add your own items.
- The composable renders nothing in an Android Studio
@Preview.
Something missing?
If one of these blocks what you are building, say so in an issue - it is how the Roadmap gets ordered. Pull requests are welcome too, see Contributing.