
R3f Fundamentals
- 107 installs
- 8 repo stars
- Updated August 4, 2026
- bbeierle12/skill-mcp-claude
Embed interactive 3D scenes in React apps using React Three Fiber primitives, cameras, lights, and responsive canvas layouts.
About
Covers React Three Fiber essentials for mounting Three.js scenes in React, including canvases, meshes, lighting, controls, and patterns for interactive 3D product viewers, games, and marketing experiences.
- Scene, camera, and light setup
- JSX-driven Three.js objects
- Resize and DPR handling
- React state tied to 3D props
R3f Fundamentals by the numbers
- 107 all-time installs (skills.sh)
- Ranked #1,036 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/bbeierle12/skill-mcp-claude --skill r3f-fundamentalsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 107 |
|---|---|
| repo stars | ★ 8 |
| Last updated | August 4, 2026 |
| Repository | bbeierle12/skill-mcp-claude ↗ |
What it does
Embed interactive 3D scenes in React apps using React Three Fiber primitives, cameras, lights, and responsive canvas layouts.
Files
React Three Fiber Fundamentals
Declarative Three.js via React components. R3F maps Three.js objects to JSX elements with automatic disposal, reactive updates, and React lifecycle integration.
Quick Start
import { Canvas } from '@react-three/fiber';
function App() {
return (
<Canvas>
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} />
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="hotpink" />
</mesh>
</Canvas>
);
}Core Principle: Declarative Scene Graph
R3F converts Three.js imperative API to React's declarative model:
| Three.js (Imperative) | R3F (Declarative) |
|---|---|
new THREE.Mesh() | <mesh> |
mesh.position.set(1, 2, 3) | <mesh position={[1, 2, 3]}> |
scene.add(mesh) | JSX nesting handles hierarchy |
mesh.geometry.dispose() | Automatic on unmount |
Canvas Configuration
import { Canvas } from '@react-three/fiber';
<Canvas
// Renderer settings
gl={{ antialias: true, alpha: false, powerPreference: 'high-performance' }}
dpr={[1, 2]} // Device pixel ratio range
shadows // Enable shadow maps
// Camera (default: PerspectiveCamera)
camera={{
fov: 75,
near: 0.1,
far: 1000,
position: [0, 0, 5]
}}
// Or use orthographic
orthographic
camera={{ zoom: 50, position: [0, 0, 100] }}
// Performance
frameloop="demand" // 'always' | 'demand' | 'never'
performance={{ min: 0.5 }} // Adaptive performance
// Events
onCreated={({ gl, scene, camera }) => {
// Access Three.js objects after mount
}}
// Sizing
style={{ width: '100vw', height: '100vh' }}
/>Frameloop Modes
| Mode | When to Use |
|---|---|
always | Continuous animation (games, simulations) |
demand | Static scenes, only re-render on state change |
never | Manual control via invalidate() |
// Demand mode with manual invalidation
import { useThree } from '@react-three/fiber';
function Controls() {
const invalidate = useThree(state => state.invalidate);
const handleDrag = () => {
// Update state...
invalidate(); // Request re-render
};
}Scene Hierarchy
JSX nesting = Three.js parent-child relationships:
<group position={[0, 2, 0]} rotation={[0, Math.PI / 4, 0]}>
{/* Children inherit parent transforms */}
<mesh position={[1, 0, 0]}>
<sphereGeometry args={[0.5, 32, 32]} />
<meshStandardMaterial color="blue" />
</mesh>
<mesh position={[-1, 0, 0]}>
<boxGeometry args={[0.8, 0.8, 0.8]} />
<meshStandardMaterial color="red" />
</mesh>
</group>Common Container Components
// Group: Transform container (no rendering)
<group position={[0, 0, 0]} />
// Object3D: Base class, rarely used directly
<object3D />
// Scene: Usually implicit (Canvas creates one)
<scene />Camera Systems
Default Perspective Camera
<Canvas camera={{
fov: 75, // Field of view (degrees)
aspect: width/height, // Auto-calculated
near: 0.1, // Near clipping plane
far: 1000, // Far clipping plane
position: [0, 5, 10]
}} />Custom Camera Component
import { PerspectiveCamera } from '@react-three/drei';
function Scene() {
return (
<>
<PerspectiveCamera
makeDefault // Set as active camera
fov={60}
position={[0, 2, 8]}
/>
{/* Scene contents */}
</>
);
}Camera Access
import { useThree } from '@react-three/fiber';
function CameraController() {
const { camera } = useThree();
useEffect(() => {
camera.lookAt(0, 0, 0);
}, [camera]);
return null;
}Lighting
Light Types
// Ambient: Uniform, directionless
<ambientLight intensity={0.4} color="#ffffff" />
// Directional: Sun-like, parallel rays
<directionalLight
position={[5, 10, 5]}
intensity={1}
castShadow
/>
// Point: Radiates from position
<pointLight
position={[0, 5, 0]}
intensity={1}
distance={20} // Range (0 = infinite)
decay={2} // Physical falloff
/>
// Spot: Cone-shaped
<spotLight
position={[0, 10, 0]}
angle={Math.PI / 6} // Cone angle
penumbra={0.5} // Edge softness
castShadow
/>
// Hemisphere: Sky/ground gradient
<hemisphereLight
skyColor="#87ceeb"
groundColor="#362907"
intensity={0.6}
/>Shadow Setup
<Canvas shadows>
<directionalLight
castShadow
position={[10, 10, 10]}
shadow-mapSize={[2048, 2048]}
shadow-camera-far={50}
shadow-camera-left={-10}
shadow-camera-right={10}
shadow-camera-top={10}
shadow-camera-bottom={-10}
/>
<mesh castShadow>
<boxGeometry />
<meshStandardMaterial />
</mesh>
<mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]} position={[0, -1, 0]}>
<planeGeometry args={[20, 20]} />
<meshStandardMaterial />
</mesh>
</Canvas>Render Loop (useFrame)
useFrame runs every frame (60fps target). This is where animation happens.
import { useFrame, useThree } from '@react-three/fiber';
import { useRef } from 'react';
function RotatingBox() {
const meshRef = useRef<THREE.Mesh>(null!);
useFrame((state, delta) => {
// state: R3F state (camera, scene, clock, etc.)
// delta: Time since last frame (seconds)
meshRef.current.rotation.x += delta;
meshRef.current.rotation.y += delta * 0.5;
});
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshNormalMaterial />
</mesh>
);
}useFrame State Object
useFrame((state) => {
state.clock // THREE.Clock
state.clock.elapsedTime // Total time (seconds)
state.camera // Active camera
state.scene // Scene object
state.gl // WebGLRenderer
state.size // { width, height }
state.viewport // { width, height, factor, distance }
state.mouse // Normalized mouse position [-1, 1]
state.raycaster // THREE.Raycaster
});Render Priority
// Lower priority runs first, higher runs later
// Default is 0
useFrame(() => {
// Update physics
}, -1); // Runs before default
useFrame(() => {
// Update visuals
}, 0); // Default
useFrame(() => {
// Post-processing / camera
}, 1); // Runs after defaultAccessing Three.js Objects
useThree Hook
import { useThree } from '@react-three/fiber';
function SceneInfo() {
const {
gl, // WebGLRenderer
scene, // THREE.Scene
camera, // Active camera
size, // Canvas dimensions
viewport, // Viewport in Three.js units
clock, // THREE.Clock
set, // Update state
get, // Get current state
invalidate, // Request re-render (demand mode)
advance, // Advance one frame (never mode)
} = useThree();
return null;
}Refs for Direct Access
import { useRef } from 'react';
import * as THREE from 'three';
function DirectAccess() {
const meshRef = useRef<THREE.Mesh>(null!);
const materialRef = useRef<THREE.MeshStandardMaterial>(null!);
useEffect(() => {
// Direct Three.js API access
meshRef.current.geometry.computeBoundingBox();
materialRef.current.needsUpdate = true;
}, []);
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial ref={materialRef} />
</mesh>
);
}Events
R3F provides pointer events on meshes:
<mesh
onClick={(e) => console.log('click', e.point)}
onContextMenu={(e) => console.log('right click')}
onDoubleClick={(e) => console.log('double click')}
onPointerOver={(e) => console.log('hover')}
onPointerOut={(e) => console.log('unhover')}
onPointerDown={(e) => console.log('down')}
onPointerUp={(e) => console.log('up')}
onPointerMove={(e) => console.log('move')}
>
<boxGeometry />
<meshStandardMaterial />
</mesh>Event Object
onClick={(e) => {
e.stopPropagation(); // Stop event bubbling
e.point // THREE.Vector3 intersection point
e.distance // Distance from camera
e.object // Intersected object
e.face // Intersected face
e.faceIndex // Face index
e.uv // UV coordinates
e.camera // Camera used for raycasting
e.delta // Distance from last event
}}Suspense & Loading
R3F integrates with React Suspense for async loading:
import { Suspense } from 'react';
import { useLoader } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
function Model() {
const gltf = useLoader(GLTFLoader, '/model.glb');
return <primitive object={gltf.scene} />;
}
function App() {
return (
<Canvas>
<Suspense fallback={<LoadingSpinner />}>
<Model />
</Suspense>
</Canvas>
);
}
function LoadingSpinner() {
const meshRef = useRef<THREE.Mesh>(null!);
useFrame((_, delta) => {
meshRef.current.rotation.z += delta * 2;
});
return (
<mesh ref={meshRef}>
<torusGeometry args={[1, 0.2, 16, 32]} />
<meshBasicMaterial color="white" wireframe />
</mesh>
);
}Dependencies
{
"dependencies": {
"@react-three/fiber": "^8.15.0",
"three": "^0.160.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/three": "^0.160.0"
}
}File Structure
r3f-fundamentals/
├── SKILL.md
├── references/
│ ├── canvas-props.md # Complete Canvas prop reference
│ ├── hooks-api.md # useThree, useFrame, useLoader
│ └── event-system.md # Event handling deep-dive
└── scripts/
├── templates/
│ ├── basic-scene.tsx # Minimal starter
│ ├── lit-scene.tsx # With proper lighting
│ └── interactive.tsx # With events and animation
└── utils/
└── canvas-config.ts # Preset configurationsReference
references/canvas-props.md— Complete Canvas configuration optionsreferences/hooks-api.md— All R3F hooks with examplesreferences/event-system.md— Pointer events and raycasting
{
"name": "r3f-fundamentals",
"description": "React Three Fiber core setup, Canvas configuration, scene hierarchy, camera systems, lighting, render loop, and React integration patterns. Use when setting up a new R3F project, configuring the Canvas component, managing scene structure, or understanding the declarative Three.js-in-React paradigm. The foundational skill that all other R3F skills depend on.",
"tags": [
"3d",
"r3f",
"react",
"code-generation"
],
"sub_skills": [],
"source": "claude-user",
"type": "template",
"depends_on": [],
"enhances": [
"r3f-geometry",
"r3f-materials",
"r3f-drei",
"r3f-performance"
],
"last_reviewed_at": null,
"review_score": null,
"relevance_tier": null
}
R3F Hooks API Reference
Complete reference for React Three Fiber hooks.
useThree
Access R3F state from any component inside Canvas:
import { useThree } from '@react-three/fiber';
function Component() {
const state = useThree();
// or destructure specific values
const { camera, gl, scene } = useThree();
}State Properties
| Property | Type | Description |
|---|---|---|
gl | WebGLRenderer | The WebGL renderer |
scene | Scene | The scene object |
camera | Camera | Current active camera |
raycaster | Raycaster | For pointer events |
pointer | Vector2 | Normalized pointer position |
mouse | Vector2 | Alias for pointer |
clock | Clock | Three.js clock |
size | { width, height, top, left } | Canvas size in pixels |
viewport | { width, height, factor, distance, aspect } | Viewport in Three.js units |
aspect | number | Canvas aspect ratio |
set | function | Update state |
get | function | Get current state |
invalidate | function | Request re-render (demand mode) |
advance | function | Advance one frame (never mode) |
setSize | function | Resize canvas |
setDpr | function | Change device pixel ratio |
setFrameloop | function | Change frameloop mode |
setEvents | function | Update event settings |
onPointerMissed | function | Global pointer miss handler |
events | object | Event handlers state |
xr | object | WebXR state |
performance | object | Adaptive performance state |
Selector Pattern (Performance)
Only subscribe to specific state slices:
// Re-renders only when camera changes
const camera = useThree((state) => state.camera);
// Re-renders only when size changes
const { width, height } = useThree((state) => state.size);
// Multiple values with shallow compare
const [camera, gl] = useThree((state) => [state.camera, state.gl]);Viewport Calculations
const { viewport, camera } = useThree();
// Convert screen pixels to Three.js units
const unitsPerPixel = viewport.factor;
// Viewport dimensions at specific Z distance
const atZ = (z: number) => {
const distance = camera.position.z - z;
const fov = (camera.fov * Math.PI) / 180;
const height = 2 * Math.tan(fov / 2) * distance;
const width = height * viewport.aspect;
return { width, height };
};useFrame
Run code on every frame (render loop):
import { useFrame } from '@react-three/fiber';
useFrame((state, delta, frame) => {
// state: Same as useThree
// delta: Time since last frame (seconds)
// frame: XRFrame for WebXR
});Animation Patterns
// Rotate mesh
const meshRef = useRef<THREE.Mesh>(null!);
useFrame((_, delta) => {
meshRef.current.rotation.y += delta;
});
// Smooth follow
useFrame((state) => {
meshRef.current.position.lerp(targetPosition, 0.1);
});
// Oscillation
useFrame(({ clock }) => {
meshRef.current.position.y = Math.sin(clock.elapsedTime) * 2;
});
// Camera tracking
useFrame(({ camera }) => {
camera.lookAt(targetRef.current.position);
});Render Priority
Control execution order with priority argument:
// Physics update (runs first)
useFrame(() => {
updatePhysics();
}, -1);
// Visual updates (default priority)
useFrame(() => {
updateMeshes();
}, 0);
// Camera/post-processing (runs last)
useFrame(() => {
updateCamera();
}, 1);Conditional Rendering
// Skip frame logic when not needed
const [active, setActive] = useState(true);
useFrame(() => {
if (!active) return;
// ... animation logic
});useLoader
Load assets with Suspense integration:
import { useLoader } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { TextureLoader } from 'three';
// Single asset
const gltf = useLoader(GLTFLoader, '/model.glb');
const texture = useLoader(TextureLoader, '/texture.jpg');
// Multiple assets
const [model1, model2] = useLoader(GLTFLoader, ['/a.glb', '/b.glb']);
// With extensions (e.g., Draco)
const gltf = useLoader(
GLTFLoader,
'/model.glb',
(loader) => {
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/');
loader.setDRACOLoader(dracoLoader);
}
);Preloading
// Preload outside component
useLoader.preload(GLTFLoader, '/model.glb');
// Or use drei's Preload component
import { Preload } from '@react-three/drei';
<Canvas>
<Suspense fallback={null}>
<Scene />
<Preload all />
</Suspense>
</Canvas>useGraph
Traverse and extract objects from loaded scenes:
import { useGraph } from '@react-three/fiber';
import { useGLTF } from '@react-three/drei';
function Model() {
const { scene } = useGLTF('/model.glb');
const { nodes, materials } = useGraph(scene);
// Access specific meshes by name
return (
<>
<mesh geometry={nodes.Body.geometry} material={materials.Metal} />
<mesh geometry={nodes.Wheel.geometry} material={materials.Rubber} />
</>
);
}Custom Hooks Patterns
useAnimatedValue
function useAnimatedValue(target: number, speed = 0.1) {
const valueRef = useRef(target);
useFrame(() => {
valueRef.current += (target - valueRef.current) * speed;
});
return valueRef;
}useMousePosition3D
function useMousePosition3D(z = 0) {
const position = useRef(new THREE.Vector3());
const { camera, viewport } = useThree();
useFrame(({ pointer }) => {
position.current.set(
(pointer.x * viewport.width) / 2,
(pointer.y * viewport.height) / 2,
z
);
});
return position;
}useBoundingBox
function useBoundingBox(ref: RefObject<THREE.Object3D>) {
const [box, setBox] = useState<THREE.Box3 | null>(null);
useEffect(() => {
if (ref.current) {
const bbox = new THREE.Box3().setFromObject(ref.current);
setBox(bbox);
}
}, [ref]);
return box;
}// Basic R3F Scene Template
// Copy and customize for new projects
import { useRef, Suspense } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import * as THREE from 'three';
// =============================================================================
// SCENE COMPONENTS
// =============================================================================
function RotatingBox() {
const meshRef = useRef<THREE.Mesh>(null!);
useFrame((state, delta) => {
meshRef.current.rotation.x += delta * 0.5;
meshRef.current.rotation.y += delta * 0.3;
});
return (
<mesh ref={meshRef}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="#ff6b6b" />
</mesh>
);
}
function Floor() {
return (
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -1, 0]} receiveShadow>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial color="#333" />
</mesh>
);
}
function Lights() {
return (
<>
<ambientLight intensity={0.4} />
<directionalLight
position={[5, 10, 5]}
intensity={1}
castShadow
shadow-mapSize={[2048, 2048]}
shadow-camera-far={50}
shadow-camera-left={-10}
shadow-camera-right={10}
shadow-camera-top={10}
shadow-camera-bottom={-10}
/>
</>
);
}
function Scene() {
return (
<>
<Lights />
<RotatingBox />
<Floor />
</>
);
}
// =============================================================================
// LOADING FALLBACK
// =============================================================================
function Loader() {
const meshRef = useRef<THREE.Mesh>(null!);
useFrame((_, delta) => {
meshRef.current.rotation.z += delta * 2;
});
return (
<mesh ref={meshRef}>
<torusGeometry args={[0.5, 0.1, 16, 32]} />
<meshBasicMaterial color="white" wireframe />
</mesh>
);
}
// =============================================================================
// MAIN APP
// =============================================================================
export default function App() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<Canvas
shadows
camera={{ position: [3, 3, 3], fov: 75 }}
gl={{
antialias: true,
alpha: false,
powerPreference: 'high-performance'
}}
dpr={[1, 2]}
>
<color attach="background" args={['#1a1a2e']} />
<Suspense fallback={<Loader />}>
<Scene />
</Suspense>
</Canvas>
</div>
);
}
// =============================================================================
// CANVAS PRESETS (uncomment to use)
// =============================================================================
/*
// Performance-focused (mobile)
<Canvas
dpr={1}
gl={{ antialias: false, powerPreference: 'low-power' }}
frameloop="demand"
/>
// Quality-focused (desktop)
<Canvas
shadows="soft"
dpr={[1, 2]}
gl={{ antialias: true }}
camera={{ fov: 45 }}
/>
// Orthographic
<Canvas
orthographic
camera={{ zoom: 50, position: [0, 0, 100] }}
/>
*/