@darrylondil/react-three-ui
v0.2.0
Published
Liquid glass UI design system for React Three Fiber — build entire websites inside three.js with flexbox layout, theming, and mono/SBS/XR view modes.
Maintainers
Readme
@darrylondil/react-three-ui
A liquid-glass UI design system for React Three Fiber — build entire websites inside three.js that feel like regular React apps: real CSS flexbox, px units, responsive breakpoints, theming, pointer events, the mobile keyboard — except every surface is a 3D object that can render in mono, side-by-side stereo (SBS), or WebXR.
Inspired by shadcn/ui's API shape and Apple's liquid glass material.
Why it's fast
- One backdrop pass per frame, no matter how many glass surfaces exist. The scene behind the UI is grabbed once into a downsampled render target, dual-Kawase blurred, and shared by every
GlassMaterial. Glass is O(1). - A second, optional grab powers chrome glass (
backdrop="ui"): navbars, docks and overlay panels that blur the page content scrolling beneath them, iOS-toolbar style. - Zero React state per frame. All animation (hover lifts, press scales, scroll inertia, tab pills, dialog transitions) is damped mutation of refs and uniforms inside
useFrame. - Every surface is one plane + one fragment shader (rounded-rect SDF, edge refraction along the SDF gradient, fresnel rim, grain). One shader program serves all of them.
Install
npm i @darrylondil/react-three-ui three @react-three/fiberPeer deps: react >= 18, three >= 0.160, @react-three/fiber >= 9.
Quick start
import { Canvas } from "@react-three/fiber";
import { UIRoot, Flex, Card, CardHeader, CardTitle, CardContent, Text, Button } from "@darrylondil/react-three-ui";
export function App() {
return (
<Canvas dpr={[1, 1.5]} camera={{ fov: 50, near: 10, far: 60000 }}>
{/* your 3D scene lives on layer 0 and shows through the glass */}
<UIRoot theme="dark" quality="low" viewMode="mono">
<Flex width="100%" height="100%" alignItems="center" justifyContent="center">
<Card width={420}>
<CardHeader>
<CardTitle>Hello, three.js</CardTitle>
</CardHeader>
<CardContent>
<Text size="sm">This whole card is a fragment shader.</Text>
<Button onClick={() => console.log("clicked")}>Click me</Button>
</CardContent>
</Card>
</Flex>
</UIRoot>
</Canvas>
);
}1 layout px = 1 world unit on the UI plane; UIRoot parks the plane in front of the camera so the page always fills the viewport exactly.
Components
Primitives — Surface (the glass), Text (troika, measured into the layout pass), Flex/Box/Spacer (Yoga flexbox).
Controls — Button, Input (real hidden DOM input: mobile keyboard, IME, clipboard), Select, Switch, Checkbox, Slider, Progress, Badge, Avatar, Separator, Card family.
Composite — Tabs (damped indicator pill), Dialog, Sheet, Tooltip, ScrollArea (wheel + touch drag + inertia + rubber-band), Dock (pointer-proximity magnification).
System — UIRoot, ThemeProvider/useTheme (shadcn-style tokens, dark/light presets, createTheme), useBreakpoint/usePixels, UIRaycast, StereoCursor, enterXR.
View modes
<UIRoot viewMode="mono" /> // regular
<UIRoot viewMode="sbs" /> // half-SBS stereo for 3D displays / AR glasses
// WebXR: call enterXR(gl) — three renders the stereo session itselfSBS is anamorphic half-SBS with asymmetric-frustum convergence at the UI plane: flat content fuses at screen depth; elevation (elevation, hover lifts, overlays) produces real parallax pop. Pointer events keep one fused hitbox per element, the OS cursor is hidden over the canvas, and StereoCursor draws an in-scene ring in both eyes. ipd scales the depth effect.
Things worth knowing
- Layers. UI lives on layer 1 so the backdrop grab can exclude it.
UIRootpatches the store raycaster; if youcreatePortalUI into your own r3f store, render<UIRaycast />inside it or nothing is clickable. - Chrome glass.
Surface backdrop="ui"samples the second grab (scene + page UI, minus itself and the overlay tree). Use it for bars that content scrolls beneath. - Elevation is real z.
elevation, hover lifts and the overlay tree translate toward the camera. The overlay root is scale-compensated so 1px stays 1px; centered panels magnify imperceptibly. - flexBasis 0 in a column is base height 0 — exactly like CSS. The classic equal-columns idiom (
flexGrow={1} flexBasis={0}) collapses when the container switches to a column; gate it on your breakpoint. - Quality.
quality="low"= quarter-res backdrops, 3 blur taps;"high"= half-res, 5 taps + chromatic dispersion in the glass rim.
License
MIT © Darryl Huffman
