From Markup to Mobile Magic with @native-html/render
If your React Native app needs to display HTML content from a CMS, an API, or even an email template, you have a fundamental choice. You can embed a WebView, which is essentially a browser inside your app, or you can translate that HTML into actual native components. @native-html/render takes the second path, converting HTML markup into native <Text> and <View> components that feel right at home in your React Native app. No browser engine, no performance overhead, just native views all the way down.
Why Ditch the WebView
WebViews work, but they come with baggage. They spin up an entire browser engine, consume extra memory, introduce security considerations, and create a jarring visual disconnect between your native UI and the embedded web content. Scrolling behavior, touch handling, and styling all feel slightly off.
@native-html/render sidesteps all of that. It parses your HTML, processes the CSS, and outputs pure React Native components. The result looks and behaves like the rest of your app because it literally is the rest of your app. It supports a wide range of CSS properties that React Native does not natively handle, bridging the gap through its internal CSS processor.
What It Brings to the Table
- Pure native rendering with zero WebView overhead
- Extensive CSS support bridged to React Native styles, including properties like
list-style-typeandwhite-space - Custom renderers that let you override how any HTML tag gets displayed
- DOM manipulation hooks for altering the HTML tree before rendering
- Cross-platform support for iOS, Android, macOS, and Windows
- Full Expo compatibility
- Backed by Software Mansion, the team behind
react-native-reanimatedandreact-native-gesture-handler
Getting Started
Install the package with your preferred package manager.
npm install @native-html/render
yarn add @native-html/render
The library requires react and react-native as peer dependencies, so make sure those are already in your project.
Your First Render
Dropping HTML Into a Screen
The simplest use case takes an HTML string and renders it as native views.
import React from 'react';
import { useWindowDimensions } from 'react-native';
import RenderHtml from '@native-html/render';
const source = {
html: `
<h1>Welcome Back</h1>
<p>Your order has been <strong>confirmed</strong>.</p>
<ul>
<li>Item: Wireless Headphones</li>
<li>Quantity: 1</li>
<li>Status: Shipped</li>
</ul>
`
};
export default function OrderConfirmation() {
const { width } = useWindowDimensions();
return <RenderHtml contentWidth={width} source={source} />;
}
The contentWidth prop is required. It tells the renderer how wide the available space is, which matters for image scaling and responsive layout. Using useWindowDimensions is the most common approach.
Styling HTML Tags Your Way
You can apply React Native styles to specific HTML tags using tagsStyles. This gives you fine-grained control without modifying the source HTML.
import React from 'react';
import { useWindowDimensions } from 'react-native';
import RenderHtml from '@native-html/render';
const source = {
html: `
<h2>Daily Specials</h2>
<p class="highlight">Fresh sourdough bread — baked this morning.</p>
<p>Organic cold brew — locally roasted.</p>
`
};
const tagsStyles = {
h2: {
color: '#2d3436',
fontFamily: 'Georgia',
marginBottom: 8,
},
p: {
fontSize: 16,
lineHeight: 24,
color: '#636e72',
},
};
const classesStyles = {
highlight: {
backgroundColor: '#ffeaa7',
padding: 8,
borderRadius: 4,
},
};
export default function MenuScreen() {
const { width } = useWindowDimensions();
return (
<RenderHtml
contentWidth={width}
source={source}
tagsStyles={tagsStyles}
classesStyles={classesStyles}
/>
);
}
You can target tags, classes, and even IDs with tagsStyles, classesStyles, and idsStyles respectively. These merge with any inline styles already present in the HTML.
Loading Remote HTML
Instead of passing an HTML string, you can point the renderer at a URL and let it fetch the content.
import React from 'react';
import { useWindowDimensions } from 'react-native';
import RenderHtml from '@native-html/render';
export default function ArticleScreen() {
const { width } = useWindowDimensions();
return (
<RenderHtml
contentWidth={width}
source={{ uri: 'https://api.example.com/articles/42' }}
/>
);
}
The source prop accepts { html: string }, { uri: string }, or { dom: object }, giving you flexibility in how you feed it content.
Going Deeper
Building Custom Renderers
The real power of @native-html/render shows up when you need to customize how specific tags render. Say you want to turn every <img> tag into a rounded, bordered component with a loading placeholder.
import React from 'react';
import { useWindowDimensions, View, Image, ActivityIndicator } from 'react-native';
import RenderHtml from '@native-html/render';
import type { CustomBlockRenderer } from '@native-html/render';
const ImageRenderer: CustomBlockRenderer = ({ tnode }) => {
const src = tnode.attributes.src;
const [loading, setLoading] = React.useState(true);
return (
<View style={{ borderRadius: 12, overflow: 'hidden', marginVertical: 8 }}>
{loading && (
<ActivityIndicator
style={{ position: 'absolute', alignSelf: 'center', top: 40 }}
/>
)}
<Image
source={{ uri: src }}
style={{ width: '100%', height: 200 }}
resizeMode="cover"
onLoadEnd={() => setLoading(false)}
/>
</View>
);
};
const renderers = {
img: ImageRenderer,
};
const source = {
html: `
<p>Check out this photo:</p>
<img src="https://picsum.photos/600/400" alt="Random" />
`
};
export default function GalleryScreen() {
const { width } = useWindowDimensions();
return (
<RenderHtml
contentWidth={width}
source={source}
renderers={renderers}
/>
);
}
Custom renderers receive the parsed tree node, giving you access to attributes, children, and styles. You can override rendering for any tag, from <a> to <video>.
Manipulating the DOM Before Rendering
Sometimes you want to modify the HTML tree before it gets rendered. Maybe you need to strip certain tags, add wrapper elements, or transform content on the fly.
import React from 'react';
import { useWindowDimensions } from 'react-native';
import RenderHtml from '@native-html/render';
const source = {
html: `
<div>
<script>alert('sneaky')</script>
<p>This is <b>safe</b> content.</p>
<iframe src="https://evil.com"></iframe>
<p>And this is also fine.</p>
</div>
`
};
const ignoredDomTags = ['script', 'iframe'];
export default function SafeContentScreen() {
const { width } = useWindowDimensions();
return (
<RenderHtml
contentWidth={width}
source={source}
ignoredDomTags={ignoredDomTags}
/>
);
}
The ignoredDomTags prop strips unwanted elements entirely. For more advanced manipulation, you can use the domVisitors prop to walk the tree and transform nodes programmatically before they reach the renderer.
Handling Links and Navigation
When your HTML contains anchor tags, you probably want to handle taps rather than letting them do nothing.
import React from 'react';
import { useWindowDimensions, Linking, Alert } from 'react-native';
import RenderHtml from '@native-html/render';
const source = {
html: `
<p>Read our <a href="https://example.com/terms">Terms of Service</a>
or contact <a href="mailto:support@example.com">support</a>.</p>
`
};
const renderersProps = {
a: {
onPress: (_event: unknown, href: string) => {
if (href.startsWith('mailto:')) {
Linking.openURL(href);
} else {
Alert.alert('Navigate', `Opening: ${href}`);
}
},
},
};
export default function LegalScreen() {
const { width } = useWindowDimensions();
return (
<RenderHtml
contentWidth={width}
source={source}
renderersProps={renderersProps}
/>
);
}
The Architecture Under the Hood
The rendering pipeline works in three stages. First, the HTML string gets parsed into a DOM tree. Then the Transient Render Engine transforms that DOM into a Transient Render Tree, applying CSS processing along the way. Finally, the tree nodes get mapped to React Native components. This layered architecture is what makes the library so customizable. You can hook into any stage of the pipeline.
Wrapping Up
@native-html/render occupies a sweet spot in the React Native ecosystem. It handles the messy work of translating HTML and CSS into native components so you do not have to. Whether you are rendering blog posts from a headless CMS, displaying formatted email content, or building a rich content viewer, it gives you native performance without the WebView tax. With Software Mansion now backing the project, the library has the kind of long-term support that makes it easy to depend on in production.