r3f-fundamentals
Skill9 starsChanged 6 months agoMITView on GitHub
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.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- EnzeD/r3f-skillstheir own version, first seen Aug 31, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- EnzeD/r3f-skills★ 125same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository77 other files it gives its agents.
- building-mechanicsSkill
- algorithmic-artSkill
- audio-analysisSkill
- audio-playbackSkill
- audio-reactiveSkill
- audio-routerSkill
- aws-skillsSkill
- backend-dev-guidelinesSkill
- brainstormingSkill
- brand-guidelinesSkill
- builder-uxSkill
- building-routerSkill
- canvas-designSkill
- case-studies-referenceSkill
- component-librarySkill
- d3js-visualizationSkill
- dashboardSkill
- decay-upkeepSkill
- doc-coauthoringSkill
- docxSkill
- form-accessibilitySkill
- form-reactSkill
- form-securitySkill
- forms-routerSkill
- form-ux-patternsSkill
- form-validationSkill
- form-vanillaSkill
- form-vueSkill
- frontend-designSkill
- frontend-dev-guidelinesSkill
- gsap-fundamentalsSkill
- gsap-reactSkill
- gsap-routerSkill
- gsap-scrolltriggerSkill
- gsap-sequencingSkill
- immersive-visuals-routerSkill
- internal-commsSkill
- mcp-builderSkill
- multiplayer-buildingSkill
- particles-gpuSkill
- particles-lifecycleSkill
- particles-physicsSkill
- particles-routerSkill
- pdfSkill
- performance-at-scaleSkill
- platform-buildingSkill
- postfx-bloomSkill
- postfx-composerSkill
- postfx-effectsSkill
- postfx-routerSkill
- pptxSkill
- product-self-knowledgeSkill
- r3f-dreiSkill
- r3f-geometrySkill
- r3f-materialsSkill
- r3f-performanceSkill
- r3f-routerSkill
- router-templateSkill
- scientific-documentationSkill
- shader-effectsSkill
- The first 60 shown.
The file
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
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

