Claude Code for React Three Fiber: 3D Scenes in React — Claude Skills 360 Blog
Blog / Frontend / Claude Code for React Three Fiber: 3D Scenes in React
Frontend

Claude Code for React Three Fiber: 3D Scenes in React

Published: March 22, 2027
Read time: 8 min read
By: Claude Skills 360

React Three Fiber renders Three.js scenes declaratively as React components — <Canvas> creates the WebGL context, <mesh> wraps geometry and material, and useFrame drives the animation loop. All Three.js constructors map to lowercase JSX: <boxGeometry args={[1, 1, 1]} />, <meshStandardMaterial color="hotpink" />. useThree() accesses the renderer, camera, scene, and size. The @react-three/drei package adds helpers: OrbitControls, Environment, Text, Html, useGLTF, useTexture, Stars, and Sparkles. useGLTF(url) loads GLB models with auto-generated TypeScript types from gltfjsx. @react-three/rapier adds physics with RigidBody and Collider. useRef<THREE.Mesh>() targets mesh nodes for imperative updates. Raycasting through onClick, onPointerOver, and onPointerOut events on meshes enables interaction. Claude Code generates React Three Fiber scene setups, animated meshes, model loading, physics simulations, and interactive 3D interfaces.

CLAUDE.md for React Three Fiber

## React Three Fiber Stack
- Version: @react-three/fiber >= 8.15, @react-three/drei >= 9.100, three >= 0.165
- Canvas: <Canvas camera={{ position: [0, 2, 5], fov: 60 }} shadows> — scene root
- Mesh: <mesh castShadow><boxGeometry args={[1,1,1]}/><meshStandardMaterial/></mesh>
- Animation: useFrame((state, delta) => { ref.current.rotation.y += delta }) — 60fps loop
- Camera: const { camera, gl, scene, size } = useThree() — renderer access
- Models: const { nodes, materials } = useGLTF("/model.glb") — drei hook
- Physics: <Physics><RigidBody><mesh/></RigidBody></Physics> — rapier integration
- Lighting: <ambientLight/> + <directionalLight castShadow/> — standard PBR rig

Basic Scene Setup

// components/3d/ProductShowcase.tsx — interactive product viewer
"use client"
import { Canvas, useFrame, useThree } from "@react-three/fiber"
import {
  OrbitControls,
  Environment,
  ContactShadows,
  Html,
  PresentationControls,
} from "@react-three/drei"
import { useRef, useState, Suspense } from "react"
import * as THREE from "three"

function Box({
  color = "#3b82f6",
  onClick,
}: {
  color?: string
  onClick?: () => void
}) {
  const meshRef = useRef<THREE.Mesh>(null!)
  const [hovered, setHovered] = useState(false)
  const [active, setActive] = useState(false)

  useFrame((state, delta) => {
    meshRef.current.rotation.x += delta * 0.3
    meshRef.current.rotation.y += delta * 0.5

    // Pulse scale on hover
    const targetScale = hovered ? 1.2 : 1
    meshRef.current.scale.setScalar(
      THREE.MathUtils.lerp(meshRef.current.scale.x, targetScale, 0.1)
    )
  })

  return (
    <mesh
      ref={meshRef}
      onClick={() => {
        setActive(!active)
        onClick?.()
      }}
      onPointerOver={() => setHovered(true)}
      onPointerOut={() => setHovered(false)}
      castShadow
    >
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial
        color={hovered ? "#f97316" : color}
        metalness={0.5}
        roughness={0.3}
        emissive={active ? color : "#000000"}
        emissiveIntensity={active ? 0.3 : 0}
      />
    </mesh>
  )
}

function FloatingLabel({ text, position }: { text: string; position: [number, number, number] }) {
  return (
    <Html position={position} center distanceFactor={6}>
      <div className="bg-black/80 text-white text-xs px-2 py-1 rounded whitespace-nowrap pointer-events-none">
        {text}
      </div>
    </Html>
  )
}

