rehype plugin to transform HTML into JSX elements for React, Preact, Solid, Svelte, Vue and other frameworks
npx @tessl/cli install tessl/npm-rehype-react@8.0.0rehype-react is a unified/rehype plugin that transforms HTML (hast syntax trees) into JSX elements compatible with multiple JavaScript frameworks including React, Preact, Solid, Svelte, and Vue. It serves as a bridge between HTML content and component-based applications, enabling developers to render HTML structures as JSX components with framework-specific optimizations.
npm install rehype-reactimport rehypeReact from "rehype-react";For re-exported types:
import rehypeReact, { Components, Options } from "rehype-react";import { Fragment, createElement } from "react";
import * as prod from "react/jsx-runtime";
import rehypeParse from "rehype-parse";
import rehypeReact from "rehype-react";
import { unified } from "unified";
// Production JSX runtime configuration
const production = {
Fragment: prod.Fragment,
jsx: prod.jsx,
jsxs: prod.jsxs
};
// Transform HTML to JSX
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(rehypeReact, production);
const html = '<h2>Hello, world!</h2><p>Welcome to my page 👀</p>';
const file = await processor.process(html);
const jsxElement = file.result; // JSX.Element ready to renderrehype-react operates within the unified ecosystem as a compiler plugin:
Main plugin function that transforms HTML into JSX for various frameworks.
/**
* Turn HTML into preact, react, solid, svelte, vue, etc.
* Registers a compiler that returns a JSX.Element where compilers typically return string.
* When using .stringify on unified, the result is such a JSX.Element.
* When using .process (or .processSync), the result is available at file.result.
*
* @param options - Configuration (required)
* @returns Nothing (undefined) - Plugin registers itself on the processor
*/
declare function rehypeReact(options: Options): undefined;
export default rehypeReact;Complete configuration interface for the plugin.
interface Options {
/** Fragment component from JSX runtime (required) */
Fragment: Fragment;
/** Dynamic JSX function (required in production mode) */
jsx?: Jsx;
/** Static JSX function (required in production mode) */
jsxs?: Jsx;
/** Development JSX function (required in development mode) */
jsxDEV?: JsxDev;
/** Custom component mapping (optional) */
components?: Partial<Components>;
/** Whether to use jsxDEV (development) or jsx/jsxs (production) */
development?: boolean;
/** Specify casing for attribute names */
elementAttributeNameCase?: 'html' | 'react';
/** Pass the hast element node to components */
passNode?: boolean;
/** HTML or SVG namespace context */
space?: 'html' | 'svg';
/** Specify casing for CSS property names in style objects */
stylePropertyNameCase?: 'css' | 'dom';
/** Convert obsolete align props to CSS style props */
tableCellAlignToStyle?: boolean;
}Type definition for custom component mapping.
/**
* Possible components to use for custom element mapping.
* Maps HTML element names to custom React/JSX components.
*/
type Components = {
[TagName in keyof JSX.IntrinsicElements]?: ComponentType<any>;
} & {
[key: string]: ComponentType<any>;
};Core JSX runtime function signatures and component types compatible with multiple frameworks.
/** Global JSX namespace for element types - Framework agnostic */
declare global {
namespace JSX {
interface Element {}
interface IntrinsicElements {
[elemName: string]: any;
}
}
}
/**
* Component type definition - compatible with React, Preact, Solid, Vue
* @template P - Props type for the component
*/
type ComponentType<P = {}> = (props: P) => JSX.Element | null;
/**
* Node type for children and fragment content
* Used by React, Preact, and other JSX frameworks
*/
type ReactNode = JSX.Element | string | number | boolean | null | undefined | ReactNode[];
/**
* JSX runtime fragment component
* Maps to: React.Fragment, preact.Fragment, solid-js.Fragment, vue.Fragment
*/
type Fragment = ComponentType<{ children?: ReactNode }>;
/**
* Production JSX runtime function for dynamic content
* Maps to: react/jsx-runtime.jsx, preact.createElement, solid-js/jsx-runtime.jsx
* @param type - Element type (string for HTML elements, function for components)
* @param props - Element props/attributes
* @param key - Optional React key for list items
*/
type Jsx = (
type: any,
props: Record<string, any>,
key?: string | number
) => JSX.Element;
/**
* Development JSX runtime function with additional debugging info
* Maps to: react/jsx-dev-runtime.jsxDEV
* @param type - Element type (string for HTML elements, function for components)
* @param props - Element props/attributes
* @param key - Optional React key for list items
* @param isStaticChildren - Whether children are static (React optimization)
* @param source - Source location for debugging (React DevTools)
* @param self - Component instance (React DevTools)
*/
type JsxDev = (
type: any,
props: Record<string, any>,
key?: string | number,
isStaticChildren?: boolean,
source?: { fileName: string; lineNumber: number; columnNumber: number },
self?: any
) => JSX.Element;Different JSX frameworks require specific configuration options:
import { unified } from "unified";
import rehypeParse from "rehype-parse";
import rehypeReact from "rehype-react";
import * as prod from "react/jsx-runtime";
const reactOptions = {
Fragment: prod.Fragment,
jsx: prod.jsx,
jsxs: prod.jsxs,
elementAttributeNameCase: 'react', // Uses className instead of class
stylePropertyNameCase: 'dom' // Uses backgroundColor instead of background-color
};
// Usage with React
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(rehypeReact, reactOptions);import { unified } from "unified";
import rehypeParse from "rehype-parse";
import rehypeReact from "rehype-react";
import { Fragment, createElement } from "preact";
const preactOptions = {
Fragment: Fragment,
jsx: createElement,
jsxs: createElement,
elementAttributeNameCase: 'html', // Uses class instead of className
stylePropertyNameCase: 'dom'
};
// Usage with Preact
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(rehypeReact, preactOptions);import { unified } from "unified";
import rehypeParse from "rehype-parse";
import rehypeReact from "rehype-react";
import { Fragment } from "solid-js";
import { jsx } from "solid-js/jsx-runtime";
const solidOptions = {
Fragment: Fragment,
jsx: jsx,
jsxs: jsx,
elementAttributeNameCase: 'html', // Uses class
stylePropertyNameCase: 'css' // Uses kebab-case properties
};
// Usage with Solid
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(rehypeReact, solidOptions);import { unified } from "unified";
import rehypeParse from "rehype-parse";
import rehypeReact from "rehype-react";
import { Fragment } from "vue";
import { jsx, jsxs } from "vue/jsx-runtime";
const vueOptions = {
Fragment: Fragment,
jsx: jsx,
jsxs: jsxs,
elementAttributeNameCase: 'html',
stylePropertyNameCase: 'dom'
};
// Usage with Vue
const processor = unified()
.use(rehypeParse, { fragment: true })
.use(rehypeReact, vueOptions);The plugin supports both development and production JSX transforms:
import * as dev from "react/jsx-dev-runtime";
const developmentOptions = {
Fragment: dev.Fragment,
jsxDEV: dev.jsxDEV,
development: true // Enables development mode
};import * as prod from "react/jsx-runtime";
const productionOptions = {
Fragment: prod.Fragment,
jsx: prod.jsx,
jsxs: prod.jsxs,
development: false // Default: production mode
};Replace HTML elements with custom components:
import { MyButton, MyLink } from "./components";
const customOptions = {
Fragment: prod.Fragment,
jsx: prod.jsx,
jsxs: prod.jsxs,
components: {
button: MyButton, // All <button> elements become <MyButton>
a: MyLink, // All <a> elements become <MyLink>
h1: 'h2' // All <h1> elements become <h2>
}
};When using custom components, you can access the original hast node:
const MyHeading = ({ children, node, ...props }) => {
// node contains the original hast element
console.log('Original element:', node.tagName);
return <h2 {...props}>{children}</h2>;
};
const nodeOptions = {
Fragment: prod.Fragment,
jsx: prod.jsx,
jsxs: prod.jsxs,
components: {
h1: MyHeading
},
passNode: true // Enables node prop
};The plugin preserves meaningful whitespace while processing HTML:
// HTML with whitespace
const htmlWithWhitespace = `
<table>
<tbody>
<tr>
<th>
Header
</th>
<td>
Data
</td>
</tr>
</tbody>
</table>
`;
// Whitespace is preserved in the JSX output
const result = await processor.process(htmlWithWhitespace);By default, obsolete align attributes on table cells are converted to CSS styles:
// HTML: <th align="right">Header</th>
// Becomes: <th style={{textAlign: 'right'}}>Header</th>
// To disable this behavior:
const options = {
Fragment: prod.Fragment,
jsx: prod.jsx,
jsxs: prod.jsxs,
tableCellAlignToStyle: false // Keep align attribute as-is
};DOCTYPE declarations are automatically filtered out during transformation as they are not valid JSX:
// HTML with doctype
const htmlWithDoctype = '<!DOCTYPE html><h1>Hello</h1>';
// Only the h1 element is transformed; doctype is ignored
const result = await processor.process(htmlWithDoctype);
// Result: <h1>Hello</h1>The plugin may throw errors in these cases:
Fragment, jsx, jsxs, or jsxDEV) are not provideddevelopment: true but jsxDEV is not provided, or vice versahast-util-to-jsx-runtime encounters invalid HTML structures⚠️ XSS Warning: Using rehype-react with untrusted HTML can expose applications to cross-site scripting attacks. Always sanitize HTML before transformation:
import rehypeSanitize from "rehype-sanitize";
const safeProcessor = unified()
.use(rehypeParse, { fragment: true })
.use(rehypeSanitize) // Sanitize before transformation
.use(rehypeReact, options);The plugin extends unified's TypeScript definitions:
declare module 'unified' {
interface CompileResultMap {
/** JSX Element result type for rehype-react */
JsxElement: JSX.Element;
}
}This enables proper TypeScript inference when using the plugin with unified processors.