Chakra UI in a single HTML file

Chakra UI is a set of React components, and its docs start with npm and a bundler. One HTML file can still run it: an import map, a module script and a provider.

Chakra UI is a component library for React: buttons, inputs, cards, dialogs and a styling system you set through props such as p={6} or colorPalette="teal".

Its project page describes it as "accessible React components for building high-quality web apps and design systems", and it is MIT licensed.

The official setup uses npm and a project. If what you want is one working HTML file, the example below is the whole page. 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>Chakra UI in one HTML file</title>
<!-- Map package names to pinned jsDelivr ESM bundles -->
<script type="importmap">
{
  "imports": {
    "react": "https://cdn.jsdelivr.net/npm/react@19.2.8/+esm",
    "react-dom/client": "https://cdn.jsdelivr.net/npm/react-dom@19.2.8/client/+esm",
    "@chakra-ui/react": "https://cdn.jsdelivr.net/npm/@chakra-ui/react@3.37.0/+esm",
    "https://cdn.jsdelivr.net/npm/react@19.0.0/+esm": "https://cdn.jsdelivr.net/npm/react@19.2.8/+esm"
  }
}
</script>
</head>
<body>
<div id="root"><p style="font-family: system-ui, sans-serif; padding: 24px; margin: 0;">Loading Chakra UI...</p></div>

<script type="module">
  import { createElement as h, useState } from 'react';
  import { createRoot } from 'react-dom/client';
  import { ChakraProvider, defaultSystem, Box, Heading, Text, Button } from '@chakra-ui/react';

  function App() {
    const [count, setCount] = useState(0);
    // h(Component, props, ...children) is what JSX compiles to
    return h(Box, { p: 6, maxW: 'sm' },
      h(Heading, { size: 'xl', mb: 2 }, 'Hello from Chakra UI'),
      h(Text, { color: 'fg.muted', mb: 4 }, 'One HTML file, no build step.'),
      h(Button, { colorPalette: 'teal', onClick: () => setCount(count + 1) }, 'Clicked ' + count + ' times')
    );
  }

  // Every Chakra component must sit inside ChakraProvider
  createRoot(document.getElementById('root')).render(
    h(ChakraProvider, { value: defaultSystem }, h(App))
  );
</script>
</body>
</html>
Chakra UI 3.37.0 and React 19.2.8 loaded from jsDelivr. Edit the code and the example reruns.

The first load fetches the library as many small module files from cdn.jsdelivr.net, so it shows "Loading Chakra UI..." for a moment. Later loads come from the browser cache.

How the one-file page is built

There is no build step, so the page does four jobs a bundler would normally do for you.

The four parts of a single-file Chakra page, from the head down.
The four parts of a single-file Chakra page, from the head down.
  1. Import map. A <script type="importmap"> block maps the names react, react-dom/client and @chakra-ui/react to pinned files on cdn.jsdelivr.net. It must come before the module script.
  2. Module script. The code goes in <script type="module">. A plain <script> stops with "Cannot use import statement outside a module".
  3. ChakraProvider. Every Chakra component reads its theme from ChakraProvider. With no custom theme, pass value: defaultSystem.
  4. Components. Each JSX tag becomes a createElement call, imported as h to keep it short.

The import map, line by line

These are the four entries the examples use:

<script type="importmap">
{
  "imports": {
    "react": "https://cdn.jsdelivr.net/npm/react@19.2.8/+esm",
    "react-dom/client": "https://cdn.jsdelivr.net/npm/react-dom@19.2.8/client/+esm",
    "@chakra-ui/react": "https://cdn.jsdelivr.net/npm/@chakra-ui/react@3.37.0/+esm",
    "https://cdn.jsdelivr.net/npm/react@19.0.0/+esm": "https://cdn.jsdelivr.net/npm/react@19.2.8/+esm"
  }
}
</script>

The /+esm ending asks jsDelivr for a browser-ready module. Inside that file, the dependencies are already written as full jsDelivr addresses with exact versions, such as react@19.2.8.

The last line looks odd, and it matters most. The Emotion bundle that Chakra styles through imports react@19.0.0, while Chakra and React DOM import react@19.2.8. Import map keys can be full URLs, so this line sends the stray version to the same file.

Two copies of React leave the page blank. One remap line fixes it.
Two copies of React leave the page blank. One remap line fixes it.

Without that line, the page stays blank and the console shows "Cannot read properties of null (reading 'useContext')". If you change any version number, open the bundle URLs and check their import lines again.

JSX without a compiler

Chakra's docs show JSX, such as <Button colorPalette="teal">. Browsers cannot read JSX, and React documents createElement as the alternative. The translation is mechanical:

// JSX:  <Button colorPalette="teal" onClick={add}>Add</Button>
h(Button, { colorPalette: 'teal', onClick: add }, 'Add')

