CLI tool
roman01la/html-to-react-components avatar
roman01la/html-to-react-components

html-to-react-components: splitting annotated HTML into a component tree

Converts HTML pages into React components

2,175 stars137 forksJavaScriptMIT

At a glance

What is it?
html-to-react-components is a CLI and Node library that finds elements marked with data-component in a pile of HTML and emits one React module per element, TypeScript by default. The annotation step is manual; the translation is not.
Who is it for?
html-to-react-components is worth reaching for when a designer hands you static HTML that already has the structure you want to keep, because it preserves that nesting through imported child modules instead of flattening it. It is the wrong tool when the HTML has no meaningful component boundaries, since data-component is something you add by hand and everything else in the file becomes one anonymous blob of JSX.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two attributes decide what becomes a component and what becomes a prop

The conversion is driven by markup you write yourself. An element carrying a data-component attribute becomes its own module, and the attribute value is the name of the React component. Everything nested inside that element is preserved by generating imports for the child components and substituting them at the right place, so the output is a tree of modules rather than one large file.

The second attribute controls the boundary between HTML attributes and React props. Prefixing an attribute with public: promotes it to a prop, and the prefix is stripped in the output.

html
<input public:type="text" id="input" data-component="Input" />

That single line is the whole contract. data-component="Input" names the module, public:type becomes a typed prop called type, and id is left as a plain attribute on the rendered element because it was not marked public. Anything you do not mark stays inside the component, which is how you keep structural classes and ids out of the API surface of a component.

The reasoning behind the tool is stated plainly in the README: you have just been handed a pile of HTML from a designer, and the first thing you would do is break it into React components. That step is mechanical, so it should not be typed by hand.

html2react takes a glob, not a file list

The CLI is installed globally and then pointed at a pattern.

bash
$ npm i -g html-to-react-components

Node.js 18 or later is required, and package.json states the same requirement as an engines field. The binary is html2react, and it accepts any glob so a whole directory of templates can be converted in one pass:

bash
$ html2react "./src/*.html"

The README makes a point of quoting the pattern, because the recursive form needs double quotes to keep the shell from expanding it before the tool sees it:

bash
$ html2react "./src/**/*.html"

Files land in a components directory under the current directory by default, with a tsx extension, or js when TypeScript is turned off. That default is worth noting because it writes into whatever directory you happen to run the command from. The output path, the extension and the naming scheme are all options, and the delimiter option controls filenames: pass one and the component name is split into lower-cased words joined by that character, pass none and the name from the HTML is used unchanged.

Component shape and module format are separate choices. componentType takes functional, which is the default, class or es5. moduleType takes es for an EcmaScript module, the default, or cjs for CommonJS. Four independent axes of output style, which is more flexibility than most one-file generators offer and also more to keep straight when you are diffing generated code.

TypeScript output is the default rather than a flag you find later

The current default is worth stating first because it is the sort of thing that changes without a version bump in the docs. Output is TypeScript by default, and components with public: props are generated with typed interfaces derived from the annotation.

tsx
// in component's module
interface InputProps {
  type: string;
}
const Input = ({ type }: InputProps) => <input type={type} id="input" />;

The interface is generated, not written, and it is derived from whatever the public: attributes say. Mark four attributes public and you get a four field interface. The types are strings rather than anything more specific, which follows from the fact that HTML attributes have no type information to draw on.

If you want plain JavaScript, the typescript option is set to false, and the CLI equivalent is the --no-typescript flag. The package version in package.json is 3.0.0, which suggests the TypeScript default arrived with a major version, and the file extension follows the setting: tsx by default, js when TypeScript is off.

This is where the tool and its documentation are worth reading together rather than separately. The README's own framing is that a components tree is ready to be rendered once generated, and typed props are part of that promise rather than an add-on.

The Node API hands back module sources, not a rendered tree

The programmatic entry point is async and resolves to an object whose keys are component names and whose values are the generated module source as strings. That shape matters: nothing is written to disk unless you ask for it, so the library fits a build step that wants to inspect or transform the output first.

js
await extractReactComponents(html, {
  componentType: "functional", // "functional", "class", or "es5"
  moduleType: "es",            // "es" or "cjs"
  typescript: true,            // true (default) or false
  output: {
    path: "components",        // output directory
    fileExtension: "tsx",      // file extension
  },
});

The same options object drives both the CLI and the library, and the output block is what the CLI uses to decide where files land. In the README's worked example, a Header containing Heading and Nav, with Nav holding two ListItem elements, resolves to a map where Header imports Heading and Nav and replaces the original markup with the imported components. Repeated elements of the same name collapse into one module, so two list items become one ListItem module used twice.

