You can run React 7,897 with no build step, which is the fastest way to try an idea or to add one interactive widget to a page your server already renders. Three things have to be in place: React and React DOM as modules, a DOM node to render into, and something that understands JSX.
Older tutorials load react.development.js and react-dom.development.js as UMD scripts and use the globals React and ReactDOM. That recipe stops working at React 19, whose UMD builds were dropped: unpkg.com/react@19.3.0/umd/react.development.js returns 404. The replacement is an import map, which lets the browser resolve a bare specifier such as react to a URL, so the import statements of a real project also work in a single file.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="importmap">
{"imports": {
"react": "https://esm.sh/react@19.3.0",
"react-dom/client": "https://esm.sh/react-dom@19.3.0/client"
}}
</script>
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@8.0.5/babel.min.js"></script>
<style>body { font: 16px system-ui, sans-serif; margin: 20px } button { font: inherit }</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel" data-type="module">
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
function LikeButton() {
const [likes, setLikes] = useState(0);
return <p>
<button onClick={() => setLikes(likes + 1)}>Like</button>
{' '}{likes} {likes === 1 ? 'person likes' : 'people like'} this.
</p>;
}
createRoot(document.getElementById('root')).render(<LikeButton />);
</script>
</body>
</html>
Click the button and the sentence updates: setLikes schedules a re-render, LikeButton runs again with the new value, and React rewrites one text node. createRoot attaches React to a DOM container and is the only entry point in React 19 — ReactDOM.render was removed.
Two details are easy to miss. type="text/babel" hands the script to Babel 41,624 Standalone instead of the browser, and data-type="module" tells Babel the code contains import statements, so it re-injects the compiled result as a module. Babel Standalone compiles JSX with the classic runtime, turning <p> into React.createElement('p', ...), so React must be imported by name; drop that import and the page throws React is not defined. Build tools use the automatic runtime and need no such import (JSX Syntax and How It Compiles).