@nanogiants/react-native-render-html
v1.0.6
Published
A lightweight, customisable HTML renderer for React Native
Readme
@nanogiants/react-native-render-html
A lightweight, customisable HTML renderer for React Native.
Parses an HTML string and maps each recognised element to the appropriate React Native primitive — no native modules, no WebView. Images and text components are injected via render props, keeping the package free of any hard image-library dependency.
Installation
npm install @nanogiants/react-native-render-html
# or
yarn add @nanogiants/react-native-render-html
# or
pnpm add @nanogiants/react-native-render-htmlreact and react-native are peer dependencies and must already be present in your project.
Quick start
import { RenderHTML } from '@nanogiants/react-native-render-html';
import { Image } from 'react-native';
const html = `
<h1>Hello world</h1>
<p>This is a <strong>bold</strong> statement.</p>
<img src="https://picsum.photos/400/200" />
`;
export default function Screen() {
return (
<RenderHTML
html={html}
renderImage={(props) => <Image {...props} />}
baseStyle={{ fontSize: 16, lineHeight: 24, color: '#111' }}
onLinkPress={({ url }) => Linking.openURL(url)}
/>
);
}Props
html (required)
html: stringThe HTML string to parse and render.
renderImage
renderImage?: (props: ImageProps) => ReactNodeRender prop invoked for every image in the output: <img> tags and unordered-list bullet markers. Receives standard React Native ImageProps (source, style, alt, …).
When omitted the built-in React Native <Image> component is used.
// Using expo-image for better caching
import { Image } from 'expo-image';
<RenderHTML
html={html}
renderImage={(props) => <Image {...props} contentFit="contain" />}
/>renderTextComponent
renderTextComponent?: (props: TextProps) => ReactNodeRender prop invoked for every text node. Receives standard React Native TextProps. Use this to swap in a custom typography component.
<RenderHTML
html={html}
renderTextComponent={(props) => <MyAppText {...props} />}
/>baseStyle
baseStyle?: TextStyleBase text style applied to all elements before per-tag overrides. Commonly used to set a global font family, size, line height, or colour.
tagStyles
tagStyles?: Partial<TagStyles>Per-tag style overrides. Each key is an HTML tag name; the value is { text?: TextStyle; block?: ViewStyle }.
<RenderHTML
html={html}
tagStyles={{
h1: { text: { color: 'navy' } },
blockquote: { block: { borderLeftColor: '#6366f1' } },
code: { text: { fontFamily: 'JetBrainsMono' } },
section: { block: { marginVertical: 16 } },
}}
/>classesStyles
classesStyles?: { [className: string]: { text?: TextStyle; block?: ViewStyle } }Style overrides keyed by HTML class attribute value. Applied on top of the matching tag style.
<RenderHTML
html='<p class="lead">Introduction paragraph</p>'
classesStyles={{
lead: { text: { fontSize: 20, fontWeight: '600' } },
}}
/>listGap
listGap?: numberVertical gap between items in <ul> and <ol> lists. Maps to the flex gap property. Defaults to 0.
onLinkPress
onLinkPress?: (options: { url: string; title: string }) => voidCalled when the user taps an <a> element.
import { Linking } from 'react-native';
<RenderHTML
html={html}
onLinkPress={({ url }) => Linking.openURL(url)}
/>markerColor
markerColor?: stringColour of bullet markers in unordered lists. Falls back to the inherited text colour, then 'pink'.
renderLinkIcon
renderLinkIcon?: (href: string, style: ImageStyle) => ReactNodeRender prop called for every <a> element. Receives the raw href string and a pre-computed ImageStyle sized and tinted to match the surrounding text (height, width, tintColor, paddingTop).
Return a node to display an icon after the link text, or null/undefined to show nothing. When this prop is omitted no icon is rendered.
import { Image } from 'expo-image';
<RenderHTML
html={html}
renderLinkIcon={(href, style) => {
const isExternal = href.startsWith('http://') || href.startsWith('https://');
if (!isExternal) return null;
return <Image source={{ uri: MY_EXTERNAL_ICON_URI }} style={style} contentFit="contain" />;
}}
/>Supported HTML tags
| Category | Tags |
|---|---|
| Headings | h1 h2 h3 h4 h5 h6 |
| Sectioning | main section article aside nav header footer |
| Text block | p blockquote pre div |
| Inline | a b strong i em u s del mark small sup sub span code |
| Lists | ul ol li dl dt dd |
| Media | img |
| Table | table thead tbody tfoot tr th td |
| Other | hr br |
Unknown tags (e.g. script, style, iframe) are silently ignored.
Default styles
The renderer ships with sensible defaults for all supported tags. Every default can be overridden via tagStyles.
| Tag | Default |
|---|---|
| h1 | 32px, line-height 38.4, top/bottom margin |
| h2 | 24px, line-height 28.8, top/bottom margin |
| h3 | 18.72px, matching margin |
| h4–h6 | Scaled down accordingly |
| p | marginTop: 8 / marginBottom: 8 (collapsed at container edges) |
| a | Underlined; optional icon via renderLinkIcon |
| blockquote | Italic, 4px left border, grey background |
| pre / code | Monospace font, grey background |
| ul / ol | paddingLeft: 12, configurable gap |
| b / strong | Bold |
| i / em | Italic |
| u | Underline |
| s / del | Strikethrough |
| mark | backgroundColor: '#fff59d' |
| small | max(baseSize − 2, 12) |
| sup / sub | max(baseSize − 4, 10), aligned top/bottom |
| hr | 1px bottom border, marginVertical: 12 |
| img | flex: 1, height: 100, marginVertical: 8; aspect ratio corrected after async Image.getSize |
| th | Bold, padding: 8, 1px border |
| td | padding: 8 |
| section / article | marginVertical: 8 |
| header | marginBottom: 8 |
| footer | marginTop: 8 |
| main / aside / nav | No default margin (semantic wrappers) |
Known issues
The following elements are partially supported but do not yet render correctly in all cases:
| Tag | Issue |
|---|---|
| sub | Vertical offset relies on textAlignVertical, which is Android-only. No visual offset on iOS. |
| sup | Same as sub — no visual offset on iOS. |
| code | Rendered as an inline <Text> span. When used outside a <pre> block, padding and background may bleed into surrounding text or be clipped by the parent <Text>. |
| pre | Newlines and whitespace are not preserved. The HTML source has all newlines stripped before parsing, and all remaining whitespace is collapsed during text-node rendering. Code blocks appear as a single unwrapped line. |
License
Apache 2.0 — see LICENSE for details.
