State Builder
molstar-components provides a visual State Builder for composing
MolViewSpec (MVS) scenes,
paired with a Monaco-based code editor. The two are kept in sync — any scene built visually becomes
executable JavaScript, and any script can be loaded back into the visual tree.
The module is designed for two kinds of users: those who prefer a graphical, point-and-click workflow with no prior knowledge of the MVS data model, and those who prefer writing code but want guided parameter input for specific node kinds.
Visual workflow
Add nodes, configure them through dedicated dialogs, preview the result in the viewer — no code required.
Code workflow
Write MVS builder scripts with full type-checking; use hybrid mode to get helper dialogs for individual method calls.
Core Concepts
A scene in this builder is a tree of nodes. Each node is one step in the visualization pipeline — fetching data, interpreting it, selecting subsets, and rendering them. The tree can branch: the same parsed structure can have multiple components, each displayed in a different style simultaneously.
The steps below describe the common path. They map directly to the node tree you see in the builder. The underlying format is MolViewSpec ↗ — the full node reference is there if you need details beyond what the builder exposes.
Add a structure source download / parse ↗
In the builder, the first node you add is Download/Parse — a combined step
where you supply the URL of your structure file and select its format
(mmCIF,
BinaryCIF,
PDB).
Choose the structure type structure ↗
Add a Structure node under Download/Parse. This tells the viewer how to interpret the coordinate file — the same file can describe the molecule several ways:
- model — the deposited asymmetric unit. The most common starting point for a PDB entry; no assembly copies are generated.
- assembly — a biological assembly defined in the file, identified by its ID (
"1","2", …). The functional form of a protein is often assembly"1". - symmetry — a crystallographic unit cell, supercell, or individual NMR model.
Select what to show component ↗ selector syntax ↗
Add one or more Component nodes under the Structure. Each component selects
a subset of atoms or residues. Built-in group selectors let you quickly target
polymer,
protein,
nucleic,
branched,
ligand,
ion, or
water.
For finer control the selector helper lets you filter by chain, residue range, or atom.
Multiple components branch independently — use this to display the backbone as a cartoon
and a ligand as ball-and-stick in the same view.
Add a representation representation ↗
Add a Representation node under each component to decide how it is drawn. Common styles:
- cartoon — ribbon diagram emphasising secondary structure; the default and standard view for proteins.
- ball_and_stick — individual atoms and bonds; good for ligands and active-site residues.
- surface — molecular surface; shows the overall shape and binding pockets.
- spacefill — van der Waals spheres; emphasises atomic volume.
- Others:
backbone,line,carbohydrate.
Apply color color ↗
Add a Color node under a representation to color it. Colors are entered as hex codes or SVG color names. Stack multiple Color nodes to layer overrides — for example, a uniform base color followed by a per-residue highlight. Scheme-based coloring (by chain, element, B-factor, etc.) is also available via the color schema reference ↗.
Going further
Explicitly set camera position and target point for fully reproducible viewpoints.
label / tooltipAttach 3D text annotations to components. Tooltips appear on mouse interaction rather than always visible.
transformApply a rotation and translation to a structure — the basis for superimposing two structures in the same scene.
opacityMake a representation transparent — useful for showing buried elements through a surface.
component / color / label from URIDrive selections, coloring, or labels from external annotation files (CIF, BinaryCIF, JSON), keeping scene description and data separate.
primitivesDraw geometric shapes — arrows, lines, spheres, boxes — to annotate or highlight regions of interest.
See the Advanced Example for a live demo combining primitives, opacity animation, and ligand selections in one scene.
Visual Node Tree
The visual editor represents an MVS scene as a tree of typed nodes. Each node corresponds to one step in the MolViewSpec pipeline — download a file, parse it, select components, apply representations and colors, and more.
- → Node grammar enforced — only valid child kinds are offered in the add-child menu, preventing invalid configurations.
- → Per-kind helper dialogs — clicking a node summary opens a dedicated configuration modal tailored to that node's parameter schema.
-
→
Ref field — optional named reference on any node; used to target nodes from
interpolatesteps in animations. - → Generate Code — converts the current tree into an executable JavaScript snippet using the MVS builder API.
Setup Wizard
When the node tree is empty, the Setup Wizard guides you through the most common scene composition pattern in three steps — no prior knowledge of the MVS data model required.
-
1
Source — enter a structure URL and select the file format. Creates the
downloadandparsenodes. - 2 Structure type — choose between model, assembly, symmetry, or other structure modes.
- 3 Components — define one or more component groups, each with a selector, representation type, color, and optional opacity or label.
https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif
format: bcif
As with most PDB structures, model with defaults works fine here.
Advanced Example
The example below loads hemoglobin (1OG5 assembly), colors two ligands individually, adds ellipsoid markers around each ligand and a distance measurement primitive, then animates the opacity of both so they fade in sequentially.
For a broader showcase of MolViewSpec capabilities across many scene types, explore the MolViewSpec features story ↗.
Monaco Editor & Hybrid Mode
The built-in Monaco editor has the full MVS builder API injected as TypeScript definitions, giving you type-checking and autocomplete as you write scenes in code.
With hybrid mode enabled, right-clicking any
.component(),
.color(),
.transform(), or
.camera() call opens
the same helper dialog used in the visual builder — parameters are read from
the call, and writing them back updates the source code in place.
.component(...) call and choose Edit Selector with Helper, or right-click a .color(...) call and choose Pick Color with Helper.
Bidirectional Sync
The visual builder and the code editor are kept in sync in both directions. Clicking Generate Code in the Builder tab produces a JavaScript snippet and pushes it to the editor. Going the other way, clicking → Sync to Builder in the Code tab executes the current script and reconstructs the visual node tree from the resulting MVS data.
Installation
The package currently distributes TypeScript source via JSR. Several of the steps below work around limitations of that format in Node-based toolchains. A direct npm release is planned that will remove most of this configuration. Treat this section as a living document.
Prerequisites
Install the following peer dependencies before adding this package:
| Package | Minimum | Notes |
|---|---|---|
| react | 19+ | |
| react-dom | 19+ | |
| molstar | 5.0.0+ | |
| monaco-editor | 0.55+ | Required for MolViewEditor |
| jotai | 2.x |
Install
Node (npm / pnpm / yarn / bun) — via JSR's npm compatibility layer:
# pnpm
pnpm add jsr:@molstar/molstar-components
# npm
npx jsr add @molstar/molstar-components
# yarn
yarn dlx jsr add @molstar/molstar-components
# bun
bunx jsr add @molstar/molstar-components
Deno — native JSR support, no install step needed. See the Deno section below.
import { UIBuilderProvider, UIBuilder } from "jsr:@molstar/molstar-components";
Bundler setup
The package uses JSR-style npm:pkg@ver/path import specifiers internally.
Webpack-based bundlers don't resolve these natively — add the NormalModuleReplacementPlugin to rewrite them to plain package paths at bundle time.
The package also exports an alias map at @molstar/molstar-components/aliases for bundlers that don't support webpack plugins (e.g. Vite). For webpack and Next.js, the plugin alone is sufficient.
// webpack.config.js
import webpack from "webpack";
export default {
plugins: [
// Rewrite JSR-style "npm:pkg@ver/path" specifiers to plain npm paths
new webpack.NormalModuleReplacementPlugin(
/^npm:/,
(resource) => {
resource.request = resource.request
.slice(4)
.replace(/((?:@[^@/]+\/)?[^@/]+)@[^/]*(.*)/g, "$1$2");
}
),
],
};
Tailwind v4 CSS
Theme variables and utility styles are injected automatically by UIBuilderProvider via React 19's style precedence — no CSS import required.
For Tailwind v4, add an @source directive so Tailwind scans the library source and generates the utility classes it uses:
/* Adjust the path to your node_modules relative to this CSS file */
@source "../../../node_modules/@molstar/molstar-components/src/state-builder-ui";
When using Turbopack, some utility classes that appear only inside the library are missed by the @source scanner and must be force-generated.
The list below was compiled from a working Next.js integration — your project may need more or fewer entries depending on what your own source already generates:
@layer utilities {
.px-5 { padding-inline: 1.25rem; }
.pt-1 { padding-top: 0.25rem; }
.pt-3 { padding-top: 0.75rem; }
.pt-5 { padding-top: 1.25rem; }
.pb-1 { padding-bottom: 0.25rem; }
.pb-2 { padding-bottom: 0.5rem; }
.gap-0\.5 { gap: 0.125rem; }
/* Dialog size constraints whose sm: responsive variants Turbopack fails to emit */
.max-h-\[85vh\] { max-height: 85vh; }
.max-w-\[calc\(100vw-2rem\)\] { max-width: calc(100vw - 2rem); }
@media (min-width: 640px) {
.sm\:max-w-md { max-width: var(--container-md, 28rem); }
.sm\:max-w-lg { max-width: var(--container-lg, 32rem); }
.sm\:max-w-2xl { max-width: var(--container-2xl, 42rem); }
.sm\:max-w-\[520px\] { max-width: 520px; }
}
}
Molstar's own CSS still needs to be imported explicitly: import 'molstar/build/viewer/molstar.css'
TypeScript paths
JSR packages use versioned npm:pkg@ver/path specifiers. TypeScript's bundler resolution doesn't handle these automatically — map them to your local node_modules via paths.
{
"compilerOptions": {
"paths": {
"npm:react@*": ["./node_modules/@types/react/index.d.ts"],
"npm:react-dom@*": ["./node_modules/@types/react-dom/index.d.ts"],
"npm:molstar@5.9.0/*": ["../../node_modules/molstar/*"],
"npm:monaco-editor@0.55.1/*": ["./node_modules/monaco-editor/*"],
"npm:@radix-ui/react-dialog@*": ["./node_modules/@radix-ui/react-dialog/dist/index.d.ts"],
"npm:@radix-ui/react-dropdown-menu@*": ["./node_modules/@radix-ui/react-dropdown-menu/dist/index.d.ts"],
"npm:@radix-ui/react-label@*": ["./node_modules/@radix-ui/react-label/dist/index.d.ts"],
"npm:@radix-ui/react-select@*": ["./node_modules/@radix-ui/react-select/dist/index.d.ts"],
"npm:@radix-ui/react-slot@*": ["./node_modules/@radix-ui/react-slot/dist/index.d.ts"],
"npm:@radix-ui/react-switch@*": ["./node_modules/@radix-ui/react-switch/dist/index.d.ts"],
"npm:@radix-ui/react-tabs@*": ["./node_modules/@radix-ui/react-tabs/dist/index.d.ts"],
"npm:class-variance-authority@*": ["./node_modules/class-variance-authority/dist/index.d.ts"],
"npm:clsx@*": ["./node_modules/clsx/dist/clsx.d.ts"],
"npm:lucide-react@*": ["./node_modules/lucide-react/dist/cjs/lucide-react.d.ts"],
"npm:tailwind-merge@*": ["./node_modules/tailwind-merge/dist/types.d.ts"],
"npm:jotai@*": ["./node_modules/jotai/index.d.ts"]
}
}
}
Monaco worker setup
MolViewEditor runs Monaco's TypeScript language service in a Web Worker.
The worker entry points use new URL(…, import.meta.url) and must be processed by your bundler — the library cannot do this on your behalf.
Without this the editor mounts, but type-checking and autocomplete are disabled.
Create a setup module and call it before mounting MolViewEditor:
// lib/monaco-worker-setup.ts
export function setupMonacoWorkers(): void {
if (typeof window === 'undefined') return;
if ((window as { MonacoEnvironment?: unknown }).MonacoEnvironment) return;
(window as Window & { MonacoEnvironment?: unknown }).MonacoEnvironment = {
getWorker(_moduleId: string, label: string): Worker {
if (label === 'typescript' || label === 'javascript') {
return new Worker(
new URL('monaco-editor/esm/vs/language/typescript/ts.worker', import.meta.url)
);
}
return new Worker(
new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url)
);
},
};
}
// In the file that uses MolViewEditor — top-level call, runs once on module load
import { setupMonacoWorkers } from './lib/monaco-worker-setup';
setupMonacoWorkers();
Deno
Deno has native JSR support — no install step, no bundler plugin, and no npm: rewrite needed. Import directly or pin in deno.json:
// Direct import
import { UIBuilderProvider, UIBuilder } from "jsr:@molstar/molstar-components";
// deno.json — pin a version
{
"imports": {
"@molstar/molstar-components": "jsr:@molstar/molstar-components@^0.6.0"
}
}
Monaco worker setup still applies when targeting a browser — use the same setupMonacoWorkers pattern above, adapted for your Deno bundler (e.g. esbuild with @luca/esbuild-deno-loader).
Component usage
Use UIBuilderProvider and UIBuilder to embed the visual builder into your layout:
import { UIBuilderProvider, UIBuilder } from '@molstar/molstar-components';
<UIBuilderProvider
onCodeGenerated={(code) => runCode(code)}
onNotification={(n) => toast(n.message)}
>
<UIBuilder />
</UIBuilderProvider>
For viewer integration, the imperative UIBuilderHandle API, theming options, and local cross-repo development, see
DEV.md ↗.