src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import './styles.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
Online Headless UI playground with React, TypeScript, accessible unstyled components, editable project files, and instant live preview.
Choose a source file from the explorer and edit it with Monaco.
Edit package.json to add browser-compatible npm packages or change versions. Preview packages load from esm.sh. Successful previews save resolved versions for ranges and tags with your draft and shares; change the range to resolve again. Scripts and build plugins run only in downloaded projects.
Auto-run updates the browser preview as you type. Turn it off to run your changes manually.
Use the console for browser logs and errors, or evaluate a command against the preview.
Share creates a snapshot link. Download exports the source so you can continue locally.
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import './styles.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
import { useState } from 'react'
import {
Dialog,
DialogBackdrop,
DialogPanel,
DialogTitle,
Listbox,
ListboxButton,
ListboxOption,
ListboxOptions,
Menu,
MenuButton,
MenuItem,
MenuItems,
Switch,
Tab,
TabGroup,
TabList,
TabPanel,
TabPanels,
} from '@headlessui/react'
import { ActivityRow } from './components/ActivityRow'
import { activity, teams } from './data'
export default function App() {
const [team, setTeam] = useState(teams[0])
const [updates, setUpdates] = useState(true)
const [dialogOpen, setDialogOpen] = useState(false)
const [saved, setSaved] = useState(false)
const savePreferences = () => {
setSaved(true)
setDialogOpen(false)
window.setTimeout(() => setSaved(false), 1800)
}
return (
<main className="app-shell">
<section className="workspace" aria-labelledby="page-title">
<header className="topbar">
<div className="brand-mark" aria-hidden="true"><span /></div>
<div className="heading-copy">
<p className="eyebrow">HEADLESS UI · REACT 19</p>
<h1 id="page-title">Workspace pulse</h1>
<p>Accessible interactions, styled for a focused product team.</p>
</div>
<Menu as="div" className="profile-menu">
<MenuButton className="avatar-button" aria-label="Open account menu">DM</MenuButton>
<MenuItems anchor="bottom end" className="menu-items">
<div className="menu-heading">Danielle · Admin</div>
<MenuItem><button type="button">Account settings</button></MenuItem>
<MenuItem><button type="button">Keyboard shortcuts</button></MenuItem>
<div className="menu-separator" />
<MenuItem><button type="button">Sign out</button></MenuItem>
</MenuItems>
</Menu>
</header>
<div className="control-row">
<div>
<span className="control-label">Active team</span>
<Listbox value={team} onChange={setTeam}>
<ListboxButton className="listbox-button">
<span className="team-avatar" style={{ background: team.accent }}>{team.initials}</span>
<span>{team.name}</span>
<span className="chevron" aria-hidden="true">⌄</span>
</ListboxButton>
<ListboxOptions anchor="bottom start" className="listbox-options">
{teams.map((option) => (
<ListboxOption key={option.id} value={option} className="listbox-option">
<span className="team-avatar" style={{ background: option.accent }}>{option.initials}</span>
<span className="option-copy">
<strong>{option.name}</strong>
<small>{option.members} members</small>
</span>
<span className="option-check" aria-hidden="true">✓</span>
</ListboxOption>
))}
</ListboxOptions>
</Listbox>
</div>
<span className="status-pill"><i /> Systems ready</span>
</div>
<TabGroup className="dashboard">
<TabList className="tab-list" aria-label="Workspace sections">
<Tab className="tab">Overview</Tab>
<Tab className="tab">Activity</Tab>
</TabList>
<TabPanels>
<TabPanel className="tab-panel">
<div className="metric-grid">
<article className="metric-card"><span>Accessibility</span><strong>98</strong><small>+4 this release</small></article>
<article className="metric-card"><span>Components</span><strong>24</strong><small>Across 8 flows</small></article>
<article className="metric-card"><span>Open reviews</span><strong>3</strong><small>2 due today</small></article>
</div>
<div className="settings-card">
<div>
<h2>Release updates</h2>
<p>Get notified when accessibility reviews are complete.</p>
</div>
<Switch checked={updates} onChange={setUpdates} className="switch" aria-label="Release updates">
<span className="switch-thumb" />
</Switch>
</div>
</TabPanel>
<TabPanel className="tab-panel">
<div className="activity-list">
{activity.map((item) => <ActivityRow key={item.title} {...item} />)}
</div>
</TabPanel>
</TabPanels>
</TabGroup>
<div className="footer-actions">
<p aria-live="polite">{saved ? 'Preferences saved.' : ''}</p>
<button className="primary-button" type="button" onClick={() => setDialogOpen(true)}>
Review preferences
</button>
</div>
<Dialog open={dialogOpen} onClose={setDialogOpen} className="dialog-root">
<DialogBackdrop className="dialog-backdrop" />
<div className="dialog-container">
<DialogPanel className="dialog-panel">
<div className="dialog-icon" aria-hidden="true">✦</div>
<DialogTitle>Release preferences</DialogTitle>
<p className="dialog-description">
Choose whether {team.name} should receive accessibility review updates.
</p>
<label className="dialog-setting">
<span><strong>Release updates</strong><small>{updates ? 'Currently enabled' : 'Currently disabled'}</small></span>
<Switch checked={updates} onChange={setUpdates} className="switch" aria-label="Dialog release updates">
<span className="switch-thumb" />
</Switch>
</label>
<div className="dialog-actions">
<button className="secondary-button" type="button" onClick={() => setDialogOpen(false)}>Cancel</button>
<button className="primary-button" type="button" onClick={savePreferences}>Save preferences</button>
</div>
</DialogPanel>
</div>
</Dialog>
</section>
</main>
)
}
The editor uses Monaco, the engine behind VS Code, with language-aware editing tools and familiar keyboard controls.
Edit a source file from the project explorer — the live preview recompiles automatically.
Add, rename, duplicate or delete project files as needed. Update affected imports after renaming or moving a file.
Use the console for logs and errors, and Format to tidy the active file.
Share the complete project or download a Vite project ZIP with its source files and package.json.
Headless UI 2.2
React 19 + TypeScript
Accessible Components
Keyboard Navigation
Focus Management
Portalled Dialogs and Menus
Local Component Imports
Live Preview
Vite Project Export
Auto-run updates the preview as you type. Turn it off to run manually with Run or Ctrl/⌘ + Enter.
The Console prompt can inspect browser globals and the rendered DOM, but not variables scoped inside ES modules or component state.
Console shortcuts apply when its prompt is focused. Editor shortcuts apply inside the code editor.
Code runs in a sandboxed browser preview, without Node.js, a backend, or private environment variables.
Download the project when you need build plugins, server APIs, native modules, or a production dependency workflow.