component
OfficeChair
A swivel office chair: five-star base on casters, gas column, padded seat, low or high back, optional arms. Seats a Person in sit or type pose.
⌖ Explore the library in 3D → · View as Markdown
Add it
npx @runek/cli add officechair
Pulls @react-three/drei@^10.7.7, @react-three/rapier@^2.2.0, @runek/core@^0.13.0.
Use it
import { OfficeChair } from './runek/OfficeChair'
<OfficeChair position={[0, 0, 0]} seed={1} />
Or as a node in a world file (worlds as data):
{ "type": "OfficeChair", "props": { "position": [0, 0, 0], "seed": 1 } }
Props
| Prop | Type | Default | Description |
|---|---|---|---|
seatHeight | number | 0.47 | Top of the seat cushion, in units. |
back | "low" | "high" | Seeded when unset. | |
arms | boolean | Armrests. Seeded when unset. | |
color | color | Seat and back; defaults to the palette’s fabric. | |
frameColor | color | Base, column, and arms; defaults to the palette’s metal. | |
position | [x, y, z] | [0, 0, 0] | Position [x, y, z] in units (1 unit = 1 m, Y-up). |
rotation | [x, y, z] | [0, 0, 0] | Euler rotation [x, y, z] in radians. |
seed | number | 1 | Seed for the deterministic variation: same seed, same result. |
Registry manifest
registry manifest /r/components/officechair.json Self-contained JSON: inlined source plus resolved dependencies, exactly whatrunek add officechair fetches.
Browse the whole catalog in the gallery →.
Source (OfficeChair.tsx)
import { RoundedBox } from '@react-three/drei'
import { CuboidCollider, RigidBody } from '@react-three/rapier'
import { pick, rng, sub, useWorld, type WorldComponentProps } from '@runek/core'
import { useMemo } from 'react'
/** A `low` back that stops at the shoulder blades, or a `high` one up to the head. */
export type OfficeChairBack = 'low' | 'high'
export interface OfficeChairProps extends WorldComponentProps {
/** Top of the seat cushion, in units. */
seatHeight?: number
/** Seeded when unset. */
back?: OfficeChairBack
/** Armrests. Seeded when unset. */
arms?: boolean
/** Seat and back; defaults to the palette's `fabric`. */
color?: string
/** Base, column, and arms; defaults to the palette's `metal`. */
frameColor?: string
}
const LEGS = 5
/**
* A swivel office chair: a five-star base on casters, a gas column, a padded seat, and a back,
* with or without arms. Seat faces local +Z, so `pose="sit"` or `pose="type"` on a `Person` at the
* same position and rotation sits in it. One cuboid collider for the seat.
*/
export function OfficeChair({
position = [0, 0, 0],
rotation = [0, 0, 0],
seed = 1,
seatHeight = 0.47,
back,
arms,
color,
frameColor,
}: OfficeChairProps) {
const { unit, palette } = useWorld()
const cloth = color ?? palette.fabric
const frame = frameColor ?? palette.metal
const u = unit
const look = useMemo(() => {
const r = rng(sub(seed, 0))
const rolled = { back: pick<OfficeChairBack>(r, ['low', 'high', 'high']), arms: r() < 0.7 }
return { back: back ?? rolled.back, arms: arms ?? rolled.arms }
}, [seed, back, arms])
const seatY = seatHeight * u
const cushion = 0.08 * u
const base = 0.09 * u
const backH = (look.back === 'high' ? 0.68 : 0.46) * u
const legs = Array.from({ length: LEGS }, (_, i) => (i / LEGS) * Math.PI * 2)
return (
<RigidBody type="fixed" colliders={false} position={position} rotation={rotation}>
<CuboidCollider args={[0.26 * u, seatY / 2, 0.26 * u]} position={[0, seatY / 2, 0]} />
{/* five-star base on casters */}
{legs.map((a) => (
<group key={a} rotation={[0, a, 0]}>
<mesh position={[0, base, 0.17 * u]} rotation={[0.12, 0, 0]} castShadow>
<boxGeometry args={[0.04 * u, 0.03 * u, 0.32 * u]} />
<meshStandardMaterial color={frame} roughness={0.4} metalness={0.5} />
</mesh>
<mesh position={[0, 0.03 * u, 0.31 * u]} castShadow>
<sphereGeometry args={[0.03 * u, 10, 8]} />
<meshStandardMaterial color="#1c1c1e" roughness={0.6} />
</mesh>
</group>
))}
{/* gas column */}
<mesh position={[0, (base + seatY - cushion) / 2, 0]} castShadow>
<cylinderGeometry args={[0.028 * u, 0.034 * u, seatY - cushion - base, 12]} />
<meshStandardMaterial color={frame} roughness={0.3} metalness={0.7} />
</mesh>
{/* seat */}
<RoundedBox
args={[0.5 * u, cushion, 0.48 * u]}
radius={0.03 * u}
smoothness={3}
position={[0, seatY - cushion / 2, 0.01 * u]}
castShadow
receiveShadow
>
<meshStandardMaterial color={cloth} roughness={0.9} />
</RoundedBox>
{/* back, on a spine up from under the seat, reclined a touch */}
<mesh position={[0, seatY + 0.06 * u, -0.25 * u]} castShadow>
<boxGeometry args={[0.05 * u, 0.22 * u, 0.025 * u]} />
<meshStandardMaterial color={frame} roughness={0.4} metalness={0.5} />
</mesh>
<RoundedBox
args={[0.46 * u, backH, 0.07 * u]}
radius={0.03 * u}
smoothness={3}
position={[0, seatY + 0.1 * u + backH / 2, -0.28 * u]}
rotation={[-0.12, 0, 0]}
castShadow
receiveShadow
>
<meshStandardMaterial color={cloth} roughness={0.9} />
</RoundedBox>
{look.arms &&
[-1, 1].map((s) => (
<group key={s} position={[s * 0.27 * u, seatY, -0.02 * u]}>
<mesh position={[0, 0.1 * u, 0]} castShadow>
<boxGeometry args={[0.03 * u, 0.2 * u, 0.03 * u]} />
<meshStandardMaterial color={frame} roughness={0.4} metalness={0.5} />
</mesh>
<mesh position={[0, 0.21 * u, 0.02 * u]} castShadow>
<boxGeometry args={[0.06 * u, 0.03 * u, 0.26 * u]} />
<meshStandardMaterial color="#232326" roughness={0.7} />
</mesh>
</group>
))}
</RigidBody>
)
}
OfficeChair.groundSitting = true