three is the foundational WebGL library that provides low-level access to graphics primitives like scenes, cameras, and geometries. react-three-fiber is a React renderer for three, allowing developers to build 3D scenes using declarative React components instead of imperative code. @react-three/drei is a collection of useful helpers and common abstractions built on top of react-three-fiber, offering ready-to-use components for controls, environments, and interactions to speed up development.
Building 3D experiences on the web usually involves one of three layers: the core engine, the React renderer, or a helper library. three gives you raw power, react-three-fiber connects that power to React, and @react-three/drei provides the common tools you need to finish the job. Let's compare how they handle everyday tasks.
three requires you to manually create the scene, camera, and renderer, then append the canvas to the DOM.
// three: Manual setup
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
react-three-fiber wraps the setup in a <Canvas> component.
// react-three-fiber: Declarative setup
import { Canvas } from '@react-three/fiber';
function App() {
return (
<Canvas>
{/* Scene content goes here */}
</Canvas>
);
}
@react-three/drei builds on the <Canvas> from react-three-fiber.
// @react-three/drei: Enhanced setup
import { Canvas } from '@react-three/fiber';
import { Stars } from '@react-three/drei';
function App() {
return (
<Canvas>
<Stars /> {/* Instant background */}
</Canvas>
);
}
three uses constructor classes for geometries and materials.
// three: Imperative objects
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandardMaterial({ color: 'orange' });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
react-three-fiber maps three.js classes to JSX elements.
<mesh> and <boxGeometry>.// react-three-fiber: JSX primitives
function Box() {
return (
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
);
}
@react-three/drei provides high-level components that wrap primitives.
<Box> include geometry and material in one tag.// @react-three/drei: High-level components
import { Box } from '@react-three/drei';
function Box() {
return <Box args={[1, 1, 1]} color="orange" />;
}
three relies on the standard requestAnimationFrame loop.
// three: Manual loop
function animate() {
requestAnimationFrame(animate);
mesh.rotation.x += 0.01;
renderer.render(scene, camera);
}
animate();
react-three-fiber provides the useFrame hook.
// react-three-fiber: useFrame hook
import { useFrame } from '@react-three/fiber';
import { useRef } from 'react';
function RotatingBox() {
const ref = useRef();
useFrame((state, delta) => {
ref.current.rotation.x += delta;
});
return <mesh ref={ref}><boxGeometry /></mesh>;
}
@react-three/drei offers declarative animation components.
<Float> animate objects without writing loop logic.// @react-three/drei: Declarative animation
import { Float } from '@react-three/drei';
function FloatingBox() {
return (
<Float speed={2} rotationIntensity={1}>
<Box args={[1, 1, 1]} color="orange" />
</Float>
);
}
three requires importing controls and wiring them to the camera and DOM.
// three: Manual controls
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
const controls = new OrbitControls(camera, renderer.domElement);
function animate() {
requestAnimationFrame(animate);
controls.update(); // Must call update
renderer.render(scene, camera);
}
react-three-fiber allows manual control integration via hooks.
useThree.// react-three-fiber: Manual integration
import { useThree } from '@react-three/fiber';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { useEffect } from 'react';
function Controls() {
const { camera, gl } = useThree();
useEffect(() => {
const controls = new OrbitControls(camera, gl.domElement);
return () => controls.dispose();
}, [camera, gl]);
return null;
}
@react-three/drei exports ready-to-use control components.
<OrbitControls /> into the scene.// @react-three/drei: Ready-made controls
import { OrbitControls } from '@react-three/drei';
function Scene() {
return (
<>
<OrbitControls makeDefault />
<Box />
</>
);
}
While these packages serve different roles, they share a tight connection.
react-three-fiber and @react-three/drei both rely on three.three object can be used within the React ecosystem.// All three can access core THREE objects
import * as THREE from 'three';
const color = new THREE.Color('red');
react-three-fiber and @react-three/drei react to prop changes.three requires manual setting of properties.// react-three-fiber / drei
<meshStandardMaterial color={isActive ? 'blue' : 'red'} />
// three
material.color.set(isActive ? 'blue' : 'red');
three code inside React components when needed.// react-three-fiber: Mixing imperative code
useFrame((state) => {
mesh.current.material.uniforms.time.value = state.clock.elapsedTime;
});
| Feature | three | react-three-fiber | @react-three/drei |
|---|---|---|---|
| Style | ⚙️ Imperative JS | ⚛️ Declarative JSX | 🧩 Pre-built Components |
| Setup | 📝 Manual Boilerplate | 📦 <Canvas> Wrapper | 🚀 Helpers inside Canvas |
| Animation | ⏱️ requestAnimationFrame | 🪝 useFrame Hook | 🎈 <Float> Components |
| Controls | 🔌 Manual Wiring | 🪝 Hook Integration | 🎮 <OrbitControls /> |
| Best For | 🎮 Custom Engines | 🌐 React Apps | ⚡ Rapid Prototyping |
three is the engine under the hood — it powers everything but requires you to build the car yourself. Use it for maximum control or non-React projects.
react-three-fiber is the steering wheel and dashboard — it lets you drive the engine using React patterns. Use it to integrate 3D into your modern web app.
@react-three/drei is the GPS and comfort features — it gives you navigation and luxury tools so you don't have to build them. Use it to ship faster with standard features.
Final Thought: You rarely choose just one. Most professional React 3D apps use all three together — three for the core types, react-three-fiber for the scene graph, and @react-three/drei for the heavy lifting.
Choose @react-three/drei if you are already using react-three-fiber and want to avoid reinventing common wheels like camera controls, loading states, or complex materials. It is the standard utility belt for production-ready React 3D apps, saving time on boilerplate while maintaining flexibility.
Choose react-three-fiber if you are working within a React application and want to manage your 3D scene graph using components and hooks. It is ideal when you need to sync 3D state with DOM state, leverage React's ecosystem for state management, or prefer declarative syntax over imperative boilerplate.
Choose three if you are building a non-React application or need complete low-level control over the WebGL rendering pipeline without the overhead of a React reconciliation layer. It is the best fit for custom game engines, heavy data visualizations, or projects where React is not part of the stack.
A growing collection of useful helpers and fully functional, ready-made abstractions for @react-three/fiber.
If you make a component that is generic enough to be useful to others, think about CONTRIBUTING!
npm install @react-three/drei
[!IMPORTANT] this package is using the stand-alone
three-stdlibinstead ofthree/examples/jsm.
import { PerspectiveCamera, PositionalAudio, ... } from '@react-three/drei'
import { PerspectiveCamera, PositionalAudio, ... } from '@react-three/drei/native'
The native route of the library does not export Html or Loader. The default export of the library is web which does export Html and Loader.
[!WARNING] Below is an archive of the anchors links with their new respective locations to the documentation website. Do not update the links below, they are for reference only.
Pre-requisites:
$ nvm install
$ nvm use
$ node -v # make sure your version satisfies package.json#engines.node
nb: if you want this node version to be your default nvm's one: nvm alias default node$ corepack enable
$ corepack prepare --activate # it reads "packageManager"
$ yarn -v # make sure your version satisfies package.json#engines.yarn
$ yarn install
Pre-requisites:
$ npx playwright install
To run visual tests locally:
$ yarn build
$ yarn test
To update a snapshot:
$ PLAYWRIGHT_UPDATE_SNAPSHOTS=1 yarn test
[!IMPORTANT] Snapshots are system-dependent, so to run playwright in the same environment as the CI:
$ docker run --init --rm \
-v $(pwd):/app -w /app \
ghcr.io/pmndrs/playwright:drei \
sh -c "corepack enable && yarn install && yarn build && yarn test"
To update a snapshot:
$ docker run --init --rm \
-v $(pwd):/app -w /app \
-e PLAYWRIGHT_UPDATE_SNAPSHOTS=1 \
ghcr.io/pmndrs/playwright:drei \
sh -c "corepack enable && yarn install && yarn build && yarn test"