v10 deployment.Canvas
The Canvas object is where you start to define your React Three Fiber Scene.
import React from 'react'
import { Canvas } from '@react-three/fiber'
const App = () => (
<Canvas>
<pointLight position={[10, 10, 10]} />
<mesh>
<sphereGeometry />
<meshStandardMaterial color="hotpink" />
</mesh>
</Canvas>
)
Properties
Defaults
Canvas uses createRoot which will create a translucent THREE.WebGLRenderer with the following constructor args:
- antialias=true
- alpha=true
- powerPreference="high-performance"
and with the following properties:
- outputColorSpace = THREE.SRGBColorSpace
- toneMapping = THREE.ACESFilmicToneMapping
It will also create the following scene internals:
- A
THREE.Perspectivecamera - A
THREE.Orthographiccam iforthographicis true - A
THREE.PCFShadowMapifrenderer={{ shadows: true }}(orgl={{ shadows: true }}) - A
THREE.Scene(into which all the JSX is rendered) and aTHREE.Raycaster
In recent versions of threejs, THREE.ColorManagement.enabled will be set to true to enable automatic conversion of colors according to the renderer's configured color space. R3F will handle texture color space conversion. For more on this topic, see https://threejs.org/manual/#en/color-management.
Errors and fallbacks
On some systems WebGL may not be supported, you can provide a fallback component that will be rendered instead of the canvas:
<Canvas fallback={<div>Sorry no WebGL supported!</div>}>
<mesh />
</Canvas>
You should also safeguard the canvas against WebGL context crashes, for instance if users have the GPU disabled or GPU drivers are faulty.
import { useErrorBoundary } from 'use-error-boundary'
function App() {
const { ErrorBoundary, didCatch, error } = useErrorBoundary()
return didCatch ? (
<div>{error.message}</div>
) : (
<ErrorBoundary>
<Canvas>
<mesh />
</Canvas>
</ErrorBoundary>
)
}
Ideally, and if possible, your fallback is a seamless, visual replacement for what the canvas would have otherwise rendered.
Canvas Background
The background prop sets the scene background (and, for environment maps, image-based lighting) declaratively — replacing manual <color attach="background"> or a separate Environment component for simple cases.
// Color (CSS color names, hex strings, rgb(), or a hex number)
<Canvas background="red" />
<Canvas background="#1a1a2e" />
<Canvas background={0xff0000} />
// Drei environment preset (apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse)
<Canvas background="sunset" />
// URL to an HDR/EXR/image file: .hdr, .exr, .jpg (Ultra HDR or plain), .png, .webp, .avif, .gif
<Canvas background="/env.hdr" />
<Canvas background="./sky.jpg" />
// Six cube faces (+x, -x, +y, -y, +z, -z); `.hdr` faces load as an HDR cubemap
<Canvas background={{ files: ['px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr'], path: '/pisa/' }} />
When a string is given it is resolved in this order: exact preset name → URL (starts with /, ./, ../, http://, https://, data:, or ends in .hdr/.exr/.jpg/.jpeg/.png/.webp/.avif/.gif, optionally followed by a ?query or #hash) → otherwise a CSS color. So "city" is a preset, "./city.jpg" is a URL, and "cyan" is a color.
HDR files (.hdr, .exr, an Ultra HDR .jpg) also light the scene with linear radiance. Any other image is an sRGB equirect: it works as a backdrop and as a (low dynamic range) environment.
For separate backdrop and environment maps, pass an object:
<Canvas
background={{
preset: 'city', // or files: '/env.hdr'
backgroundMap: '/sky.jpg', // optional separate visual backdrop; on its own, it sets only the backdrop
backgroundBlurriness: 0.5,
backgroundIntensity: 1,
environmentIntensity: 1.2,
path: '/textures/',
}}
/>
The underlying Environment component is also exported from @react-three/fiber for direct use.
Canvas Size Control
By default the canvas auto-sizes to its container. The width/height props pin the drawing-buffer resolution instead — independent of CSS size — which is useful for fixed-resolution capture such as video export.
// Fixed resolution regardless of container size
<Canvas width={1920} height={1080}>
<Scene />
</Canvas>
// Partial: width pinned, height from container
<Canvas width={800}>
<Scene />
</Canvas>
// 4K capture (set dpr={1} for exact pixel dimensions)
<Canvas width={3840} height={2160} dpr={1}>
<Scene />
</Canvas>
Size follows an ownership model: props (or container fallback) drive size until you call setSize() imperatively, which takes ownership; call setSize() with no arguments to release back to props/container.
const { setSize } = useThree()
setSize(3840, 2160) // imperative mode, 4K
setSize(1024) // 1024×1024 square
setSize() // reset to props/container
width/height are logical pixels; DPR multiplies as usual. Set dpr={1} for exact pixel dimensions. Use forceEven to round dimensions down to even numbers for video encoders.
Pixel ratio
dpr is either a fixed number or a [min, max] range (the default is [1, 2]). A range clamps window.devicePixelRatio into it, and keeps following it: when the window moves to a display with a different pixel ratio, or the user zooms the page, R3F re-resolves the range and resizes the drawing buffer. It listens with matchMedia, since neither change resizes the canvas element. A fixed number never follows the display.
<Canvas dpr={[1, 2]} /> // follows the display, clamped between 1 and 2
<Canvas dpr={1.5} /> // always 1.5
A value set imperatively with setDpr() wins until the dpr prop itself changes. During a WebXR session the change waits until the session ends, since three.js manages the pixel ratio while presenting.
Renderer settings
Renderer-wide settings go into the renderer prop (WebGPU, @react-three/fiber) or the gl prop (WebGL, @react-three/fiber/legacy): constructor parameters (antialias, forceWebGL, depth, ...), renderer properties (toneMapping, outputColorSpace, ...), and two settings R3F applies itself:
shadows:trueforTHREE.PCFShadowMap, one of'basic','percentage','soft','variance', or props assigned torenderer.shadowMap.'soft'is kept only as a deprecated alias of'percentage': both useTHREE.PCFShadowMap(three.js deprecatedPCFSoftShadowMapand madePCFShadowMapsoft), and'soft'logs a deprecation notice. Defaultfalse. Applied to a renderer R3F builds; a renderer instance or factory keeps theshadowMapyou give it.textureColorSpace: the color space of 8-bit input textures loaded in this canvas.
<Canvas renderer={{ shadows: 'variance', toneMapping: THREE.AgXToneMapping }} />
These configure a renderer the canvas owns. A canvas sharing a <Canvas primary>'s renderer ignores them (with a development warning) because the primary owns the renderer; textureColorSpace, which is per canvas, still applies. Constructor parameters are read once, when the renderer is built: changing them later has no effect.
Color settings
Renderer color options — textureColorSpace, outputColorSpace, toneMapping:
// WebGL (@react-three/fiber/legacy)
<Canvas gl={{ outputColorSpace: THREE.LinearSRGBColorSpace, toneMapping: THREE.NoToneMapping, textureColorSpace: THREE.LinearSRGBColorSpace }} />
// WebGPU
<Canvas renderer={{ outputColorSpace: THREE.LinearSRGBColorSpace, toneMapping: THREE.NoToneMapping, textureColorSpace: THREE.LinearSRGBColorSpace }} />
Defaults: outputColorSpace = THREE.SRGBColorSpace, toneMapping = THREE.ACESFilmicToneMapping, textureColorSpace = THREE.SRGBColorSpace.
WebGPU and WebGL
<Canvas> from @react-three/fiber renders with three's WebGPURenderer, which needs async initialization; R3F handles init() for you — no manual await renderer.init(). Browser WebGPU support is not required: WebGPURenderer falls back to a WebGL2 backend automatically. Node materials are available as JSX:
import { Canvas } from '@react-three/fiber'
export default () => (
<Canvas>
<mesh>
<meshBasicNodeMaterial />
<boxGeometry />
</mesh>
</Canvas>
)
renderer takes a config bag, a renderer instance, or a sync/async callback. R3F disposes a renderer it built from a config bag or a callback once the last Canvas using it unmounts; an instance you pass in is yours to dispose. Three's WebGPU renderer is a separate chunk, fetched when the first <Canvas> mounts. For node materials, TSL hooks and multi-canvas, see the WebGPU section.
WebGLRenderer is on @react-three/fiber/legacy. Use it for GLSL ShaderMaterials, onBeforeCompile and libraries that need WebGLRenderer: WebGPURenderer cannot compile GLSL, and R3F warns in development when a GLSL material reaches a WebGPU canvas.
import { Canvas } from '@react-three/fiber/legacy'
export default () => <Canvas gl={{ antialias: false }}>{/* ... */}</Canvas>
View transitions
React 19.3's <ViewTransition> can wrap elements inside a Canvas. React hands a commit that touches a <ViewTransition> to the renderer, and R3F applies it synchronously, the same way react-dom does in browsers without document.startViewTransition. Nothing is animated: the scene is not a DOM snapshot, so enter, exit and update animations don't play on 3D content, but the update commits and effects run as usual. Gesture transitions are not supported.
import { ViewTransition } from 'react'
<Canvas>
<ViewTransition enter="fade">{shown && <Model />}</ViewTransition>
</Canvas>
Custom Canvas
R3F can render to a root, similar to how react-dom and all the other React renderers work. This allows you to shave off react-dom (~40kb), react-use-measure (~3kb) and, if you don't need them, pointer-events (~7kb) (you need to explicitly import events and add them to the config otherwise).
Roots have the same options and properties as Canvas, but you are responsible for resizing it. It requires an existing DOM <canvas> object into which it renders.
CreateRoot
Creates a root targeting a canvas, rendering JSX.
import { createRoot, events } from '@react-three/fiber'
// Create a react root. Three's classes are JSX elements on it without any extend() call:
// the root resolves element names against the namespace of the renderer it loads.
const root = createRoot(document.querySelector('canvas'))
// Keep the whole configuration in one object: each configure() call replaces the previous one
const config = { events, camera: { position: [0, 0, 50] } }
async function app() {
// Configure the root, inject events optionally, set camera, etc
// This *must* be called before render, and it must be awaited
await root.configure(config)
// createRoot by design is not responsive, you have to take care of resize yourself.
// Pass the same config again with the new size, not just the size on its own
window.addEventListener('resize', () => {
root.configure({ ...config, size: { width: window.innerWidth, height: window.innerHeight } })
})
// Trigger resize
window.dispatchEvent(new Event('resize'))
// Render entry point
root.render(<App />)
// Unmount and dispose of memory
// root.unmount()
}
app()
configure() replaces the root's configuration rather than merging into it, the same way <Canvas> passes its full set of props every time it renders. A prop left out of a later call goes back to its default: calling root.configure({ size }) on its own drops the renderer or gl settings (turning shadows off) and resets frameloop to 'always' and dpr to [1, 2]. So keep one config object and spread it into every call, changing only what's new.
The camera, the scene and the events manager are the exception: the camera and scene are created by the first call, the events manager by the first call that passes events, and all three are kept after that. Leaving camera out of a later call does not reset the camera, and camera props passed to a later call are not re-applied. To move the camera after the first configure(), change it from inside the tree (useThree) or through the store root.render() returns.
Tree-shaking
What a bundle carries is decided by the renderer, not by which elements you use. Fiber's core imports nothing from three; a root loads one renderer's support module (three for WebGL, three/webgpu for WebGPU) when it mounts, and that module carries the whole namespace of its flavour. So an app on @react-three/fiber ships no three code eagerly, downloads only the renderer its Canvas asks for, and never both. Three's own classes are not tree-shaken below that: <mesh> and <boxGeometry> resolve against the loaded namespace, and an extend({ Mesh, BoxGeometry }) call does not remove anything from it.
extend is for classes three does not ship, such as addons and your own subclasses:
import { extend, createRoot } from '@react-three/fiber'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
extend({ OrbitControls })
createRoot(canvas).render(
<>
<orbitControls args={[camera, domElement]} />
<mesh>
<boxGeometry />
<meshStandardMaterial />
</mesh>
</>,
)
The /legacy and /webgpu entries import their renderer statically instead, for apps that would rather skip the extra request. See Entry Points for the comparison.