Three.js, written as React components. R3F lets you build 3D scenes declaratively with JSX — <mesh>, <boxGeometry>, <meshStandardMaterial> — and React keeps them in sync.
Click a shape to make it grow, and drag to orbit. In R3F this whole scene is JSX; here it's rendered with raw Three.js so it runs without a build step — but the behaviour is identical.
[]// The same scene as real React Three Fiber JSX import { Canvas, useFrame } from '@react-three/fiber' import { OrbitControls } from '@react-three/drei' function Box({ position }) { const ref = useRef() const [big, setBig] = useState(false) useFrame(() => (ref.current.rotation.y += 0.01)) return ( <mesh ref={ref} scale={big ? 1.5 : 1} position={position} onClick={() => setBig(!big)}> <boxGeometry args={[1,1,1]} /> <meshStandardMaterial color="#6d4dff" /> </mesh> ) } export default () => ( <Canvas camera={{ position: [0,0,8] }}> <ambientLight /> <directionalLight position={[3,4,5]} /> {positions.map((p,i) => <Box key={i} position={p} />)} <OrbitControls /> </Canvas> )
In plain Three.js you imperatively create objects and push them into a scene. R3F flips that: you describe the scene as a component tree, and React's reconciler creates, updates and disposes the underlying Three.js objects for you. State, props and hooks like useFrame work exactly as in any React app — so a 3D scene composes like the rest of your UI.
R3F auto-generates a JSX element for every Three.js class: THREE.Mesh → <mesh>, THREE.BoxGeometry → <boxGeometry>, THREE.PointLight → <pointLight>. Constructor arguments go in the args prop; any property is a prop. That's the whole mental model.