A React component that runs tscircuit inside a webworker and shows you the PCB, Schematic, 3D and other previews.
- Automatically imports other snippets and libraries if imported
- Runs inside webworker (doesn't block the main thread)
- Converts typescript to javascript using babel
import { RunFrame } from "@tscircuit/runframe/runner"
const App = () => (
<RunFrame
fsMap={{
"main.tsx": `
circuit.add(
<resistor resistance="1k" />
)
`,
}}
entrypoint="main.tsx"
// Listen to events
onCircuitJsonChange={(circuitJson) => void}
onRenderingFinished={({ circuitJson }) => void}
onRenderEvent={(event) => void}
onError={(error) => void}
availableTabs={["pcb", "schematic", "cad"]}
defaultTab="pcb"
/>
)Choose System from the More views dropdown to display system block
pages from Circuit JSON. The viewer uses the resolved block sizes, positions,
and routes, with a page selector for multiple diagrams. The standalone
@tscircuit/system-block-viewer package provides the same drag, scroll, and
touch gestures as schematic-viewer, without zoom buttons.
Use defaultActiveTab="system" to open it initially, or
availableTabs={["system"]} for a System-only preview. Circuits without
system_block_page records show an empty state.
If you already have circuit JSON and just want to display it:
import { CircuitJsonPreview } from "@tscircuit/runframe/preview"
const App = () => (
<CircuitJsonPreview
circuitJson={myCircuitJson}
// Optional props
showCodeTab={false} // Show/hide the code editor tab
showJsonTab={true} // Show/hide the raw JSON viewer tab
className="h-screen" // Container className
headerClassName="px-4" // Header className
isFullScreen={false} // Toggle fullscreen mode
onToggleFullScreen={() => void} // Fullscreen toggle callback
/>
)For displaying circuit JSON files that are fetched on-demand without code execution:
import { RunFrameStaticBuildViewer } from "@tscircuit/runframe"
const App = () => (
<RunFrameStaticBuildViewer
files={[
{
filePath: "main.circuit.json",
fileStaticAssetUrl: "https://api.example.com/circuits/main.json"
},
{
filePath: "components/sensor.circuit.json",
fileStaticAssetUrl: "https://api.example.com/circuits/sensor.json"
}
]}
// Optional: Custom fetch function for authentication, caching, etc.
onFetchFile={async (fileRef) => {
const response = await fetch(fileRef.fileStaticAssetUrl, {
headers: { Authorization: 'Bearer token' }
})
return response.json()
}}
projectName="My Project"
defaultToFullScreen={false}
showToggleFullScreen={true}
/>
)When multiple files are provided, the viewer includes a circuit gallery. Gallery
cards look for preview assets next to each circuit.json, preferring 3d.png
and falling back to PCB or schematic PNG/SVG assets. Add gallery to either the
query string or hash (for example, ?gallery or #gallery=true) to open the
gallery directly.
import evalWebWorkerBlobUrl from "@tscircuit/eval/blob-url"RunFrame can be embedded in an iframe, allowing you to evaluate tscircuit code from a parent application. There are two main ways to integrate RunFrame: using the React component or creating a standalone iframe implementation.
<!DOCTYPE html>
<html>
<head>
<title>RunFrame Host</title>
</head>
<body>
<iframe
id="runframe"
src="https://runframe.tscircuit.com/iframe.html"
style="width: 100%; height: 600px; border: none;"
></iframe>
<script>
const iframe = document.getElementById("runframe")
// Listen for ready message from iframe
window.addEventListener("message", (event) => {
if (event.data?.runframe_type === "runframe_ready_to_receive") {
// Send circuit configuration
iframe.contentWindow.postMessage(
{
runframe_type: "runframe_props_changed",
runframe_props: {
fsMap: {
"main.tsx": `circuit.add(<resistor resistance="1k" />)`,
},
entrypoint: "main.tsx",
},
},
"*"
)
}
})
</script>
</body>
</html>window.addEventListener("message", (event) => {
if (event.data?.runframe_type === "runframe_event") {
const { type } = event.data.runframe_event
if (type === "error") {
console.error(event.data.runframe_event.error_message)
}
}
})Edit events can be a complex to understand, because it can take some time for rendering to complete, edit events are applied for an approximate render before the circuit is fully rendered. The sequence diagram below can be used as a reference to understand how the edit events are applied and queued for rendering.
sequenceDiagram
participant CLI
participant FileServer as File Server
participant BrowserRunframe as Browser Runframe
participant Viewer as Schematic/PCB Viewer
BrowserRunframe->>Viewer: Initial Circuit JSON
Note over Viewer: Edit event created
Viewer->>BrowserRunframe: onEditEvent
Note over BrowserRunframe: newEditEvent._applied=false<br/>editEvents.push(newEditEvent)
BrowserRunframe->>Viewer: editEvents={editEvents}
BrowserRunframe->>FileServer: /files/get manual-edits.json
FileServer-->>BrowserRunframe: manual-edits.json content
Note over BrowserRunframe: Apply edit events to manual-edits.json<br/>Mark editEvent._applied = true
BrowserRunframe->>FileServer: /files/update manual-edits.json
FileServer->>CLI: FILE_UPDATED event
Note over CLI: Update manual-edits.json on filesystem
rect rgb(200, 230, 255)
Note over BrowserRunframe: Start re-render
Note over Viewer: Edit event created
Viewer->>BrowserRunframe: onEditEvent
Note over BrowserRunframe: newEditEvent._applied=false<br/>editEvents.push(newEditEvent)
BrowserRunframe->>Viewer: editEvents={editEvents}<br/>both unapplied & applied<br/>edit events are sent
Note over BrowserRunframe: Render complete
end
Note over BrowserRunframe: Remove all applied editEvents
BrowserRunframe->>Viewer: editEvents={editEvents}<br/>circuitJson={circuitJson}<br/>Send unapplied editEvents list<br/>+ Circuit JSON
Note over BrowserRunframe: If there are unapplied editEvents, repeat
The event bus for the RunFrame is accessible from the @tscircuit/file-server
/events/list endpoint. RunFrame creates the following events:
| Event Name | Description |
|---|---|
| FILE_UPDATED | A file was updated |
| REQUEST_TO_SAVE_SNIPPET | Browser request to save the current circuit to snippets |
| FAILED_TO_SAVE_SNIPPET | Failed to save the current snippet to Registry |
| SNIPPET_SAVED | Snippet was saved to Registry |
RunFrame, CircuitJsonPreview, RunFrameWithApi, RunFrameForCli, and
RunFrameStaticBuildViewer accept extraTabs. Import ExtraRunFrameTab from
@tscircuit/runframe or @tscircuit/runframe/runner:
<RunFrame
fsMap={files}
mainComponentPath="boards/controller.tsx"
extraTabs={[
{
tabName: "Sysconfig",
iframeUrl: "https://sysconfig-viewer.tscircuit.com/#encodedcontent",
mainComponentPath: "boards/controller.tsx",
},
]}
/>Omit mainComponentPath for a project-wide tab. The RunFrame, API/CLI, and
static-build wrappers filter scoped tabs against the selected circuit path.
CircuitJsonPreview accepts the already-selected views. Static-build paths must
match files[].filePath. Different circuits may each define a tab named
Sysconfig; duplicate names within the selected circuit are ignored after the
first valid descriptor. Blank names, malformed URLs, credential-bearing URLs,
and protocols other than HTTP(S) are ignored.
Extra views appear in More views, with the selected view also shown in the
header. They are additional to availableTabs, which filters built-in views.
Use defaultActiveTab="extra:Sysconfig" to select this example initially; the
suffix is the URL-encoded tab name. Iframes load when first selected and remain
mounted between view changes. Removing the selected view falls back to an
available tab. Changing its URL navigates the existing iframe to the new page.
For the standalone CLI bundle, inject the global before loading the bundle:
<script>
window.TSCIRCUIT_EXTRA_RUNFRAME_TABS = [
{ tabName: "Sysconfig", iframeUrl: "https://sysconfig-viewer.tscircuit.com/#encodedcontent" }
];
</script>For live updates, replace that array and dispatch
window.dispatchEvent(new Event("tscircuit:extra-runframe-tabs-change")).
React consumers update the extraTabs prop instead.
Pages must be trusted by the embedding application. Frames use
sandbox="allow-scripts allow-same-origin" so application assets and SDK data
can load, without granting popup, form, or top-level navigation capabilities.
An iframe served from the parent's own origin shares that origin's trust;
use a separate origin for external viewers. The framed page controls read-only
behavior and must allow framing through its response headers.
example62-extra-iframe-tabs demonstrates transport, retained navigation, and
circuit scoping using a local read-only sample. It is not a TI SysConfig parity
demo and does not implement SysConfig parsing or URL encoding.