// JSX:  <Card.Root><Card.Body>Hi</Card.Body></Card.Root>
h(Card.Root, null, h(Card.Body, null, 'Hi'))

Props stay the same, including Chakra's style props such as p, mb, maxW and bg. Components with parts, like Card.Root and Field.Label, are passed the same way.

Variants, colour palettes and dark mode

Buttons take a variant prop for the style and a colorPalette prop for the colour. Setting colorPalette on a parent such as HStack passes it to the buttons inside. Pick a palette, then switch to dark mode:

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>Chakra UI variants, palettes and dark mode</title>
<script type="importmap">
{
  "imports": {
    "react": "https://cdn.jsdelivr.net/npm/react@19.2.8/+esm",
    "react-dom/client": "https://cdn.jsdelivr.net/npm/react-dom@19.2.8/client/+esm",
    "@chakra-ui/react": "https://cdn.jsdelivr.net/npm/@chakra-ui/react@3.37.0/+esm",
    "https://cdn.jsdelivr.net/npm/react@19.0.0/+esm": "https://cdn.jsdelivr.net/npm/react@19.2.8/+esm"
  }
}
</script>
</head>
<body>
<div id="root"><p style="font-family: system-ui, sans-serif; padding: 24px; margin: 0;">Loading Chakra UI...</p></div>

<script type="module">
  import { createElement as h, useState } from 'react';
  import { createRoot } from 'react-dom/client';
  import { ChakraProvider, defaultSystem, Box, Stack, HStack, Button, Text } from '@chakra-ui/react';

  const palettes = ['teal', 'blue', 'purple', 'red'];
  const variants = ['solid', 'subtle', 'outline', 'ghost'];

  function App() {
    const [palette, setPalette] = useState('teal');
    const [dark, setDark] = useState(false);

    function toggleDark() {
      // Chakra's dark styles apply under a .dark class on a parent
      document.documentElement.classList.toggle('dark', !dark);
      setDark(!dark);
    }

    return h(Box, { p: 5, bg: 'bg', color: 'fg', minH: '100vh' },
      h(Stack, { gap: 4 },
        h(Text, { fontWeight: 'semibold' }, 'colorPalette'),
        h(HStack, { wrap: 'wrap' }, palettes.map((p) =>
          h(Button, { key: p, size: 'sm', variant: p === palette ? 'solid' : 'outline',
            onClick: () => setPalette(p) }, p))),
        h(Text, { fontWeight: 'semibold' }, 'variant'),
        // colorPalette on a parent is inherited by the buttons inside
        h(HStack, { wrap: 'wrap', colorPalette: palette }, variants.map((v) =>
          h(Button, { key: v, variant: v }, v))),
        h(Button, { alignSelf: 'start', variant: 'surface', onClick: toggleDark },
          dark ? 'Light mode' : 'Dark mode')
      )
    );
  }

  createRoot(document.getElementById('root')).render(
    h(ChakraProvider, { value: defaultSystem }, h(App))
  );
</script>
</body>
</html>
colorPalette and variant on Button, and a dark mode switch that toggles a class on the html element.

In a project, the Chakra docs add dark mode through a Provider snippet built on the next-themes package. Chakra's dark styles apply under a .dark class on a parent element, so a single file can toggle that class on <html> itself:

document.documentElement.classList.toggle('dark');

The colour tokens in the example, such as bg, fg and fg.muted, switch with it. Fixed colours like white do not. For dark mode without a library, see dark mode in CSS.

A finished example: a sign-up card

A form uses Card, Field and Input. Press Sign up with the fields empty, then fill them in.

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>Chakra UI sign-up card</title>
<script type="importmap">
{
  "imports": {
    "react": "https://cdn.jsdelivr.net/npm/react@19.2.8/+esm",
    "react-dom/client": "https://cdn.jsdelivr.net/npm/react-dom@19.2.8/client/+esm",
    "@chakra-ui/react": "https://cdn.jsdelivr.net/npm/@chakra-ui/react@3.37.0/+esm",
    "https://cdn.jsdelivr.net/npm/react@19.0.0/+esm": "https://cdn.jsdelivr.net/npm/react@19.2.8/+esm"
  }
}
</script>
</head>
<body>
<div id="root"><p style="font-family: system-ui, sans-serif; padding: 24px; margin: 0;">Loading Chakra UI...</p></div>

