npx skills add ...
npx skills add impertio-studio/three.js-claude-skill-package --skill threejs-impl-drei
Use when using Drei helper components with React Three Fiber: controls, environment maps, text rendering, HTML overlays, or performance helpers. Prevents the common mistake of not wrapping useGLTF in Suspense, using wrong Environment preset name, or forgetting makeDefault on controls. Covers 150+ Drei components across controls, environment, text, materials, loaders, performance. Keywords: Drei, @react-three/drei, OrbitControls, Environment, useGLTF, useTexture, Html, Text, Stage, ContactShadows, Instances, helper components, ready-made, quick setup React 3D.
npx skills add impertio-studio/three.js-claude-skill-package --skill threejs-impl-drei
ALWAYS wrap components that use loader hooks (useGLTF, useTexture, useFBX, useKTX2, useFont) in <Suspense fallback={...}>. Omitting Suspense causes the entire React tree to crash.
ALWAYS add makeDefault to your primary camera controls (<OrbitControls makeDefault />). Without makeDefault, Drei controls do NOT integrate with R3F's event system and pointer events break.
NEVER use an invalid Environment preset name. The ONLY valid presets are: apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse.
NEVER forget to call .preload() for critical assets. Use useGLTF.preload('/model.glb') at module scope to start loading before component mount.
ALWAYS use <Instances> for rendering more than 100 identical meshes. Individual meshes cause one draw call each; instances batch them into one.
The most common camera control. Orbit, zoom, and pan around a target.
| Component | Use Case |
|---|---|
CameraControls | Full-featured camera (recommended for complex scenes) |
MapControls | Top-down map navigation (orbit restricted to vertical axis) |
PresentationControls | Drag-to-rotate with spring physics (product viewers) |
ScrollControls | Scroll-driven animation (pages prop sets scroll length) |
TransformControls | Translate/rotate/scale gizmo on selected objects |
DragControls | Drag objects in 3D space |
KeyboardControls | Keyboard input as React context |
FaceControls | Face-tracking camera movement |
Loads HDR environment maps for realistic reflections and lighting.
Valid presets: apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse.
Complete lighting and shadow setup in one component. Ideal for product viewers.
| Component | Use Case | Key Props |
|---|---|---|
ContactShadows | Soft ground shadows (no light needed) | opacity, scale, blur, far, resolution, color |
AccumulativeShadows | High-quality baked soft shadows | frames, alphaTest, scale, opacity |
RandomizedLight | Child of AccumulativeShadows | amount, radius, intensity, position |
BakeShadows | Bake shadow maps once, stop updating | — |
SoftShadows | PCSS soft shadows for real-time lights | — |
| Component | Purpose | Key Props |
|---|---|---|
Sky | Procedural sky dome | sunPosition, turbidity, rayleigh |
Stars | Particle starfield | radius, count, factor, fade |
Sparkles | Floating particles | count, size, speed, color |
Cloud | Volumetric clouds | opacity, speed, segments, bounds |
High-quality 2D text rendered with signed distance fields via troika-three-text.
Extruded 3D geometry text. Requires a JSON font file (use facetype.js to convert).
Renders DOM elements positioned in 3D space.
ALWAYS faces the camera. Use for labels and sprites.
Renders a heads-up display in a separate orthographic scene.
| Component | Purpose |
|---|---|
MeshReflectorMaterial | Reflective floors with blur, distortion, resolution |
MeshTransmissionMaterial | Glass with chromatic aberration, distortion, thickness |
MeshRefractionMaterial | Refraction using environment cube map |
MeshWobbleMaterial | Animated wobble on MeshStandardMaterial |
MeshDistortMaterial | Perlin noise distortion on MeshStandardMaterial |
MeshDiscardMaterial | Renders nothing (shadow-only objects) |
shaderMaterial | Helper to create custom ShaderMaterial as JSX |
ALWAYS wrap loader-consuming components in <Suspense>.
| Hook | Returns | Preload |
|---|---|---|
useGLTF(url) | { nodes, materials, scene, animations } | useGLTF.preload(url) |
useTexture(url) | THREE.Texture or map object | useTexture.preload(url) |
useFBX(url) | THREE.Group | useFBX.preload(url) |
useKTX2(url) | Compressed texture | useKTX2.preload(url) |
useFont(url) | Font data for Text3D | useFont.preload(url) |
useAnimations(clips, ref) | { actions, names, mixer, ref } | — |
useVideoTexture(url) | THREE.VideoTexture | — |
ALWAYS use for large numbers of identical meshes (>100). Reduces draw calls from N to 1.
Merges different geometries into a single draw call.
| Component | Purpose |
|---|---|
Detailed | LOD — switches geometry based on camera distance |
BakeShadows | Bakes shadows once, stops updating |
AdaptiveDpr | Lowers device pixel ratio during performance drops |
AdaptiveEvents | Reduces event frequency during performance drops |
PerformanceMonitor | Monitors FPS, triggers regression callbacks |
Bvh | BVH-accelerated raycasting for complex meshes |
meshBounds | Fast bounding-box raycasting (replaces per-triangle) |
| Component | Purpose | Key Props |
|---|---|---|
Center | Centers children at origin | top, right, bottom, left, front, back |
Float | Floating hover animation | speed, rotationIntensity, floatIntensity |
Bounds | Auto-fit camera to content | fit, clip, observe, margin |
Resize | Normalizes children to unit size | width, height, depth |
| Component | Purpose |
|---|---|
Edges | Renders wireframe edges |
Outlines | Screen-space outlines |
Trail | Motion trail behind objects |
Decal | Project texture onto mesh surface |
Splat | Gaussian splatting renderer |
Clone | Deep clone with shared geometry/materials |
Image | Texture-mapped plane with shader effects |
MeshPortalMaterial | Portal — renders scene inside mesh surface |
GradientTexture | Procedural gradient texture |
| Component | Purpose |
|---|---|
GizmoHelper | Viewport orientation widget |
PivotControls | Interactive pivot gizmo (translate/rotate/scale) |
TransformControls | Three.js TransformControls wrapper |
Grid | Infinite configurable grid plane |
Helper / useHelper | Visualize light/camera helpers |
| Scenario | Component |
|---|---|
| Product viewer | Stage + OrbitControls + Environment |
| Architectural walkthrough | CameraControls + Environment + ContactShadows |
| Scrolling experience | ScrollControls + useScroll |
| Data visualization | Instances + Html + Billboard |
| Text labels in 3D | Text (2D) or Text3D (extruded) + Billboard |
| Glass/transparent objects | MeshTransmissionMaterial + Environment |
| Reflective floors | MeshReflectorMaterial |
| Large identical meshes | Instances (>100) or Merged (mixed geometries) |
| Model loading | useGLTF + Suspense + .preload() |
| HUD / overlay | Hud or Html with fullscreen |
import { ScrollControls, useScroll } from '@react-three/drei'
<ScrollControls pages={3} damping={0.1}>
<ScrollScene />
</ScrollControls>
function ScrollScene() {
const scroll = useScroll()
useFrame(() => {
const offset = scroll.offset // 0 to 1
})
return <mesh />
}import { Environment } from '@react-three/drei'
// Preset (downloads from polyhaven CDN)
<Environment preset="city" background blur={0.5} />
// Custom HDR file
<Environment files="/env.hdr" background />
// Custom environment with Lightformers
<Environment background>
<Lightformer form="rect" intensity={2} position={[0, 5, -5]} scale={[10, 5, 1]} />
</Environment>import { Stage } from '@react-three/drei'
<Stage preset="rembrandt" intensity={0.5} environment="city" adjustCamera>
<Model />
</Stage><AccumulativeShadows temporal frames={100} scale={10} position={[0, -0.5, 0]}>
<RandomizedLight amount={8} radius={4} position={[5, 5, -10]} />
</AccumulativeShadows>import { Text } from '@react-three/drei'
<Text
fontSize={0.5}
color="white"
anchorX="center"
anchorY="middle"
maxWidth={2}
font="/fonts/Inter-Bold.woff"
>
Hello World
</Text>import { Text3D, Center } from '@react-three/drei'
<Center>
<Text3D font="/fonts/Inter_Bold.json" size={0.75} height={0.2} bevelEnabled bevelSize={0.02}>
Hello
<meshStandardMaterial color="orange" />
</Text3D>
</Center>import { Html } from '@react-three/drei'
<mesh position={[0, 2, 0]}>
<Html
transform // transforms with 3D position
distanceFactor={10} // scales with distance
occlude // hides behind 3D objects
center // centers the HTML element
className="label"
>
<div style={{ color: 'white' }}>Annotation</div>
</Html>
</mesh>import { Billboard, Text } from '@react-three/drei'
<Billboard follow={true} lockX={false} lockY={false} lockZ={false}>
<Text fontSize={0.5}>Always Visible</Text>
</Billboard>import { Hud, OrthographicCamera } from '@react-three/drei'
<Hud renderPriority={1}>
<OrthographicCamera makeDefault position={[0, 0, 5]} />
<Text position={[0, 0, 0]} fontSize={0.1}>HUD Text</Text>
</Hud><mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]}>
<planeGeometry args={[50, 50]} />
<MeshReflectorMaterial
blur={[300, 100]}
resolution={1024}
mixBlur={1}
mixStrength={50}
roughness={1}
depthScale={1.2}
minDepthThreshold={0.4}
maxDepthThreshold={1.4}
color="#151515"
metalness={0.5}
/>
</mesh>import { shaderMaterial } from '@react-three/drei'
import { extend } from '@react-three/fiber'
const WaveMaterial = shaderMaterial(
{ uTime: 0, uColor: new THREE.Color(0.2, 0.0, 0.1) },
/* vertex */ `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,
/* fragment */ `uniform float uTime; uniform vec3 uColor; varying vec2 vUv; void main() { gl_FragColor = vec4(vUv * uColor, 1.0); }`
)
extend({ WaveMaterial })
// Usage: <waveMaterial uTime={clock.elapsedTime} />import { useGLTF } from '@react-three/drei'
function Model(props) {
const { nodes, materials } = useGLTF('/model.glb')
return (
<group {...props}>
<mesh geometry={nodes.Body.geometry} material={materials.Metal} />
</group>
)
}
useGLTF.preload('/model.glb')const textures = useTexture({
map: '/color.jpg',
normalMap: '/normal.jpg',
roughnessMap: '/roughness.jpg',
aoMap: '/ao.jpg',
})
// Spread directly onto material
<meshStandardMaterial {...textures} />function AnimatedModel() {
const group = useRef()
const { nodes, animations } = useGLTF('/character.glb')
const { actions } = useAnimations(animations, group)
useEffect(() => {
actions['Walk']?.play()
return () => actions['Walk']?.stop()
}, [actions])
return <group ref={group}><primitive object={nodes.Scene} /></group>
}import { Instances, Instance } from '@react-three/drei'
<Instances limit={1000} range={1000}>
<boxGeometry />
<meshStandardMaterial />
{positions.map((pos, i) => (
<Instance key={i} position={pos} color="orange" />
))}
</Instances>import { Merged } from '@react-three/drei'
function Furniture({ nodes }) {
return (
<Merged meshes={[nodes.Chair, nodes.Table, nodes.Lamp]}>
{(Chair, Table, Lamp) => (
<>
<Chair position={[0, 0, 0]} />
<Table position={[2, 0, 0]} />
<Lamp position={[1, 1, 0]} />
</>
)}
</Merged>
)
}<PerformanceMonitor
onIncline={() => setDpr(2)}
onDecline={() => setDpr(1)}
flipflops={3}
onFallback={() => setDpr(0.5)}
/>