JSFiddle React: the same app as one HTML file

JSFiddle is an online code playground with HTML, CSS and JavaScript panels. A React fiddle moves into a plain HTML file as a div, three script tags and your own script.

"JSFiddle" here means the online code playground at jsfiddle.net, where you write HTML, CSS and JavaScript in separate panels and watch the result beside them.

A fiddle can run React, and so can a plain .html file. You need one <div>, three script tags and a script of your own.

Try the single-file version first. Click the button.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>React in one HTML file</title>
<style>
  body { margin: 0; padding: 24px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .card { max-width: 340px; padding: 18px 20px; border-radius: 12px; background: #fff; box-shadow: 0 6px 20px rgba(0, 0, 0, .1); }
  h1 { margin: 0 0 6px; font-size: 20px; }
  p { margin: 0 0 14px; color: #4b5563; }
  button { font: inherit; padding: 8px 16px; border: 0; border-radius: 8px; background: #2563eb; color: #fff; cursor: pointer; }
  button:active { background: #1d4ed8; }
</style>
</head>
<body>
<!-- 1. the element React will fill (JSFiddle's HTML panel) -->
<div id="root"></div>

<!-- 2. React, React DOM, then Babel, in this order (JSFiddle's Resources list) -->
<script crossorigin src="https://unpkg.com/react@18.3.1/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.26.4/babel.min.js"></script>

<!-- 3. your code (JSFiddle's JavaScript panel, language set to Babel + JSX) -->
<script type="text/babel" data-presets="react">
  const { useState } = React;

  function Counter() {
    const [count, setCount] = useState(0);
    return (
      <div className="card">
        <h1>Clicked {count} times</h1>
        <p>React {React.version}, running from a single file.</p>
        <button onClick={() => setCount(count + 1)}>Add one</button>
      </div>
    );
  }

  // createRoot needs a real element, so #root must already exist above
  ReactDOM.createRoot(document.getElementById('root')).render(<Counter />);
</script>
</body>
</html>
React 18.3.1 and Babel loaded from unpkg.com, one component, no build step. Edit the code and the example reruns.

The comments in the code say which fiddle panel each part came from.

What a fiddle is made of

JSFiddle's getting-started page describes four panels: HTML, CSS, JavaScript and a result preview. Libraries are added separately, either as a URL or by typing a name and picking it from the CDNJS suggestions. A single HTML file holds the same pieces.

Each fiddle panel becomes one part of the file. The Result panel is the browser showing it.
Each fiddle panel becomes one part of the file. The Result panel is the browser showing it.
Fiddle part In a single HTML file
HTML panel Elements inside body, such as the div React fills
CSS panel A style element in head
JavaScript panel A script element at the end of body
External resources script tags above your own script
Result panel The browser window

In a file you assemble those pieces yourself, and the order matters.

React needs three script tags

React's CDN page lists two files for React 18: react and react-dom. Add Babel's standalone build as a third so the browser can read JSX. They load in this order, and your own script comes last.

Libraries first, your script last. A script that runs early cannot see what has not loaded yet.
Libraries first, your script last. A script that runs early cannot see what has not loaded yet.
<div id="root"></div>

<script crossorigin src="https://unpkg.com/react@18.3.1/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.26.4/babel.min.js"></script>

<script type="text/babel" data-presets="react">
  // your component and ReactDOM.createRoot(...) go here
</script>

Pin the version in the URL, as above, so the page does not change when a new release comes out.

React's docs say the development files are not suitable for production. For a page you send around, switch to the .production.min.js files, as the finished example below does.

Why JSX needs Babel

JSX is a syntax extension to JavaScript, not JavaScript itself. A browser cannot read <Counter /> inside a normal script.

Babel rewrites JSX into plain function calls before the browser runs it.
Babel rewrites JSX into plain function calls before the browser runs it.

Babel's standalone build compiles every script tag whose type is text/babel. The browser itself treats a script with an unknown type as a data block and does not run it.

So if Babel fails to load, a text/babel script does nothing and the page stays blank, with no error to point at it.

The console will also show a warning that you are using the in-browser Babel transformer. That is expected. Babel's docs recommend precompiling scripts for production, which is the build step this single file skips on purpose.

React 19 has no script-tag build

Older tutorials load React from a /umd/ path. The React team has said that starting with React 19 it no longer produces those UMD builds.

For script-tag use they recommend an ESM-based CDN such as esm.sh. Their example looks like this, shown for reference and not run in this article:

<script type="module">
  import React from "https://esm.sh/react@19/?dev"
  import ReactDOMClient from "https://esm.sh/react-dom@19/client?dev"
  // ...
</script>

The examples on this page pin React 18.3.1 and load only from the unpkg.com and cdn.jsdelivr.net hosts, which NOS pages can load from.

Without Babel: React and htm

If you would rather not ship a compiler, you can skip JSX. The htm library lets you write JSX-like markup inside a template string. Its readme describes it as JSX-like syntax in plain JavaScript with no transpiler necessary.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>React without Babel</title>
<style>
  body { margin: 0; padding: 24px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .card { max-width: 340px; padding: 18px 20px; border-radius: 12px; background: #fff; box-shadow: 0 6px 20px rgba(0, 0, 0, .1); }
  h1 { margin: 0 0 10px; font-size: 20px; }
  input { box-sizing: border-box; width: 100%; font: inherit; padding: 8px 10px; border: 1px solid #cbd2dc; border-radius: 8px; }
  ul { margin: 12px 0 0; padding-left: 20px; min-height: 96px; }
  .none { color: #9a3412; list-style: none; margin-left: -20px; }
</style>
</head>
<body>
<div id="root"></div>

<script crossorigin src="https://unpkg.com/react@18.3.1/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"></script>
<!-- htm: JSX-like markup in a tagged template, no compile step -->
<script src="https://cdn.jsdelivr.net/npm/htm@3.1.1/dist/htm.umd.js"></script>

<script>
  const { useState } = React;
  const html = htm.bind(React.createElement);

  const FRUIT = ['apple', 'apricot', 'banana', 'blueberry', 'cherry', 'grape'];

  function Filter() {
    const [q, setQ] = useState('');
    const hits = FRUIT.filter((f) => f.includes(q.trim().toLowerCase()));
    return html`
      <div className="card">
        <h1>Filter the list</h1>
        <input value=${q} placeholder="type a, b, or ch" onInput=${(e) => setQ(e.target.value)} />
        <ul>
          ${hits.length
            ? hits.map((f) => html`<li key=${f}>${f}</li>`)
            : html`<li className="none">No match</li>`}
        </ul>
      </div>
    `;
  }

  ReactDOM.createRoot(document.getElementById('root')).render(html`<${Filter} />`);
</script>
</body>
</html>
React 18.3.1 plus htm from cdn.jsdelivr.net. No Babel, so the script is ordinary JavaScript.

The only new line is htm.bind(React.createElement). After it, html followed by a template string builds React elements. The demo uses className, the React name for the class attribute.

Where your script goes

JSFiddle's JavaScript panel has a Load type setting: run on load, run on DOM ready, or place the code unwrapped at the bottom of head or body. In a file the equivalent is placement. Put your script after the div it uses.

React's createRoot documentation explains why. A script cannot see elements that come after it in the HTML, so getElementById returns null and React reports that the target container is not a DOM element.

A finished example: split the bill

The same three tags scale to a real small tool. This one keeps three numbers in state and recomputes the result on every change. The inputs are handled inside the page, and nothing is submitted anywhere.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Split the bill</title>
<style>
  * { box-sizing: border-box; }
  body { margin: 0; padding: 20px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .card { max-width: 380px; margin: 0 auto; padding: 18px 20px 20px; border-radius: 12px; background: #fff; box-shadow: 0 6px 20px rgba(0, 0, 0, .1); }
  h1 { margin: 0 0 14px; font-size: 20px; }
  label { display: block; margin: 0 0 12px; font-size: 14px; color: #4b5563; }
  .lbl { font-size: 14px; color: #4b5563; }
  input[type=number] { display: block; width: 100%; margin-top: 4px; font: inherit; font-size: 16px; padding: 8px 10px; border: 1px solid #cbd2dc; border-radius: 8px; color: #1d2330; }
  .tips { display: flex; gap: 8px; margin: 4px 0 14px; }
  .tips button { flex: 1; font: inherit; padding: 8px 0; border: 1px solid #cbd2dc; border-radius: 8px; background: #fff; color: #1d2330; cursor: pointer; }
  .tips button[aria-pressed=true] { background: #2563eb; border-color: #2563eb; color: #fff; }
  .out { padding: 12px 14px; border-radius: 10px; background: #ecfdf3; color: #0f5132; }
  .out b { font-size: 24px; }
  .out small { display: block; margin-top: 2px; color: #3f6f57; }
</style>
</head>
<body>
<div id="root"></div>

<script crossorigin src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.26.4/babel.min.js"></script>

<script type="text/babel" data-presets="react">
  const { useState } = React;
  const TIPS = [0, 10, 15, 20];

  function App() {
    const [bill, setBill] = useState(84);
    const [tip, setTip] = useState(15);
    const [people, setPeople] = useState(3);

    const total = bill * (1 + tip / 100);
    const each = people > 0 ? total / people : 0;
    const money = (n) => n.toFixed(2);

    return (
      <div className="card">
        <h1>Split the bill</h1>
        <label>Bill
          <input type="number" min="0" value={bill} onChange={(e) => setBill(+e.target.value)} />
        </label>
        <div className="lbl">Tip</div>
        <div className="tips">
          {TIPS.map((t) => (
            <button key={t} aria-pressed={t === tip} onClick={() => setTip(t)}>{t}%</button>
          ))}
        </div>
        <label>People
          <input type="number" min="1" value={people} onChange={(e) => setPeople(+e.target.value)} />
        </label>
        <div className="out">
          <b id="each">{money(each)}</b> each
          <small id="total">Total with tip: {money(total)}</small>
        </div>
      </div>
    );
  }

  ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
State for bill, tip and people. Production builds of React. Change a number or tap a tip button.
  • State: useState holds each value, and the result is calculated while rendering, not stored.
  • Buttons: the selected tip uses aria-pressed, which both styles the button and marks it as pressed for assistive technology.
  • Guard: zero people shows 0.00 instead of dividing by zero.

When it does not work

Symptom Cause Fix
Blank page, Unexpected token '<' JSX inside a plain script Use type="text/babel" and load Babel first
Blank page, no error A text/babel script and Babel did not load Check the Babel URL in the Network tab
ReactDOM is not defined Your script runs before the libraries, or a URL failed Libraries first, then check each URL
Library never loads React 19 has no UMD files Pin 18.3.1, or use an ESM CDN
Error: Target container is not a DOM element The script sits above the div Move the script below the div
Error: Cannot use import statement outside a module import inside a text/babel script Add data-type="module", or use the globals
Console: Invalid DOM property class class written in JSX Write className
Babel warning in the console The in-browser transformer is in use Expected. Precompile for a real site

If a different kind of script problem shows up, JavaScript not working in an HTML file covers the general checks. To host a project that does have a build step, see hosting a built front end.

A fiddle's own link is made for showing code to other developers. A playground or a page compares the two. When the person opening it should see the working page, send the page.

Paste the whole file into a NOS document and choose Create share link. HTML to link walks through it. The page renders as written and its scripts run, so the people you send it to can click the counter themselves.

The scripts from unpkg.com and cdn.jsdelivr.net load, and anyone with the link can open it without an account. If you change the code later, the same link shows the new version.

Questions people ask

Can I run React in JSFiddle?

Yes. JSFiddle's panel settings list Babel and React among the languages of the JavaScript panel, and the editor can add libraries by URL. The same code also runs in a plain HTML file once you load React and Babel with script tags.

How do I turn a fiddle into one HTML file?

Put the HTML panel inside body, the CSS panel in a style element, the libraries in script tags, and the JavaScript panel in a script placed after them at the end of body. The Result panel is then the browser itself.

Why does React 19 not load from a script tag?

The React team announced that starting with React 19 it no longer produces UMD builds, the files older script-tag tutorials point to. For React 19 they recommend an ESM-based CDN. The examples here pin React 18.3.1, which still has UMD files.

Do I need Babel to use React in an HTML file?

Only if you write JSX. Without Babel you can call React.createElement directly, or use a small library such as htm that gives you JSX-like markup in a template string with no compile step.

Can I embed a fiddle in my page?

JSFiddle's docs describe an Embed option in the action menu that gives an iframe or script snippet. Pages shared from NOS block iframes, so send the page itself as a link instead of embedding another page inside it.

Keep reading