Getting Started
Installation
npm
npm install @liveroom-tech/react-immersive three @react-three/fiberthree and @react-three/fiber are peer dependencies, along with React 19, so
your app has exactly one copy of each. Everything else installs with the
package.
Import Styles
Import the stylesheet for each component you use, once, from your application
root. In a Next.js App Router project, put these in app/layout.tsx:
import "@liveroom-tech/react-immersive/styles.css"; // ModelViewer
import "@liveroom-tech/react-immersive/simple-model-viewer.css"; // SimpleModelViewer
import "@liveroom-tech/react-immersive/binding-builder.css"; // BindingBuilderSimpleModelViewer and BindingBuilder are imported from their own entry
points, @liveroom-tech/react-immersive/simple-model-viewer and
@liveroom-tech/react-immersive/binding-builder, so an app downloads only the
components it uses.
If a component’s stylesheet is missing, it renders unstyled, and in development it logs a console warning naming the import to add.
Get a License Key
ModelViewer and BindingBuilder require a licenseKey. (SimpleModelViewer does not, it’s unlicensed.) To get one:
- Sign in (or sign up) at the React Immersive Console
- Choose a plan
- Generate a license key from your dashboard
Keep your license key out of public repositories. Load it from an environment variable in your app.
Quick Start
To see your model working before you write any bindings, let the viewer generate them:
import { ModelViewer } from "@liveroom-tech/react-immersive";
export default function App() {
return (
<div style={{ height: "100vh", overflow: "hidden" }}>
<ModelViewer
modelUrl="/model.glb"
licenseKey={process.env.NEXT_PUBLIC_LICENSE_KEY!}
/>
</div>
);
}Every mesh can then be hovered and selected, with Change Color, Change Material, and Toggle Visibility in the side panel (or light controls for meshes whose names contain “light”). See generating bindings automatically to keep and refine them. The steps below write the bindings yourself, which gives you control over labels, actions, metadata, and camera views.
Define your object bindings
Each key in objectBindings must match a model node identifier. Unique nodes
use their authored name; duplicate names receive identifiers such as Cube [1]
and Cube [2]. BindingBuilder generates the correct identifiers automatically.
You can write the bindings by hand, or generate starter bindings in
BindingBuilder and export them as
objectBindings.json or a TypeScript file to import into your app.
For a configured scene, export sceneConfig.ts from BindingBuilder’s Scene
tab and pass its named sceneConfig export to ModelViewer as well. The
BindingBuilder export and import guide
explains where each toolbar action lives and when to choose JSON versus
TypeScript.
If you want a ready-to-run starter, download the sample
red_room.glb model and
objectBindings.ts bindings file from these docs. Put
red_room.glb in your app’s public/ folder, and place objectBindings.ts
in your source tree so you can import it into your viewer page.
Sample model credit: “Red Room” by LaisLH, licensed under Creative Commons Attribution 4.0 .
import { defineObjectBindings } from "@liveroom-tech/react-immersive/utils";
const objectBindings = defineObjectBindings({
BrickWall: {
id: "brick-wall",
modelObjectId: "BrickWall",
type: "wall",
label: "Room Walls",
selectable: true,
hoverable: true,
visible: true,
style: {},
metrics: {},
metadata: {},
cameraState: {
position: [-0.91, 0.87, 0.42],
target: [0.09, 0.98, 0.036],
fov: 45,
},
actions: [
{ id: "change-color", label: "Change Color", type: "command" },
{ id: "change-material", label: "Change Material", type: "command" },
{ id: "toggle-visibility", label: "Toggle Visibility", type: "command" },
],
},
});Mount the viewer
Wrap ModelViewer in a full-height container. The component owns its own layout.
import { useState } from "react";
import { ModelViewer } from "@liveroom-tech/react-immersive";
import { objectBindings as initialBindings } from "./objectBindings.ts";
export default function App() {
const [objectBindings, setObjectBindings] = useState(initialBindings);
return (
<div style={{ height: "100vh", overflow: "hidden" }}>
<ModelViewer
modelUrl="/red_room.glb"
licenseKey="your-license-key"
objectBindings={objectBindings}
onObjectBindingsChange={setObjectBindings}
/>
</div>
);
}Control the viewer from your app
useViewer connects every viewer hook to ModelViewer at once. Spread its
props onto the viewer, then drive bindings, camera, selection, animations,
and scene settings from your own UI.
import { ModelViewer, useViewer } from "@liveroom-tech/react-immersive";
import { objectBindings } from "./objectBindings";
export default function Example() {
const viewer = useViewer({
bindings: objectBindings,
onAction: (event) => console.log(event),
});
const { toggleObjectVisibility } = viewer.bindings;
const { resetView, focusObject } = viewer.camera;
const { clips, play } = viewer.animations;
return (
<div style={{ height: "100vh", overflow: "hidden" }}>
<ModelViewer
modelUrl="/red_room.glb"
licenseKey="your-license-key"
{...viewer.props}
/>
<button onClick={() => toggleObjectVisibility("some-object-id")}>
Toggle Visibility
</button>
<button onClick={() => resetView()}>Reset Camera</button>
<button onClick={() => focusObject("some-object-id")}>
Focus Object
</button>
{clips.map((name) => (
<button key={name} onClick={() => play(name)}>
{name}
</button>
))}
</div>
);
}Leave out bindings and useViewer generates them from the model, like
objectBindings="auto". See useViewer for
everything it returns, and the other hooks
for wiring individual pieces yourself.