The parsing stack is PostHTML, with posthtml-parser for the tree and posthtml-render for the untouched parts, plus Babel for parsing and generating JavaScript and Prettier for formatting. That combination explains an important limitation noted below in the browser section: formatting is part of the Node path.

For trying it without installing anything, the project hosts an online REPL under its GitHub Pages site, alongside a video demo and a DevTools based annotation workflow.

The browser bundle needs a compile time flag and loses formatting

There is a second distribution target in package.json. The main entry is lib/index.js for Node, and the browser field points at dist/html2react.js, built by a separate webpack configuration. webpack.browser.js is in the repository tree next to webpack.config.js.

Using it in a browser build requires an environment variable named IN_BROWSER to be set at compile time, which disables the Node specific modules. The README's example uses webpack's DefinePlugin.

js
  plugins: [
    new webpack.DefinePlugin({
      IN_BROWSER: JSON.stringify(true),
    }),
  ],

The second condition is the one to keep in mind: code formatting is not included in the in-browser bundle. Since Prettier is a Node dependency and part of the generated output in the Node path, a browser consumer gets valid modules without the formatting pass. That is a small difference until you notice your diffs after switching targets.

There is also a browser extension in the wider project, extract-to-react, built on top of this library, which lets you select elements in Chrome or Chromium and load the extracted HTML and CSS into CodePen or JSFiddle. The README is honest about the awkward part of the annotation workflow: you cannot see rendered output inside a code editor, so people annotate through DevTools and copy the markup back into the files that will be processed.

Maintenance signals are thin, and the CI badge is a legacy one

A few practical facts before you depend on this. The repository has no published GitHub releases, so the version history lives in CHANGELOG.md rather than on the releases page, and package.json sits at version 3.0.0. The last push to the repository was on 2026-04-14.

The build scripts in package.json still use the short production flag, with build:example running webpack -p and build:browser running webpack -p with the browser config, against a devDependency on webpack 4. Test runs through jest against the test directory, and coveralls is wired in for coverage. The README badge points at travis-ci.org and the tree contains a .travis.yml, which is a generation of continuous integration configuration that GitHub-hosted projects have largely moved off.

Licensing is MIT, stated in package.json and in a LICENSE.md file. That is the permissive end of the scale, which matters here for a tool whose output ends up in your component tree: generated modules are ordinary source files you own once written, and the MIT terms leave you free to edit them afterwards.

That last point is the practical reason to think of this as scaffolding. It is at its best on the first pass over a static template, and at its worst on every subsequent hand edit, because generated modules invite regeneration that will overwrite the fixes you made.

Editorial conclusion

html-to-react-components is worth reaching for when a designer hands you static HTML that already has the structure you want to keep, because it preserves that nesting through imported child modules instead of flattening it. It is the wrong tool when the HTML has no meaningful component boundaries, since data-component is something you add by hand and everything else in the file becomes one anonymous blob of JSX. Two things to verify before you commit. Check the npm version against the flags you intend to use, because this package has gone through a TypeScript-by-default change and the option names are not stable across majors. And check whether your project needs the browser bundle at all, since the in-browser path requires IN_BROWSER to be set at compile time and drops code formatting. Start by running html2react over a single annotated file and reading the emitted module before you point it at a directory.

Frequently asked questions

How do I mark up HTML so html-to-react-components can convert it?

Add a data-component attribute whose value is the component name, and prefix any attribute you want exposed as a React prop with public:. An input marked public:type="text" and data-component="Input" becomes an Input module with a type prop, while unmarked attributes such as id stay on the rendered element.

Does html-to-react-components generate TypeScript or JavaScript?

TypeScript by default, with typed interfaces generated for components that have public: props. The typescript option is true by default and can be set to false, and the CLI equivalent is the --no-typescript flag. Output files use a tsx extension unless you disable TypeScript, which switches the extension to js.

How do I run html-to-react-components over a whole folder of HTML?

Install the package globally with npm i -g html-to-react-components and run html2react with a glob pattern, for example html2react "./src/**/*.html". The README asks for double quotes around the pattern so the shell does not expand it first. Node.js 18 or later is required.

Can html-to-react-components run inside a browser build?

There is a browser bundle pointed at from the browser field of package.json, but it needs an IN_BROWSER environment variable defined at compile time, for example with webpack DefinePlugin, so that Node specific modules are disabled. Code formatting is not included in that bundle, so browser consumers get unformatted module output.

Official sources

  1. Issues
  2. License: MIT
  3. Project website
  4. README
  5. roman01la/html-to-react-components on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/roman01la-html-to-react-components.svg)](https://hysenlabs.com/projects/roman01la-html-to-react-components)