A browser-based canvas for rendering React components live.
react-code-canvas is useful when you need to preview JSX from strings and for artifacts.
bun add react-code-canvas@latestnpm install react-code-canvas@latestyarn add react-code-canvas@latestimport{ReactCanvas}from"react-code-canvas";<ReactCanvascode={CODE}showPreview={true}showEditor={false}showError={true}scope={SCOPE}/>;You can use a Tailwind CDN script in the host app if the rendered code depends on Tailwind utility classes.
| Prop | Type | Description |
|---|---|---|
code | string | React functional component code, usually export default function..., without import statements. |
showPreview | boolean | Displays the rendered output. |
showEditor | boolean | Shows the code editor. |
showError | boolean | Shows an error toast in the top-right of the viewport for runtime or compile-time errors. |
errorComponent | React.ReactNode | ((error: string, dismiss: () => void) => React.ReactNode) | Replaces the built-in toast. Receives the message and a dismiss callback. Requires showError. |
dismissibleError | boolean | Shows a close button on the built-in toast. Defaults to true. |
scope | Record<string, any> | Components, values, and libraries available to the rendered code. |
persistKey | string | Optional localStorage key. When set, edited code is saved on change and restored on reload. |
onCodeChange | (code: string) => void | Called whenever the code changes in the editor. Use it to persist code yourself (URL, backend, etc.). |
showLoader | boolean | Shows a full-screen spinner overlay while the code is being compiled and evaluated. Defaults to !showEditor. Enabling it defers the initial evaluation by one macrotask so the spinner can actually paint; later edits stay synchronous. |
loader | React.ReactNode | Replaces the default spinner with your own node. |
onError | (error: string) => void | Called when non-empty code evaluates without throwing but never renders anything. |
Static analysis, no DOM and no React render — safe to run in Node (CI, batch audits, migrations):
import{analyzeReactCode}from"react-code-canvas";constresult=awaitanalyzeReactCode(CODE);// {// valid: boolean,// issues: [{ type, message, name?, line?, column? }],// unknownGlobals: string[], // names that will throw ReferenceError// referencedGlobals: string[],// hasDefaultExport: boolean,// }It parses, transpiles with sucrase, and compiles with new Function (constructed,
never called), then checks every referenced name against the scope. It does not
execute the code, so it cannot catch logic errors or failures that only surface once
mounted — render it with ReactCanvas and its onError for that.
| Option | Type | Description |
|---|---|---|
scope | Record<string, unknown> | Omit to check against exactly what the canvas would load for this code. Passing one replaces that entirely. |
allowedGlobals | string[] | Extra host-injected names to treat as available. |
forbidImports | boolean | Report import statements. Default true — the canvas strips them, so the bindings are undefined at runtime. |
import{EditTextReactCanvas}from"react-code-canvas";<EditTextReactCanvascode={CODE}scope={SCOPE}showPreview={true}showEditor={false}showError={true}onSaveFinalCode={handleSave}onError={handleError}/>;EditTextReactCanvas adds text editing behavior to rendered React code (beta).
Turn a code string into a deployable static site — index.html plus hashed assets/, with React,
every library the code references and compiled Tailwind bundled in. No CDN, no network at runtime.
// Node only. Ships as a separate entry so nothing here reaches your browser bundle.import{writeStandaloneSite}from"react-code-canvas/builder";awaitwriteStandaloneSite(CODE,"./out");// -> out/index.html, out/assets/app-<hash>.js, out/assets/style-<hash>.cssThen deploy out/ anywhere that serves files. Use buildStandaloneSite instead to get a
path -> contents map and handle delivery yourself.
Requires the optional peers it actually uses: npm i -D esbuild, plus tailwindcss if your code
uses className. Neither is loaded otherwise.
| Option | Type | Description |
|---|---|---|
title | string | <title> for code that emits none of its own. A <Helmet><title> in the code wins. |
lang | string | <html lang>. Default 'en'. |
tailwind | boolean | Compile Tailwind for the classes found in the code. Default true; only runs when the code contains className. |
css | string | Extra CSS, appended after the compiled Tailwind. |
head | string | Raw HTML appended to <head> — fonts, favicon, OG tags, analytics. |
prerender | boolean | Bake the initial markup into the HTML for SEO, then hydrate. Default true. |
minify | boolean | Default true. |
target | string | esbuild target. Default 'es2020'. |
Notes:
- Styling all works: inline
styleobjects, in-component<style>tags, and Tailwind classes are compiled into a real stylesheet. Unlike the canvas, the built site does not need the host to provide Tailwind. - Prerendering is best-effort. Code that touches
documentorwindowduring render throws in Node; the build warns and emits a client-only shell instead of failing. Guard withtypeof document !== "undefined"to keep the prerender.useEffectnever runs during prerender, so it is always safe. <Helmet>finally does something here — React 19 hoists its<title>/<meta>and the builder lifts them into<head>, where crawlers read them.- Machine-generated
<EditableText>tags from anEditTextReactCanvassession are reversed back to plain JSX; a deployed page does not keep click-to-edit fields. - recharts charts do not prerender — measured: they render nothing server-side, fixed size or not, and appear on hydration. The rest of the page still prerenders normally, so only the chart area is blank in the initial HTML.
- Code should export a default component, for example
export default function ComponentName() {}. - Do not include import statements in rendered code. Inject dependencies through the
scopeprop. - The default scope ships React, its hooks and
react-helmet-asyncup front, and lazily fetcheslucide-react(~0.8KB gz per icon),recharts(~145KB gz),motion/react(~61KB gz) andreact-icons/fa(~420KB gz) only when the code mentions a name from that group. Prefer lucide icons — oneFa*reference pulls the entire Font Awesome pack, which has no per-icon files. See Scope. - Empty or whitespace-only
coderenders nothing at all — no output and no loader. - The built-in error toast keeps the message in
#react-code-error. If you replace it witherrorComponentand rely on scraping that element (for example from a headless browser), keep the id on a statically positioned element —offsetParentisnullonposition: fixedelements. errorComponenttakes an element or a render function, not a component type. Passing the component itself (errorComponent={MyToast}) calls it with the message string in place of props, so it renders blank with no warning. UseerrorComponent={(message, dismiss) => <MyToast message={message} onDismiss={dismiss} />}— the second argument wires your own close control to the same per-message dismissal the built-in toast uses.- When an edit fails to compile,
ReactCanvaskeeps the previous successful render on screen and reports the error. Doing so re-executes the previous code, so any top-level side effects in it (analytics calls, script injection, DOM mutation) run again. Guard side effects if that matters. - This package evaluates provided code in the browser. Do not execute untrusted code without an additional isolation strategy appropriate for your application.
This repository uses Bun for dependency management and CI.
bun install --frozen-lockfile
bun run test
bun run buildRun Storybook locally:
bun run devOther useful commands:
bun run build-storybook
bun run sizeThe committed lockfile is bun.lock. Do not commit package-lock.json.
The package documentation lives in doc_site and is deployed to GitHub Pages from the master branch.
bun run docs:dev
bun run docs:buildContributions are welcome. Read CONTRIBUTING.md before opening an issue or pull request.
All participants are expected to follow the Code of Conduct.
See CHANGELOG.md for version updates.
MIT. See LICENSE.
