Navigate
Search topics across all sections
Groups & Hierarchy
Imagine Russian nesting dolls. The biggest doll is a group. Inside it are smaller dolls -- meshes, lights, or even more groups. Move the outer doll, and everything inside moves with it. Rotate it, and everything rotates together. That is what a <group> does in R3F: it bundles objects so you can transform them as one unit.
You build a robot arm. You want the forearm to swing, but when you rotate the forearm mesh directly, the hand stays in place. The arm separates at the elbow like a broken toy. The hand should follow the forearm, but it does not because the hand is not nested inside the forearm group.
Forearm rotates but hand stays stationary. Hand is a sibling of forearm, not a child. Expected: hand follows forearm rotation. Actual: hand is disconnected from forearm transform.
Real-world
Think of Russian nesting dolls (matryoshka). The outermost doll is the biggest group. Open it, and there is a smaller doll inside. Open that one, another inside. Move the outer doll to a shelf, and every doll inside comes along for the ride.
Now imagine a robot arm. The base is the outer doll. Inside the base is the upper arm (smaller doll). Inside the upper arm is the forearm. Inside the forearm is the hand. Rotate the base, and the entire arm swings. Rotate just the forearm, and only the hand follows. Each level only affects its children.
Outer Group
Move this, everything moves
Inner Group
A joint in the chain
Mesh
The visible part
Building a Robot Arm
Let us build a simple robot arm with three joints. Each joint is a group, and the nesting creates the hierarchy. Rotate one joint and everything below it follows.
The base sits on the ground and rotates left to right. It is the outermost group -- everything else is inside it.
<group ref={baseRef}> {/* Base joint */}
<mesh> {/* Base platform */}
<cylinderGeometry args={[0.5, 0.5, 0.2]} />
<meshStandardMaterial color="gray" />
</mesh>
</group>Rotate baseRef around the Y axis and the entire arm swings left and right. The base is the root of our hierarchy.
The upper arm group is nested inside the base. It sits on top of the base and tilts forward and backward.
<group ref={baseRef}>
{/* base mesh... */}
<group ref={upperArmRef} position={[0, 0.15, 0]}>
<mesh position={[0, 0.75, 0]}> {/* Arm */}
<boxGeometry args={[0.2, 1.5, 0.2]} />
<meshStandardMaterial color="steelblue" />
</mesh>
</group>
</group>Notice the upper arm is positioned at [0, 0.15, 0] -- that is relative to the base. When the base rotates, the upper arm follows because it is a child.
The forearm goes inside the upper arm group. Now we have three levels of nesting, and each joint affects everything below it.
<group ref={upperArmRef} position={[0, 0.15, 0]}>
{/* upper arm mesh... */}
<group ref={forearmRef} position={[0, 1.5, 0]}>
<mesh position={[0, 0.5, 0]}> {/* Forearm */}
<boxGeometry args={[0.15, 1, 0.15]} />
<meshStandardMaterial color="orange" />
</mesh>
</group>
</group>The forearm is at [0, 1.5, 0] relative to the upper arm group. Rotate the base? Everything moves. Rotate the upper arm? The forearm follows. Rotate the forearm? Only it moves. Each joint is independent but cascades downward.
What you just learned
A <group> is an invisible container that transforms all its children together
Groups cost nothing to render -- no vertices, no draw calls
Nested groups create transform chains (like joints in an arm)
Each level only controls its own rotation, but the effect cascades down
Child positions are always relative to their parent group
Groups are perfect for toggling visibility of many objects at once
Question
If you want a cube to orbit around a point (like a planet around a star), do you rotate the cube or something else?
You rotate a parent group, not the cube itself. Place the cube at an offset from the group's center (like [3, 0, 0]), then rotate the group. The cube sweeps in a circle around the group's origin. Rotating the cube directly would just spin it in place -- like a basketball spinning on a finger instead of orbiting the court.
Think about it...
A group at position [2, 0, 0] is rotated 90 degrees around the Y axis. Inside it, a mesh is at position [3, 0, 0]. Where does the mesh end up in world space?
Hint: Rotation changes the DIRECTION of the child's offset. A 90-degree Y rotation turns the X axis into the -Z axis.
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Set speed to 0 -- use manual sliders to pose the arm.
Try This!
Beginner
Set baseRotation to PI (3.14) -- the arm faces backward!
Try This!
Intermediate
Max out elbowAngle -- does the arm break?
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
// Group at [5,0,0], child at [3,0,0]
// World result: [8,0,0] -- not [3,0,0]!
<group position={[5, 0, 0]}>
<mesh position={[3, 0, 0]}>
<boxGeometry />
</mesh>
</group>// Parent stretches all children along X!
<group scale={[2, 1, 1]}>
<mesh> {/* Sphere becomes an ellipsoid */}
<sphereGeometry args={[0.5, 32, 32]} />
</mesh>
</group>// This spins the cube IN PLACE, not orbiting
const ref = useRef<THREE.Mesh>(null)
useFrame((_, d) => {
ref.current!.rotation.y += d
})
<mesh ref={ref} position={[3, 0, 0]} />Best Practices
Use groups as pivot points
To orbit an object around a point, wrap it in a group centered at the orbit point and rotate the group. The child sweeps in a circle.
Keep uniform scale on groups
Non-uniform scale distorts all children and breaks normals. Apply non-uniform scale only to individual meshes that need it, not to group containers.
Toggle visibility with groups
Setting visible={false} on a group hides everything inside it with a single prop. Much cleaner than hiding each mesh individually.
Think in local coordinates
Design child positions relative to the parent center, not world space. This makes compound objects portable -- move the parent and everything stays intact.