A workshop where HTML transforms into native mobile views

From Markup to Mobile Magic with @native-html/render

The Gray Cat
The Gray Cat

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-type and white-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-reanimated and react-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.