An open source HTML-to-React component converter.
Paste a layout, choose JSX or TSX, split it into meaningful files,
and download clean React components β entirely in your browser.
Moving static markup into React is repetitive and easy to get wrong. HTML attributes need React equivalents, CSS strings need JavaScript objects, void elements need self-closing tags, and larger layouts often need to be separated into individual files.
MarkupShift handles that mechanical work while keeping the generated code readable and predictable. It is intentionally local-first: there are no accounts, uploads, API keys, or server-side processing.
| Capability | Result |
|---|---|
| JSX or TSX output | Switch languages without repasting the source |
| React-safe attributes | Converts class β className, for β htmlFor, and more |
| Inline style conversion | Turns CSS strings into React style objects |
| Smart file splitting | Creates one component per top-level element |
| Predictable naming | Infers names from data-component, id, class, or semantic tag |
| Flexible downloads | Download one file or every component in a ZIP |
| Fragment validation | Rejects full documents and explains what should be pasted |
| Private by design | Pasted HTML never leaves the browser |
<header class="site-header">...</header>
<main id="portfolio">...</main>
<footer>...</footer>markupshift-components/
βββ SiteHeader.tsx
βββ Portfolio.tsx
βββ Footer.tsx
βββ index.ts
Generated component:
import type React from "react";
export default function SiteHeader(): React.JSX.Element {
return (
<header className="site-header">
...
</header>
);
}The Split top-level elements strategy creates one component for each HTML element at the first level of the pasted fragment. It does not split nested children automatically.
For example, this input has only one top-level element:
<div class="content">
<h1>Hello</h1>
<p>Welcome to the page.</p>
<ul class="actions">
<li><a href="#next">Next</a></li>
</ul>
</div>div β top-level element
βββ h1 β nested child
βββ p β nested child
βββ ul β nested child
It produces one component because the heading, paragraph, and list all belong to the same parent <div>.
To create multiple components, paste multiple sibling elements at the first level:
<header data-component="Header">
<nav>...</nav>
</header>
<main data-component="Hero">
<h1>Hello, my name is Ethereal</h1>
<p>A responsive HTML template.</p>
</main>
<footer data-component="Footer">
<p>Released under Creative Commons.</p>
</footer>header β top-level element
main β top-level element
footer β top-level element
This produces:
markupshift-components/
βββ Header.tsx
βββ Hero.tsx
βββ Footer.tsx
βββ index.ts
data-component is optional, but it is the most predictable way to choose each filename and component name. The attribute is used only as a MarkupShift instruction and is removed from the generated JSX or TSX.
The generated index.ts is not another component. It is a barrel file that re-exports the generated components:
export { default as Header } from "./Header";
export { default as Hero } from "./Hero";
export { default as Footer } from "./Footer";flowchart LR
A[HTML input] --> B[Browser DOMParser]
B --> C[Attribute and style normalization]
C --> D{Component strategy}
D -->|Single| E[One JSX / TSX file]
D -->|Split| F[Named top-level components]
E --> G[Preview and copy]
F --> G
G --> H[Individual download or ZIP]
No API or external service is involved. The input is parsed with the browser DOM, normalized for React, and serialized into readable component files.
When splitting a document, MarkupShift checks each top-level element in this order:
data-component="ComponentName"- Element
id - First meaningful class name
- Semantic tag such as
header,nav,main, orfooter - A numbered fallback such as
Component1
<section data-component="Hero">...</section>
<section id="pricing">...</section>Produces Hero.tsx and Pricing.tsx.
Open markupshift.js.org, paste HTML, select the output language and component strategy, then download the generated files.
Everything runs locally in the current browser tab. MarkupShift does not store or transmit the pasted markup.
Paste the visual markup that would normally live inside <body>:
<header>...</header>
<main>...</main>
<footer>...</footer>Complete documents containing <!DOCTYPE>, <html>, <head>, or <body> are rejected with a clear validation message.
git clone https://github.com/malopestorres/markupshift.git
cd markupshift
npm install
npm run devOpen http://localhost:3000.
npm run lint
npm test
npm run buildMarkupShift focuses on deterministic syntax conversion. It does not infer business logic, extract repeated props, or rewrite external stylesheets. Inline event handler strings are deliberately converted into safe TODO callbacks so arbitrary pasted code is never executed.
The project currently:
- Converts HTML structure, attributes, comments, text, inline styles, and void elements
- Preserves
data-*andaria-*attributes - Supports single-component and top-level split strategies
- Accepts HTML fragments and rejects complete document wrappers
- Does not execute scripts or pasted event handlers
- Does not convert external CSS files
- Does not infer props, state, hooks, or application behavior
Ideas being considered:
- Broader SVG attribute support
- Optional Prettier formatting
- Drag-and-drop HTML file input
- Configurable component naming rules
- CSS class and stylesheet assistance
- More fixtures for malformed and browser-normalized HTML
Have another idea? Open a feature request.
Contributions are welcome. Good first contributions include adding HTML edge-case fixtures, expanding React attribute mappings, improving accessibility, or clarifying documentation.
Read CONTRIBUTING.md before opening a pull request. For incorrect conversion output, please use the bug report template and include the smallest HTML snippet that reproduces the issue.
MarkupShift is open source software released under the MIT License.