export function ProductShowcase() {
  return (
    <div className="w-full h-96 rounded-xl overflow-hidden border">
      <Canvas
        shadows
        camera={{ position: [0, 2, 5], fov: 50 }}
        gl={{ antialias: true, alpha: true }}
      >
        <color attach="background" args={["#f8fafc"]} />

        {/* Lighting */}
        <ambientLight intensity={0.5} />
        <directionalLight
          position={[5, 5, 5]}
          intensity={1}
          castShadow
          shadow-mapSize={[2048, 2048]}
        />
        <pointLight position={[-5, 5, -5]} intensity={0.5} color="#6366f1" />

        <Suspense fallback={null}>
          <PresentationControls
            global
            rotation={[0.13, 0.1, 0]}
            polar={[-0.4, 0.2]}
            azimuth={[-1, 0.75]}
            config={{ mass: 2, tension: 400 }}
            snap={{ mass: 4, tension: 400 }}
          >
            <Box />
            <FloatingLabel text="Click to interact" position={[0, 1.5, 0]} />
          </PresentationControls>

          <Environment preset="city" />
          <ContactShadows
            position={[0, -1.5, 0]}
            opacity={0.4}
            scale={5}
            blur={2}
          />
        </Suspense>

        <OrbitControls makeDefault minPolarAngle={0} maxPolarAngle={Math.PI / 2} />
      </Canvas>
    </div>
  )
}

GLTF Model Loading

// components/3d/ProductModel.tsx — load GLB/GLTF models
import { useGLTF, useAnimations } from "@react-three/drei"
import { useEffect, useRef } from "react"
import { useFrame } from "@react-three/fiber"
import * as THREE from "three"

// gltfjsx generates this type — run: npx gltfjsx model.glb --types
interface ProductGLTF {
  nodes: {
    Body: THREE.Mesh
    Screen: THREE.Mesh
    Buttons: THREE.Mesh
  }
  materials: {
    Aluminum: THREE.MeshStandardMaterial
    Glass: THREE.MeshPhysicalMaterial
  }
  animations: THREE.AnimationClip[]
}

// Preload for faster first render
useGLTF.preload("/models/product.glb")

export function ProductModel({
  position = [0, 0, 0] as [number, number, number],
}: {
  position?: [number, number, number]
}) {
  const group = useRef<THREE.Group>(null!)
  const { nodes, materials, animations } = useGLTF("/models/product.glb") as unknown as ProductGLTF
  const { actions } = useAnimations(animations, group)

  // Play idle animation on mount
  useEffect(() => {
    actions["Idle"]?.play()
  }, [actions])

  // Float animation
  useFrame(state => {
    group.current.position.y = Math.sin(state.clock.elapsedTime * 0.5) * 0.1
    group.current.rotation.y = state.clock.elapsedTime * 0.2
  })

  return (
    <group ref={group} position={position} dispose={null}>
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.Body.geometry}
        material={materials.Aluminum}
      />
      <mesh
        castShadow
        geometry={nodes.Screen.geometry}
        material={materials.Glass}
      />
      <mesh
        geometry={nodes.Buttons.geometry}
        material={materials.Aluminum}
      />
    </group>
  )
}

Particle System

// components/3d/ParticleField.tsx — instanced mesh for performance
import { useRef, useMemo } from "react"
import { useFrame } from "@react-three/fiber"
import * as THREE from "three"

const COUNT = 2000

export function ParticleField() {
  const meshRef = useRef<THREE.InstancedMesh>(null!)

  // Generate initial positions
  const { positions, speeds } = useMemo(() => {
    const positions = Float32Array.from({ length: COUNT * 3 }, () => (Math.random() - 0.5) * 20)
    const speeds = Float32Array.from({ length: COUNT }, () => Math.random() * 0.02 + 0.005)
    return { positions, speeds }
  }, [])

  const dummy = useMemo(() => new THREE.Object3D(), [])
  const clock = useRef(0)

  // Initialize instance transforms
  useFrame((state, delta) => {
    clock.current += delta

    for (let i = 0; i < COUNT; i++) {
      const xi = i * 3
      dummy.position.set(
        positions[xi]! + Math.sin(clock.current * speeds[i]! + i) * 0.1,
        positions[xi + 1]! + Math.sin(clock.current * 0.5 + i) * 0.05,
        positions[xi + 2]!
      )
      dummy.scale.setScalar(Math.sin(clock.current + i) * 0.3 + 0.7)
      dummy.updateMatrix()
      meshRef.current.setMatrixAt(i, dummy.matrix)
    }
    meshRef.current.instanceMatrix.needsUpdate = true
  })

  return (
    <instancedMesh ref={meshRef} args={[undefined, undefined, COUNT]}>
      <sphereGeometry args={[0.02, 4, 4]} />
      <meshBasicMaterial color="#6366f1" transparent opacity={0.6} />
    </instancedMesh>
  )
}

