A React wrapper for the Mol* molecular visualization library, providing seamless integration of molecular structure visualization capabilities into React applications.
- Features
- Overview
- Installation
- Usage
- Props
- ViewerRef Methods
- Protein Type
- Examples
- Advanced Usage
- Documentation
- License
- Molecular Visualization - Display 3D molecular structures using the Mol* visualization engine
- React Component - Simple React component with minimal setup
- Multiple Data Sources - Load proteins from UniProt IDs or local files (PDB, CIF formats)
- Customizable Appearance - Configure background colors, labels, and UI presets (minimal, standard, expanded)
- Animations - Support for spin and rock animations with configurable speeds
- Domain Highlighting - Focus on specific protein domains using chopping data
- Transform Controls - Update protein superposition with custom rotation and translation
- Imperative API - Access viewer methods via forwarded ref for programmatic control
- Next.js Compatible - Works with Next.js using dynamic imports
- Plugin Lifecycle Management - Shared plugin instance with reference counting and automatic garbage collection
- Multiple Representations - Choose from cartoon, ball-and-stick, spacefill, line, surface, or backbone representations
- Custom Model Sources - Configure custom URLs for remote model retrieval
- Precomputed MVS Support - Load precomputed Mol* View State data directly
- Styling Support - Apply custom CSS classes and heights to the viewer container
- Responsive Design - Viewer fills available height by default, with optional explicit sizing
This package provides a lightweight React wrapper around the Mol* molecular visualization library, enabling developers to easily integrate 3D molecular structure visualization into their React applications.
npm install @e-infra/react-molstar-wrapperbun install @e-infra/react-molstar-wrapperThis component must be used within Client Components as Mol* requires the document object to be available at runtime.
import Viewer from "@e-infra/react-molstar-wrapper";
import "@e-infra/react-molstar-wrapper/style.css";When using Next.js, the component must be imported dynamically to avoid server-side rendering issues. Add the "use client" directive and use Next.js's dynamic import:
"use client";
import "@e-infra/react-molstar-wrapper/style.css";
import dynamic from "next/dynamic";
const Viewer = dynamic(
() => import("@e-infra/react-molstar-wrapper").then((mod) => mod.Viewer),
{ ssr: false }
);This approach prevents errors related to document not being defined during server-side rendering, as Mol* expects the DOM to be available during initialization.
Important
It is important to include library styles as well! Otherwise loader and error view will be broken.
import "@e-infra/react-molstar-wrapper/style.css";The Viewer component accepts the following props:
Exactly one of these must be provided:
| Prop | Type | Description |
|---|---|---|
proteins |
Protein[] |
Array of protein objects to visualize. When provided, the component will call createMVS to compute the view state. |
mvs |
MVSData |
Precomputed MVS (Mol* View State) data. If provided, the viewer loads this directly without calling createMVS. |
| Prop | Type | Default | Description |
|---|---|---|---|
modelSourceUrls |
Partial<ModelSourceUrls> |
undefined |
Optional lookup mapping used by createMVS to resolve model source URLs for remote model retrieval when proteins is used. Format: { uniProtId: (id: string) => string }. |
initialUI |
"minimal" | "standard" | "expanded" |
"standard" |
Which initial UI preset to use for the embedded plugin. Controls the visibility of control chrome. |
bgColor |
ColorHEX |
"#ffffff" |
Background color for the viewer (any valid CSS hex color). |
labels |
boolean |
true |
Whether to show labels in the viewer. |
height |
number |
undefined |
Optional explicit height (in pixels) for the outer wrapper. If omitted, the wrapper will fill available height (100%). |
className |
string |
undefined |
Optional CSS class to apply to the outer wrapper. |
At most one of these may be provided:
| Prop | Type | Default | Description |
|---|---|---|---|
spin |
boolean |
false |
Whether to enable continuous spin animation. Mutually exclusive with rock. |
rock |
boolean |
false |
Whether to enable rock animation (back-and-forth). Mutually exclusive with spin. |
spinSpeed |
number |
0.05 |
Speed multiplier for the spin animation. |
rockSpeed |
number |
0.2 |
Speed multiplier for the rock animation. |
The component forwards a ref exposing the following async methods:
Focuses/highlights a domain within the specified protein by matching the label against the protein's chopping data. The domain's first range (start/end) is used to focus the view.
-
Parameters:
proteinIndex- Index of the protein in theproteinsarraylabel- Label of the domain to highlight (must match a label in the protein'schoppingdata)
-
Behavior: No-op if plugin, proteins, or matching domain is not available.
Resets the plugin's view to its default/original pose.
Updates the transform for a loaded structure (protein) without reloading the entire scene.
-
Parameters:
proteinIndex- Index of the protein to updatetranslation- Optional[x, y, z]tuple for translationrotation- Optional 3x3 matrix represented as[[r11,r12,r13],[r21,r22,r23],[r31,r32,r33]]
-
Behavior: This method relies on the Mol* plugin API to update transforms in-place.
The Protein type represents a protein structure to visualize:
type Protein = {
// Exactly one of these must be provided
uniProtId?: string; // UniProt ID for remote fetching
file?: File; // Local file to load
// Optional properties
chain?: string; // Chain identifier
superposition?: {
rotation: Matrix3D; // 3x3 rotation matrix
translation: Vector3D; // [x, y, z] translation vector
};
chopping?: Chopping[]; // Domain definitions for highlighting
representation?: "cartoon" | "ball_and_stick" | "spacefill" | "line" | "surface" | "backbone";
};type Chopping = {
label: string; // Domain label for identification
showLabel?: boolean; // Whether to show the label in the viewer
ranges: {
start: number; // Residue start position
end: number; // Residue end position
}[];
}[];import "@e-infra/react-molstar-wrapper/style.css";
import Viewer from "@e-infra/react-molstar-wrapper";
import type { Protein } from "@e-infra/react-molstar-wrapper";
const proteins: Protein[] = [
{
uniProtId: "P12345",
},
];
function App() {
return (
<Viewer
proteins={proteins}
spin={true}
/>
);
}import "@e-infra/react-molstar-wrapper/style.css";
import Viewer from "@e-infra/react-molstar-wrapper";
import type { Protein } from "@e-infra/react-molstar-wrapper";
const proteins: Protein[] = [
{
uniProtId: "P12345",
representation: "cartoon",
chopping: [
{
label: "Domain A",
ranges: [{ start: 1, end: 100 }],
},
{
label: "Domain B",
ranges: [{ start: 101, end: 200 }],
},
],
},
{
uniProtId: "P67890",
representation: "ball_and_stick",
superposition: {
rotation: [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
],
translation: [10, 0, 0],
},
},
];
function App() {
return (
<Viewer
proteins={proteins}
initialUI="minimal"
bgColor="#1a1a2e"
labels={true}
height={600}
className="my-viewer"
/>
);
}import "@e-infra/react-molstar-wrapper/style.css";
import Viewer, { type ViewerRef } from "@e-infra/react-molstar-wrapper";
import type { Protein } from "@e-infra/react-molstar-wrapper";
import { useRef } from "react";
const proteins: Protein[] = [
{
uniProtId: "P12345",
chopping: [
{
label: "Active Site",
ranges: [{ start: 50, end: 75 }],
},
],
},
];
function App() {
const viewerRef = useRef<ViewerRef | null>(null);
const handleHighlight = async () => {
await viewerRef.current?.highlight(0, "Active Site");
};
const handleReset = async () => {
await viewerRef.current?.reset();
};
const handleUpdateTransform = async () => {
await viewerRef.current?.updateSuperposition(
0,
[5, 0, 0],
[
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
]
);
};
return (
<div>
<div style={{ marginBottom: "10px" }}>
<button onClick={handleHighlight}>Highlight Active Site</button>
<button onClick={handleReset}>Reset View</button>
<button onClick={handleUpdateTransform}>Update Transform</button>
</div>
<Viewer
ref={viewerRef}
proteins={proteins}
bgColor="#ffffff"
height={500}
/>
</div>
);
}import "@e-infra/react-molstar-wrapper/style.css";
import Viewer from "@e-infra/react-molstar-wrapper";
import type { Protein } from "@e-infra/react-molstar-wrapper";
const proteins: Protein[] = [
{
uniProtId: "P12345",
},
];
function App() {
return (
<Viewer
proteins={proteins}
rock={true}
rockSpeed={0.3}
bgColor="#f0f0f0"
/>
);
}import "@e-infra/react-molstar-wrapper/style.css";
import Viewer from "@e-infra/react-molstar-wrapper";
import type { Protein } from "@e-infra/react-molstar-wrapper";
import { useState } from "react";
function App() {
const [protein, setProtein] = useState<Protein | undefined>();
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
setProtein({ file });
}
};
return (
<div>
<input type="file" onChange={handleFileChange} accept=".pdb,.cif" />
{protein && (
<Viewer
proteins={[protein]}
height={500}
/>
)}
</div>
);
}import "@e-infra/react-molstar-wrapper/style.css";
import Viewer from "@e-infra/react-molstar-wrapper";
import type { Protein } from "@e-infra/react-molstar-wrapper";
const proteins: Protein[] = [
{
uniProtId: "P12345",
},
];
const modelSourceUrls = {
uniProtId: (id: string) => `https://api.example.com/protein/${id}`,
};
function App() {
return (
<Viewer
proteins={proteins}
modelSourceUrls={modelSourceUrls}
bgColor="#ffffff"
/>
);
}If you have precomputed MVS (Mol* View State) data, you can pass it directly to the viewer:
import "@e-infra/react-molstar-wrapper/style.css";
import Viewer from "@e-infra/react-molstar-wrapper";
import type { MVSData } from "molstar/lib/extensions/mvs/mvs-data.d.ts";
const mvsData: MVSData = {
// Your precomputed MVS data
};
function App() {
return (
<Viewer
mvs={mvsData}
bgColor="#ffffff"
/>
);
}import "@e-infra/react-molstar-wrapper/style.css";
import Viewer, { type ViewerRef } from "@e-infra/react-molstar-wrapper";
import type { Protein } from "@e-infra/react-molstar-wrapper";
import { useRef, useEffect } from "react";
const proteins: Protein[] = [
{
uniProtId: "P12345",
representation: "cartoon",
chopping: [
{ label: "N-terminal", ranges: [{ start: 1, end: 50 }] },
{ label: "Core", ranges: [{ start: 51, end: 150 }] },
{ label: "C-terminal", ranges: [{ start: 151, end: 200 }] },
],
},
{
uniProtId: "P67890",
representation: "surface",
superposition: {
rotation: [
[0.9, -0.1, 0],
[0.1, 0.9, 0],
[0, 0, 1],
],
translation: [15, 0, 0],
},
},
];
function App() {
const viewerRef = useRef<ViewerRef | null>(null);
// Auto-highlight a domain after viewer loads
useEffect(() => {
const timer = setTimeout(async () => {
await viewerRef.current?.highlight(0, "Core");
}, 2000);
return () => clearTimeout(timer);
}, []);
return (
<Viewer
ref={viewerRef}
proteins={proteins}
initialUI="expanded"
bgColor="#0d1117"
labels={true}
spin={false}
height={700}
className="custom-viewer"
/>
);
}- Contributing Guide - Development setup, code quality, and contribution guidelines
- Changelog - Version history and release notes
See LICENSE.md for details.
This library is built on top of Mol*, an open-source molecular visualization toolkit.