CodeSandbox HTML: put the whole project in one file

A CodeSandbox HTML project is a page split across several files. Fold the CSS and JavaScript into one .html file and it opens anywhere, with nothing to install.

CodeSandbox is an online code editor, and this guide uses the web-platform meaning: an HTML page you built in it.

A sandbox keeps that page in separate files. To get one working file, put the CSS in a <style> tag, put the JavaScript in a <script> at the end of the body, and load libraries from a CDN by URL.

Try the smallest version first. Type a name and press 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>One file, three jobs</title>
<style>
  /* 1. CSS lives in the head, not in styles.css */
  body {
    margin: 0; padding: 24px; min-height: 100vh; box-sizing: border-box;
    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);
  }
  input { width: 100%; box-sizing: border-box; padding: 10px; font-size: 16px; border: 1px solid #c9ced8; border-radius: 8px; }
  button { margin-top: 10px; padding: 10px 14px; font-size: 16px; border: 0; border-radius: 8px; background: #2563eb; color: #fff; }
  #out { margin: 14px 0 0; font-size: 18px; min-height: 1.4em; }
</style>
</head>
<body>
<!-- 2. The markup is the page itself -->
<div class="card">
  <label for="name"><b>Your name</b></label>
  <input id="name" placeholder="Type here">
  <button id="go" type="button">Say hello</button>
  <p id="out" aria-live="polite"></p>
</div>

<!-- 3. The JavaScript sits at the end, so the elements above already exist -->
<script>
  const nameInput = document.getElementById('name');
  const out = document.getElementById('out');
  let clicks = 0;

  document.getElementById('go').addEventListener('click', () => {
    clicks++;
    out.textContent = 'Hello, ' + (nameInput.value.trim() || 'stranger') + '! (click ' + clicks + ')';
  });
</script>
</body>
</html>
One file with CSS in the head, markup in the body and JavaScript at the end. Edit the code and the example reruns.

Everything the page needs is inside it, so you can copy it into any editor, any chat or any document and it behaves the same.

What a CodeSandbox HTML project looks like

CodeSandbox offers a static template for plain HTML, CSS and JavaScript. The template's repository holds an index.html, a styles.css and a package.json, and its index.html pulls the stylesheet in with a link tag.

Sandpack, the CodeSandbox component for code editors on other websites, lists static among its templates too.

So a project is several files that find each other by name. That works inside the editor, because the files sit side by side. It stops working when you copy only one of them somewhere else.

A sandbox project spread over three files, and the same page folded into one HTML file.
A sandbox project spread over three files, and the same page folded into one HTML file.

The smallest single file

The example above is the whole recipe, in three parts:

  1. CSS in the head. A <style> element holds the rules that used to live in styles.css.
  2. Markup in the body. The visible page, written once.
  3. JavaScript last. A classic script runs as soon as the browser parses it, so a script at the end of the body can find every element above it.

Add <meta name="viewport" content="width=device-width, initial-scale=1"> to the head so the page is sized correctly on a phone. The viewport meta tag guide explains why.

Linked CSS is the first thing to break

A link tag with href="styles.css" is a relative address: the browser looks for a file of that name next to the page.

When you paste the page into a chat or a document, nothing is next to it, so the request fails and the page loses its styling.

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>Linked file or inline style</title>
<style>
  body { margin: 0; padding: 20px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
  button { padding: 10px 14px; font-size: 15px; border: 1px solid #c9ced8; border-radius: 8px; background: #fff; }
  #target { padding: 18px; border-radius: 12px; background: #fff; border: 2px dashed #c9ced8; }
  #log { margin-top: 14px; padding: 10px 12px; border-radius: 8px; background: #fff; font: 14px/1.5 ui-monospace, Consolas, monospace; min-height: 3em; }
  .ok { color: #0f5132; } .bad { color: #9a3412; }
</style>
</head>
<body>
<div class="row">
  <button id="linked" type="button">Add &lt;link href="styles.css"&gt;</button>
  <button id="inline" type="button">Add an inline &lt;style&gt;</button>
</div>

<div id="target"><b>Styled box</b><br>It turns green with a solid border when its CSS arrives.</div>
<div id="log">Press a button.</div>

<script>
  const log = document.getElementById('log');
  const say = (text, cls) => { log.innerHTML = '<span class="' + cls + '">' + text + '</span>'; };

  // A linked file only works if a file with that name sits next to the page
  document.getElementById('linked').addEventListener('click', () => {
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = 'styles.css';
    link.onload = () => say('styles.css loaded', 'ok');
    link.onerror = () => say('styles.css was not found. Nothing was styled.', 'bad');
    document.head.appendChild(link);
  });

  // Inline CSS travels inside the page, so it cannot go missing
  document.getElementById('inline').addEventListener('click', () => {
    const style = document.createElement('style');
    style.textContent = '#target { background: #e8f6ee; border: 2px solid #1f9d55; }';
    document.head.appendChild(style);
    say('Inline style applied', 'ok');
  });
</script>
</body>
</html>
The left button adds a link to a stylesheet that is not there. The right button adds the same rules inline.

A link tag reports the failure through its error event, as the demo does. Inline CSS cannot go missing because it is part of the page. Inline CSS covers the rest of that choice.

Imports and libraries

Project code often starts with import confetti from 'confetti'. In a browser, a bare name like confetti means nothing until you define it. An import map does that: a small JSON block that maps the name to a URL.

A bare import fails on its own. With an import map that names the URL, the same line works.
A bare import fails on its own. With an import map that names the URL, the same line works.

Three rules from MDN keep it working:

  • The import map goes before any module script that uses it.
  • The code that imports runs in a <script type="module">. A regular script that uses import throws an error.
  • Module scripts are deferred automatically, so they run after the page is parsed.
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>A package from a CDN, no install</title>
<style>
  body { margin: 0; padding: 24px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  button { padding: 12px 18px; font-size: 17px; border: 0; border-radius: 10px; background: #2563eb; color: #fff; }
  button:disabled { background: #9aa3b2; }
  #status { margin-top: 14px; font-size: 15px; }
</style>

<!-- The import map must come before the module script that uses it -->
<script type="importmap">
{ "imports": { "confetti": "https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.module.mjs" } }
</script>
</head>
<body>
<button id="boom" type="button" disabled>Loading...</button>
<p id="status">Fetching the package from cdn.jsdelivr.net</p>

<script type="module">
  const button = document.getElementById('boom');
  const status = document.getElementById('status');
  try {
    // Same line you would write in a CodeSandbox project: import confetti from 'confetti'
    const { default: confetti } = await import('confetti');
    button.disabled = false;
    button.textContent = 'Fire confetti';
    status.textContent = 'Loaded. The version is pinned in the URL (1.9.3).';
    button.addEventListener('click', () => confetti({ particleCount: 90, spread: 70, origin: { y: 0.6 } }));
  } catch (err) {
    button.textContent = 'Could not load';
    status.textContent = 'The CDN did not answer: ' + err.message;
  }
</script>
</body>
</html>
An import map points the name confetti at a pinned file on cdn.jsdelivr.net. Press the button once it says Fire confetti.

Put the exact version in the URL, as the demo does with @1.9.3. jsDelivr, the CDN used here, warns that asking for the latest version is dangerous because major versions usually come with breaking changes.

One more caution from MDN: if a module script loads another local file from a file:// page, the browser raises CORS errors. An inline module script that imports from a CDN, like the demo, avoids that.

When the project needs a build step

Some sandboxes are not plain HTML at all. A React project, for example, is written in JSX, which React's documentation calls a syntax extension for JavaScript that is transformed into plain JavaScript objects.

Its documentation also recommends a build tool to package and run the source.

What runs as written in an HTML file, and what needs a build step first.
What runs as written in an HTML file, and what needs a build step first.

A plain .html file has no build tool, so this does not run in one:

// src/index.jsx in a build-based project
import { createRoot } from 'react-dom/client';
createRoot(document.getElementById('root')).render(<h1>Hello</h1>);

Either build the project and paste the output, as Host a React app as a page describes, or write the same page in plain JavaScript:

document.getElementById('root').textContent = 'Hello';

Embeds are iframes

CodeSandbox documents an embed as HTML with an iframe, which you copy from the Share menu in the editor. Embedding suits a blog that wants a live editor inside an article.

A NOS page blocks iframes, outside images and fetch calls to other sites. So do not embed a sandbox inside a page you plan to share there. Paste the page's own HTML instead, as described in Single HTML file apps.

When it does not work

What you see Cause Fix
The page has no styling Link to styles.css, file not with the page Move the CSS into a <style> tag
Script error: element is null Script ran before the element existed Put the script at the end of the body
Error on an import line A regular script, or a bare name with no map Use type="module" and an import map
Import map ignored The map comes after the module script Place the map first
Module fails on a file:// page A local module file needs a server Inline the code, or import from a CDN
.jsx or .tsx code shows as text or errors It needs a build step Build first, or rewrite in plain JavaScript
Library changed behaviour overnight The URL has no version Pin an exact version

A single file is easiest to judge when it runs. A screenshot cannot be clicked, and one file is easier to send than a folder of files.

To send the working version, paste the page 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 press the buttons themselves, with no account.

Scripts and styles from cdnjs.cloudflare.com, cdn.jsdelivr.net, cdn.tailwindcss.com, code.jquery.com and unpkg.com load. If you change the code later, the same link shows the new version.

Questions people ask

What does "CodeSandbox HTML" mean here?

CodeSandbox is an online code editor, and "CodeSandbox HTML" means an ordinary HTML page built in it. It is not the HTML sandbox attribute, which restricts what an iframe may do. This guide is about the page: how to get it into one file that runs by itself.

Why is my page unstyled after I copy index.html out of a sandbox?

The page links to a separate stylesheet by file name, and that file did not come with it. Copy the CSS into a style tag inside the head, and the styling travels with the page.

My script says an element is null. What is wrong?

The script ran before the browser reached the element. A classic script runs the moment it is parsed. Move the script tag to the end of the body, or wait for the DOMContentLoaded event.

Can I use a library in a single HTML file?

Yes, if it is published as a file you can load by URL. Add a script tag that points at a CDN, or use an import map with a module script. Put an exact version number in the URL so a later release cannot change your page.

Can I put a React project from a sandbox into one file?

Not as it is. A React project is written in JSX and uses a build tool, and a plain HTML file has no build step. Build the project first and paste the output, or rewrite the page in plain JavaScript.

Keep reading