Physics with Rapier

// components/3d/PhysicsScene.tsx — @react-three/rapier
import { Physics, RigidBody, CuboidCollider } from "@react-three/rapier"
import { Canvas } from "@react-three/fiber"
import { OrbitControls } from "@react-three/drei"
import { useRef } from "react"
import * as THREE from "three"

function PhysicsBox({ position }: { position: [number, number, number] }) {
  return (
    <RigidBody position={position} restitution={0.7} friction={0.5}>
      <mesh castShadow>
        <boxGeometry args={[0.5, 0.5, 0.5]} />
        <meshStandardMaterial color={`hsl(${Math.random() * 360}, 70%, 60%)`} />
      </mesh>
    </RigidBody>
  )
}

export function PhysicsDemo() {
  return (
    <Canvas camera={{ position: [0, 5, 10], fov: 50 }} shadows>
      <ambientLight intensity={0.5} />
      <directionalLight position={[10, 10, 5]} castShadow intensity={1} />

      <Physics gravity={[0, -9.81, 0]}>
        {/* Floor */}
        <RigidBody type="fixed">
          <mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]} position={[0, -1, 0]}>
            <planeGeometry args={[20, 20]} />
            <meshStandardMaterial color="#e2e8f0" />
          </mesh>
          <CuboidCollider args={[10, 0.1, 10]} position={[0, -1, 0]} />
        </RigidBody>

        {/* Falling boxes */}
        {Array.from({ length: 20 }, (_, i) => (
          <PhysicsBox
            key={i}
            position={[
              (Math.random() - 0.5) * 4,
              Math.random() * 8 + 2,
              (Math.random() - 0.5) * 4,
            ]}
          />
        ))}
      </Physics>

      <OrbitControls />
    </Canvas>
  )
}

For the Babylon.js alternative when more built-in game-engine features are needed — Babylon includes a full inspector GUI, PBR material library, physics integration, GUI system, and animation editor in the core package without needing React integration layers, see the WebGL engine comparison for game-focused use cases. For the Spline alternative when design-tool integration is the priority — Spline.design exports scenes directly to embeddable React components with no Three.js knowledge required, useful for marketing page animations created by designers, see the 3D web design tools guide. The Claude Skills 360 bundle includes React Three Fiber skill sets covering scenes, animations, model loading, and physics. Start with the free tier to try 3D web generation.

Keep Reading

Frontend

Claude Code for Chart.js Advanced: Custom Plugins and Mixed Charts

Advanced Chart.js patterns with Claude Code — chart.register() for tree-shaking, mixed chart types combining bar and line, custom plugin API with beforeDraw and afterDatasetsDraw hooks, ScriptableContext for computed colors, ChartDataLabels plugin for value labels, chartjs-plugin-zoom for pan and zoom, custom gradient fills via ctx.createLinearGradient, ChartJS annotation plugin for threshold lines, streaming data with chartjs-plugin-streaming, and react-chartjs-2 with useRef and chart instance.

6 min read Jun 27, 2027
Frontend

Claude Code for Nivo: Rich SVG and Canvas Charts

Build rich data visualizations with Nivo and Claude Code — ResponsiveLine and ResponsiveBar for adaptive charts, ResponsiveHeatMap for matrix data, ResponsiveTreeMap for hierarchal data, ResponsiveSunburst for nested proportions, ResponsiveChord for relationship diagrams, ResponsiveCalendar for activity heat maps, ResponsiveNetwork for force graphs, NivoTheme for consistent styling, tooltip customization with sliceTooltip, and motion config for spring animations.

6 min read Jun 26, 2027
Frontend

Claude Code for Victory Charts: React Native and Web Charts

Build cross-platform charts with Victory and Claude Code — VictoryChart, VictoryLine, VictoryBar, and VictoryScatter for web and React Native, VictoryPie for donut charts, VictoryArea for stacked areas, VictoryAxis for custom axes, VictoryTooltip and VictoryVoronoiContainer for hover tooltips, VictoryBrushContainer for range selection, VictoryZoomContainer for pan and zoom, VictoryLegend for series labels, custom theme with VictoryTheme, and VictoryStack for grouped bars.

6 min read Jun 25, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free