Build performant 3D user interfaces for Three.js using yoga with support for nested scrolling, buttons, inputs, dropdowns, tabs, checkboxes, and more.
Perfect for games, XR (VR/AR), and any web-based Spatial Computing App.
npm install three @pmndrs/uikit
A simple UI with 2 containers horizontally aligned, rendered in fullscreen. When the user hovers over a container, the container's opacity changes. | ![]() |
---|
import { PerspectiveCamera, Scene, WebGLRenderer } from 'three'
import { reversePainterSortStable, Container, Root } from '@pmndrs/uikit'
const camera = new PerspectiveCamera(70, 1, 0.01, 100)
camera.position.z = 10
const scene = new Scene()
const canvas = document.getElementById('root') as HTMLCanvasElement
const renderer = new WebGLRenderer({ antialias: true, canvas })
const root = new Root(camera, renderer, undefined, {
flexDirection: "row",
padding: 100,
gap: 100
})
scene.add(root)
const c1 = new Container(root, {
flexGrow: 1,
backgroundOpacity: 0.5,
hover: { backgroundOpacity: 1 }
backgroundColor: "red"
})
const c2 = new Container(root, {
flexGrow: 1,
backgroundOpacity: 0.5,
hover: { backgroundOpacity: 1 },
backgroundColor: "blue"
})
renderer.setAnimationLoop(animation)
renderer.localClippingEnabled = true
renderer.setTransparentSort(reversePainterSortStable)
function updateSize() {
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(window.devicePixelRatio)
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
}
updateSize()
window.addEventListener('resize', updateSize)
let prev: number | undefined
function animation(time: number) {
const delta = prev == null ? 0 : time - prev
prev = time
root.update(delta)
renderer.render(scene, camera)
}
Events such a hovering require an additional event system that dispatches pointerover, ... events into the scene. More on this later ...