SimpleModelViewer
A lightweight viewer for cases where you want to load a GLB, GLTF, OBJ, FBX, USDZ, STL, or PLY asset, inspect meshes, and toggle visibility without building objectBindings. It can either load a fixed modelUrl or, when enableModelUpload is enabled, let the user drag-and-drop or choose a local model at runtime.
Usage
import "@liveroom-tech/react-immersive/simple-model-viewer.css";
import { SimpleModelViewer } from "@liveroom-tech/react-immersive/simple-model-viewer";
export default function App() {
return (
<div style={{ height: "100vh" }}>
<SimpleModelViewer modelUrl="/model.glb" />
</div>
);
}Behavior
- Discovers renderable meshes directly from the loaded model scene
- Renders a right-side scene objects panel with search and visibility toggles
- Renders a left-side environment/scene settings panel (background color, environment preset, auto-rotate, exposure, ambient + directional light color/intensity), each seedable via props
- Can swap
modelUrlfor a built-in drag-and-drop / file-picker upload flow - Lets users click a mesh or panel row to select and focus it. Panel rows are buttons, so the keyboard can select from the panel too
- Fits the full scene on initial load, and re-fits when the canvas is resized (window resize, device rotation, or a panel opening/closing), set
refitOnResize={false}to instead preserve the user’s orbit/zoom - Uses built-in ambient, directional, and environment lighting
- Renders on demand: frames are only drawn when something changes (camera movement, hover, auto-rotate, panel edits), so an idle viewer costs no GPU time or battery
- Degrades gracefully on very large models (above roughly 12 million triangles): viewport click/hover picking is disabled for performance, a dismissible banner explains this to the user, and selection stays available through the scene objects panel
Use SimpleModelViewer when you want a quick inspection viewer. Use
ModelViewer when you need binding-driven
styling, actions, metadata, custom panels, exports, or animation integrations.
Props
type SimpleModelViewerProps = {
modelUrl: string;
poster?: string;
loading?: "lazy" | "eager"; // default "lazy"
reveal?: "auto" | "interaction"; // default "auto"
labels?: ModelViewerLabels;
modelFormat?: "glb" | "gltf" | "obj" | "fbx" | "usdz" | "stl" | "ply";
dracoDecoderPath?: string | false;
ktx2TranscoderPath?: string | false;
meshopt?: boolean;
backgroundColor?: string;
showSceneObjectsPanel?: boolean;
showSceneSettingsPanel?: boolean;
enableModelUpload?: boolean;
zoomOnSelected?: boolean;
highlightOnHover?: boolean;
showLoadingOverlay?: boolean;
refitOnResize?: boolean;
reducedMotion?: "user" | "always" | "never";
// Initial values for the scene settings (environment) panel
backgroundEnabled?: boolean;
autoRotate?: boolean;
envPreset?: PresetsType; // @react-three/drei environment preset
exposure?: number;
ambientIntensity?: number;
ambientColor?: string;
directionalIntensity?: number;
directionalColor?: string;
onModelLoaded?: (model: ViewerModelInfo) => void;
onLoadError?: (error: unknown) => void;
};SimpleModelViewer does not take a licenseKey, licensing is enforced by
ModelViewer and
BindingBuilder.
labels
Optional plain-text overrides for built-in viewer text, including button titles, accessible labels, loading messages, tool hints, and AR placement controls. Missing keys retain their English defaults.
ModelViewer and SimpleModelViewer share the same dictionary. Each viewer has its own labels, and changing the prop updates text without reloading its model. See the full label catalog for every key, default, and named placeholder.
labels does not translate annotation content, object metadata, custom panels, or raw server/loader diagnostics. Supply translations for those in your application.
<SimpleModelViewer
modelUrl="/car.glb"
labels={{
guidedTour: "Visite guidée",
tapToPlace: "Touchez pour placer",
sceneSettings: "Réglages",
objectCount: "Objets : {count}",
}}
/>modelUrl
Required. URL or public path to a .glb, .gltf, .obj, .fbx, .usdz, .stl, or .ply model. Hosted assets must serve external dependencies at their declared relative paths; USDZ dependencies are packaged internally.
modelFormat
Optional explicit format for extensionless or signed URLs. Supported extensions are detected automatically in normal URLs.
poster / loading / reveal
Work as on ModelViewer,
sharing the same page-wide count of live 3D views. When a SimpleModelViewer
is put away, only its canvas goes: the panels and their settings stay, and the
canvas comes back with its camera, hidden objects and selection.
onModelLoaded runs again when it does.
dracoDecoderPath / ktx2TranscoderPath / meshopt
Compressed-asset decoder configuration. Meshopt is bundled; DRACO and KTX2
default to immutable, versioned files on the first-party React Immersive domain.
Override with self-hosted directories or pass false to disable either format.
See the ModelViewer decoder setup.
backgroundColor
Optional canvas background color.
Default:
"#1a1a1a";showSceneObjectsPanel
Optional boolean that controls whether the right-side scene objects panel is rendered.
Default:
true;showSceneSettingsPanel
Optional boolean that controls whether the left-side environment/scene settings panel is rendered. It exposes background color/visibility, the @react-three/drei environment preset, auto-rotate, exposure, and ambient/directional light color and intensity.
Default:
true;Scene settings defaults
The following optional props seed the initial values of the scene settings (environment) panel. Each maps to one control; the panel stays interactive, so the user can still adjust them at runtime. Changing one of these props after mount does not override a value the user has since changed in the panel (they are initial values, not controlled props), the exception is backgroundColor, which is kept in sync live.
| Prop | Type | Panel control | Default |
|---|---|---|---|
backgroundEnabled | boolean | Background on/off | false |
autoRotate | boolean | Auto Rotate | false |
envPreset | PresetsType | Environment preset | "city" |
exposure | number | Exposure | 1 |
ambientIntensity | number | Ambient Light Intensity | 0.6 |
ambientColor | string | Ambient Light Color | "#ffffff" |
directionalIntensity | number | Direct Light Intensity | 1.2 |
directionalColor | string | Direct Light Color | "#ffffff" |
envPreset accepts any @react-three/drei PresetsType (for example "city", "sunset", "dawn", "night", "warehouse", "forest", "apartment", "studio", "park", "lobby").
<SimpleModelViewer
modelUrl="/model.glb"
backgroundEnabled
envPreset="sunset"
exposure={1.2}
ambientIntensity={0.4}
directionalColor="#fff4e0"
autoRotate
/>zoomOnSelected
Optional boolean. When true, selecting a mesh (via click or the objects panel) zooms/frames the camera on it.
Default:
false;highlightOnHover
Optional boolean. When true, hovering a mesh (in the viewport or the objects panel) highlights it.
On very large models (above roughly 12 million triangles) viewport hover is disabled for performance regardless of this prop, panel-row hover highlighting still works.
Default:
false;showLoadingOverlay
Optional boolean controlling whether the built-in loading overlay is shown while the model is loading, uploaded files are being prepared, and the initial camera fit is settling.
Default:
true;refitOnResize
Optional boolean controlling whether the camera re-frames the model to fit whenever the canvas is resized, a browser window resize, a device rotation, or a side panel (scene settings / scene objects) opening or closing. Set to false to preserve the user’s current orbit/zoom across resizes; the camera aspect stays correct either way, so the model never distorts, it just isn’t re-centered.
Default:
true;reducedMotion
Cuts motion the viewer starts on its own. "user" follows the visitor’s device setting (prefers-reduced-motion); "always" and "never" override it. When motion is reduced, the autoRotate prop doesn’t start auto-rotate (the visitor can still turn it on in the scene settings panel), camera moves jump instead of gliding, and panels appear in place. See ModelViewer’s reducedMotion.
Default:
"user";enableModelUpload
Optional boolean that swaps the fixed modelUrl workflow for a built-in upload UI. It accepts .glb, standalone/data-URI .gltf, .obj, .fbx, .usdz, .stl, .ply, or .zip. A ZIP may bundle GLTF dependencies, OBJ + MTL/textures, or FBX external textures; USDZ is already self-contained.
ZIP uploads are limited to 128 MiB compressed, 1,024 entries, 128 MiB per
expanded entry, 256 MiB expanded in total, and a 200:1 per-entry compression
ratio. Zipped GLTF assets may use data: URIs or paths included in the archive;
external asset URLs are rejected.
Default:
false;onModelLoaded
Optional callback fired after the model scene has loaded.
onModelLoaded?: (model: ViewerModelInfo) => void // { bounds, objectCount, materialVariants, materialVariantGroups }onLoadError
Optional callback fired if the model fails to load or render.
onLoadError?: (error: unknown) => void