<script type="module">
  import { createElement as h, useState } from 'react';
  import { createRoot } from 'react-dom/client';
  import { ChakraProvider, defaultSystem, Box, Card, Field, Input, Button, Stack, Text } from '@chakra-ui/react';

  function App() {
    const [errors, setErrors] = useState({});
    const [sent, setSent] = useState(null);

    function onSubmit(e) {
      e.preventDefault();  // handle it in the page, send nothing
      const data = Object.fromEntries(new FormData(e.target));
      const next = {};
      if (!data.name.trim()) next.name = 'Enter your name.';
      if (!/^\S+@\S+\.\S+$/.test(data.email)) next.email = 'Enter a valid email.';
      setErrors(next);
      setSent(Object.keys(next).length ? null : data);
    }

    // Field.Root gets invalid; Field.ErrorText only shows when it is set
    const field = (name, label, type) =>
      h(Field.Root, { invalid: !!errors[name] },
        h(Field.Label, null, label),
        h(Input, { name, type, placeholder: label }),
        h(Field.ErrorText, null, errors[name]));

    return h(Box, { p: 4, bg: 'bg.muted', minH: '100vh' },
      h(Card.Root, { maxW: 'sm', mx: 'auto' },
        h(Card.Header, null,
          h(Card.Title, null, 'Join the beta'),
          h(Card.Description, null, 'Two fields, checked in the page.')),
        h(Card.Body, null,
          h('form', { id: 'signup', onSubmit, noValidate: true },
            h(Stack, { gap: 4 }, field('name', 'Name', 'text'), field('email', 'Email', 'email')))),
        h(Card.Footer, { flexDirection: 'column', alignItems: 'stretch', gap: 3 },
          h(Button, { type: 'submit', form: 'signup', colorPalette: 'teal' }, 'Sign up'),
          sent && h(Text, { id: 'result', color: 'green.fg', fontSize: 'sm' },
            'Thanks, ' + sent.name + '. We would write to ' + sent.email + '.'))
      )
    );
  }

  createRoot(document.getElementById('root')).render(
    h(ChakraProvider, { value: defaultSystem }, h(App))
  );
</script>
</body>
</html>
Field.Root gets invalid when a check fails, which shows Field.ErrorText. The submit is handled in the page.
  • Errors: pass invalid to Field.Root. Field.ErrorText only appears while it is set.
  • Reading values: the submit handler calls preventDefault() and reads the inputs with FormData, covered in FormData in JavaScript.
  • Sending: nothing leaves the page. A real sign-up would post to your server, shown below but not run here.
await fetch('https://api.example.com/signup', {
  method: 'POST',
  body: new FormData(form),
});

When you need the npm project instead

The single file is for a page, a prototype or a demo you want to send. For a full app, the Chakra docs set up a project with a bundler.

The npm route the Chakra docs use, next to what runs in one HTML file.
The npm route the Chakra docs use, next to what runs in one HTML file.
npm i @chakra-ui/react @emotion/react
npx @chakra-ui/cli snippet add

The docs list Node.js 20 as the minimum, with guides for Vite, Next.js and other frameworks.

The build produces static files, and hosting a built front end covers where they can go. If you only want ready-made styling without React, a Tailwind HTML template needs one script tag.

One HTML file npm project
Install Nothing Node.js and npm
Writing components h(Button, props) JSX
Provider ChakraProvider + defaultSystem The Provider snippet
Dark mode Toggle the dark class Snippet using next-themes
Result The file itself A built folder to host

When it does not work

What you see Cause Fix
"Cannot use import statement outside a module" Plain <script> tag Use type="module"
"Failed to resolve module specifier" Import map missing, after the module script, or not valid JSON Put it first and check commas
Blank page, "reading 'useContext'" Two React versions loaded Remap react@19.0.0 in the import map
"forgot to wrap component within ChakraProvider" No provider Render inside ChakraProvider
"Unexpected token '<'" JSX in the browser Use h(...)
Dark mode leaves some parts light Fixed colours instead of tokens Use bg and fg tokens

A trailing comma after the last import map entry is enough to make it invalid JSON. For other blank-page causes, see HTML JavaScript not working.

A Chakra page is easier to show than to describe. A screenshot cannot be clicked, and an .html attachment may open as plain code on a phone.

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, its scripts run and the library loads from cdn.jsdelivr.net, so anyone with the link can press the buttons and fill in the form without an account.

If you change the code later, the same link shows the new version.

Questions people ask

Can I use Chakra UI without React?

No. Chakra UI is a React component library, and react, react-dom and @emotion/react are its peer dependencies. In a single HTML file, React is loaded from the CDN next to Chakra, so you still write React components, only without a build step.

Is there a Chakra UI script tag like Bootstrap has?

The Chakra docs do not offer one; they install with npm. In one HTML file, a module script imports the package from jsDelivr's ESM bundles, with an import map that points the package names at pinned versions.

Can I write JSX in the HTML file?

Not directly. Browsers do not understand JSX, so it needs a compile step. React's createElement is the documented alternative: h(Button, props, children) produces the same element as the JSX tag.

Why is my Chakra page blank with an error about useContext?

Two messages are common. "forgot to wrap component within ChakraProvider" means the provider is missing. "Cannot read properties of null (reading useContext)" means two copies of React loaded; remap the second version in the import map.

Is Chakra UI free?

Yes. The repository lists the MIT licence, and the package on npm is published under MIT.

Keep reading