react-babylonjs 889 (github.com/brianzinn/react-babylonjs (https://github.com/brianzinn/react-babylonjs 889 ), MIT, 4.0.2 of 2 June 2026) is a React 7,897 renderer for Babylon.js 89,603 , maintained by Brian Zinn: lower-case JSX elements such as <box> or <standardMaterial> are generated from Babylon's classes, props set properties, and nesting sets the parent or the material. Version 4 needs React 19 and @babylonjs/core and @babylonjs/gui from 8.32.0 up to, but not including, 10; React 18 projects stay on the 3.x line. MERN Stack Development covers React itself:
import { version } from 'react';
import { createRoot } from 'react-dom/client';
import { Engine, Scene } from 'react-babylonjs';
import { Vector3, Color3 } from '@babylonjs/core';
const BOOKS = [['The Quiet Harbor', '#1f5f8b'], ['Patterns of the Deep Web', '#5b3f99'],
['Salt and Saffron', '#e09a10'], ['Small Steps to Big Summits', '#3f7d3a'],
["The Clockmaker's Paradox", '#b5452f'], ['Gardens in Glass', '#2a9d8f']];
function Shelf({ picked }) { // one mesh per book
return BOOKS.map(([title, hex], i) => (
<box key={title} name={title} width={0.7} height={1.6} depth={0.25}
position={new Vector3((i - 2.5) * 0.9, i === picked ? 0.25 : 0, 0)}>
<standardMaterial name={`cover${i}`} diffuseColor={Color3.FromHexString(hex)}
emissiveColor={i === picked ? new Color3(0.25, 0.25, 0.25) : Color3.Black()} />
</box>
));
}
createRoot(document.getElementById('root')).render(
<Engine antialias adaptToDeviceRatio canvasId="books">
<Scene clearColor={Color3.FromHexString('#eceff1').toColor4()}
onSceneMount={({ scene }) => scene.onAfterRenderObservable.addOnce(() =>
console.log(`${scene.meshes.length} meshes, React ${version}`))}>
<arcRotateCamera name="camera" alpha={-Math.PI / 2} beta={1.3} radius={6}
target={Vector3.Zero()} />
<hemisphericLight name="sky" direction={new Vector3(0.3, 1, -0.6)} />
<Shelf picked={2} />
</Scene>
</Engine>,
);BJS - [19:44:34]: Babylon.js v9.28.0 - WebGL2 - Parallel shader compilation React 19 createContainer API 6 meshes, React 19.3.0

Bundled with esbuild 126 books.jsx --bundle --minify --jsx=automatic, it rendered the six books with Salt and Saffron raised, and the library logged which React API it had found. The cost is size: because react-babylonjs imports from the @babylonjs/core root, the bundle came to 9,165,608 bytes (2,054,709 gzipped), where npm Packages's hand-imported scene took 351,593 gzipped. It is a one-maintainer community project (889 GitHub 29 stars, 3,306 npm 2,036 downloads in the week to 24 September 2026, against 5.05 million for React Three Fiber 32,576 , React Three Fiber and drei), so wrap it where React state drives the scene, such as the details panel, and keep per-frame work in plain Babylon code inside onSceneMount or useBeforeRender.
<!doctype html>
<style>
body { margin: 0; background: #f7f3ec; font: 13px system-ui, sans-serif; color: #333; }
canvas { width: 100%; max-width: 600px; height: 320px; display: block; touch-action: none; outline: none; }
#info { max-width: 600px; margin: 0; padding: 4px 8px; font: 11px monospace; white-space: pre-wrap; }
</style>
<canvas id="view"></canvas>
<pre id="info"></pre>
<script src="https://cdn.jsdelivr.net/npm/babylonjs@9.28.0/babylon.js"></script>
<script>
/*
react-babylonjs renders JSX such as <box name=... position=...><standardMaterial .../></box>
into Babylon objects, and needs React 19 and a bundler (its @babylonjs/core root import made a
2 MB gzipped bundle). This page shows the same idea with a few lines of its own: a component
returns plain element descriptions, and a tiny reconciler turns props into properties,
creating objects once and only updating what changed when state changes.
*/
const BOOKS = [['The Quiet Harbor', '#1f5f8b'], ['Patterns of the Deep Web', '#5b3f99'],
['Salt and Saffron', '#e09a10'], ['Small Steps to Big Summits', '#3f7d3a'],
["The Clockmaker's Paradox", '#b5452f'], ['Gardens in Glass', '#2a9d8f']];
const h = (type, props, ...children) => ({ type, props, children }); // what JSX compiles to
function Shelf({ picked }) { // a component: state in, description out
return BOOKS.map(([title, hex], i) => h('box', { key: title, name: title, width: 0.7, height: 1.6, depth: 0.25,
position: new BABYLON.Vector3((i - 2.5) * 0.9, i === picked ? 0.25 : 0, 0) },
h('standardMaterial', { name: `cover${i}`, diffuseColor: BABYLON.Color3.FromHexString(hex),
emissiveColor: i === picked ? new BABYLON.Color3(0.25, 0.25, 0.25) : BABYLON.Color3.Black() })));
}
const canvas = document.getElementById('view');
const engine = new BABYLON.Engine(canvas, true, { adaptToDeviceRatio: true });
const scene = new BABYLON.Scene(engine);
scene.clearColor = BABYLON.Color3.FromHexString('#eceff1').toColor4();
new BABYLON.ArcRotateCamera('camera', -Math.PI / 2, 1.3, 6, BABYLON.Vector3.Zero(), scene).attachControl(canvas, true);
new BABYLON.HemisphericLight('sky', new BABYLON.Vector3(0.3, 1, -0.6), scene);
const created = new Map(); // key -> Babylon object, kept across renders
let updates = 0;
function reconcile(element, parent) {
const { type, props, children } = element;
let object = created.get(props.key ?? props.name);
if (!object) { // first render: create from the element type
object = type === 'box'
? BABYLON.MeshBuilder.CreateBox(props.name, { width: props.width, height: props.height, depth: props.depth }, scene)
: new BABYLON.StandardMaterial(props.name, scene);
created.set(props.key ?? props.name, object);
}
for (const [key, value] of Object.entries(props)) { // props become properties, only when changed
if (['key', 'width', 'height', 'depth', 'name'].includes(key)) continue;
if (!object[key]?.equals?.(value)) { object[key] = value.clone ? value.clone() : value; updates++; }
}
if (type === 'standardMaterial' && parent) parent.material = object; // nesting sets the material
children.forEach((child) => reconcile(child, object));
object.metadata = { index: BOOKS.findIndex(([title]) => title === props.name) };
}
let state = { picked: 2 };
function render() {
updates = 0;
Shelf(state).forEach((element) => reconcile(element));
document.getElementById('info').textContent = `state { picked: ${state.picked} } -> ${created.size} objects, ` +
`${updates} properties updated\n<box name="${BOOKS[state.picked][0]}" position={...raised} />`;
}
render();
scene.onPointerObservable.add(({ type, pickInfo }) => { // an event handler sets state, React-style
if (type === BABYLON.PointerEventTypes.POINTERTAP && pickInfo?.pickedMesh) {
state = { picked: pickInfo.pickedMesh.metadata.index };
render();
}
});
engine.runRenderLoop(() => scene.render());
addEventListener('resize', () => engine.resize());
</script>