Useful add-ons for react-three-fiber providing 100+ components for 3D web applications
—
Web-specific components for HTML overlays, browser integration, and user interface elements. These components bridge the gap between 3D scenes and traditional HTML/DOM elements.
HTML overlay integration with 3D positioning, occlusion, and transform options.
/**
* HTML overlay integration with 3D positioning
* @param props - HTML overlay configuration
* @returns JSX element for HTML content in 3D space
*/
function Html(props: HtmlProps): JSX.Element;
interface HtmlProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'ref'> {
/** Prepend content to existing, false */
prepend?: boolean;
/** Center content horizontally and vertically, false */
center?: boolean;
/** Use CSS 3D transforms, false */
transform?: boolean;
/** Render as CSS 2D sprite, false */
sprite?: boolean;
/** Portal HTML into custom container */
portal?: HTMLElement;
/** Distance factor for sprites, 10 */
distanceFactor?: number;
/** Occlusion objects or settings */
occlude?: React.RefObject<Object3D>[] | boolean | 'raycast' | 'blending';
/** Occlusion blending amount, 0 */
occlusionOpacity?: number;
/** Z-index for HTML, 0 */
zIndexRange?: [number, number];
/** Calculate position function */
calculatePosition?: CalculatePosition;
/** As container div, false */
as?: string;
/** Wrapper class */
wrapperClass?: string;
/** Pointer events, 'auto' */
pointerEvents?: 'auto' | 'none';
/** Full screen, false */
fullscreen?: boolean;
/** Epsilon for depth testing, 0 */
eps?: number;
}
type CalculatePosition = (
obj: Object3D,
camera: Camera,
size: { width: number; height: number }
) => [number, number];Usage Examples:
import { Html } from '@react-three/drei';
// Basic HTML overlay
<mesh position={[0, 0, 0]}>
<sphereGeometry />
<meshStandardMaterial />
<Html position={[0, 1, 0]} center>
<div style={{ background: 'white', padding: '10px', borderRadius: '5px' }}>
<h1>3D Label</h1>
<p>This HTML content follows the 3D object</p>
</div>
</Html>
</mesh>
// HTML with occlusion
<Html
position={[0, 0, 0]}
occlude={[meshRef, meshRef2]}
center
transform
>
<div className="tooltip">
This content is occluded by 3D objects
</div>
</Html>
// Sprite-style HTML
<Html
position={[5, 2, 0]}
sprite
distanceFactor={15}
center
>
<div className="ui-element">
<button onClick={() => console.log('Clicked!')}>
3D Button
</button>
</div>
</Html>Scroll-based camera controls with virtual scrolling and smooth interpolation.
/**
* Scroll-based camera controls with virtual scrolling
* @param props - Scroll controls configuration
* @returns JSX element for scroll controls
*/
function ScrollControls(props: ScrollControlsProps): JSX.Element;
/**
* Scroll component for accessing scroll state
* @param props - Scroll component props
* @returns JSX element with scroll context
*/
function Scroll(props: ScrollProps): JSX.Element;
interface ScrollControlsProps extends Omit<ThreeElements['group'], 'ref'> {
/** Virtual scroll area height, 1 */
pages?: number;
/** Scroll distance factor, 1 */
distance?: number;
/** Smooth scrolling damping, 4 */
damping?: number;
/** Horizontal scrolling, false */
horizontal?: boolean;
/** Infinite scrolling, false */
infinite?: boolean;
/** Enable zoom, true */
enabled?: boolean;
/** Style overrides for scroll area */
style?: React.CSSProperties;
/** Inner scroll area style */
innerStyle?: React.CSSProperties;
}
interface ScrollProps {
/** HTML scroll content, false */
html?: boolean;
}
interface ScrollControlsState {
/** Current scroll offset [0-1] */
offset: number;
/** Current scroll delta */
delta: number;
/** Scroll progress [0-1] */
progress: number;
/** Scroll range [start, end] */
range(from: number, to: number): number;
/** Curve interpolation */
curve(from: number, to: number, margin?: number): number;
/** Visible range check */
visible(from: number, to: number, margin?: number): boolean;
}Usage Examples:
import { ScrollControls, Scroll, useScroll } from '@react-three/drei';
// Basic scroll controls
<ScrollControls pages={3} damping={4}>
<Scroll>
{/* 3D content that responds to scroll */}
<AnimatedMesh />
</Scroll>
<Scroll html>
{/* HTML content overlay */}
<div style={{ position: 'absolute', top: '100vh' }}>
<h1>Page 2</h1>
</div>
<div style={{ position: 'absolute', top: '200vh' }}>
<h1>Page 3</h1>
</div>
</Scroll>
</ScrollControls>
// Animated content based on scroll
function AnimatedMesh() {
const scroll = useScroll();
const meshRef = useRef();
useFrame(() => {
if (meshRef.current) {
meshRef.current.rotation.y = scroll.offset * Math.PI * 2;
meshRef.current.position.y = scroll.curve(0, 1, 0.1) * 5;
}
});
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
);
}Loading UI component with progress indicators and customizable styling.
/**
* Loading UI component with progress indicators
* @param props - Loader configuration
* @returns JSX element for loading UI
*/
function Loader(props: LoaderProps): JSX.Element;
interface LoaderProps {
/** Container class name */
containerClassName?: string;
/** Inner class name */
innerClassName?: string;
/** Bar class name */
barClassName?: string;
/** Data class name */
dataClassName?: string;
/** Data interpolation function */
dataInterpolation?: (p: number) => string;
/** Initial text, 'Loading...' */
initialText?: string;
}Usage Examples:
import { Loader } from '@react-three/drei';
import { Suspense } from 'react';
// Basic loader
function App() {
return (
<>
<Canvas>
<Suspense fallback={null}>
<Scene />
</Suspense>
</Canvas>
<Loader />
</>
);
}
// Custom loader
<Loader
containerClassName="my-loader"
barClassName="my-progress-bar"
dataInterpolation={(p) => `Loading ${(p * 100).toFixed(0)}%`}
initialText="Preparing 3D Scene..."
/>Hook for changing cursor styles based on hover state.
/**
* Hook for changing cursor styles based on hover state
* @param hovered - Whether element is hovered
* @param cursor - Cursor style to apply, 'pointer'
*/
function useCursor(hovered: boolean, cursor?: string): void;Usage Examples:
import { useCursor } from '@react-three/drei';
function InteractiveMesh() {
const [hovered, setHovered] = useState(false);
useCursor(hovered, 'pointer');
return (
<mesh
onPointerEnter={() => setHovered(true)}
onPointerLeave={() => setHovered(false)}
onClick={() => console.log('Clicked!')}
>
<boxGeometry />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
);
}
// Custom cursor styles
function CustomInteractive() {
const [hovered, setHovered] = useState(false);
useCursor(hovered, 'grab');
return (
<mesh
onPointerEnter={() => setHovered(true)}
onPointerLeave={() => setHovered(false)}
>
<sphereGeometry />
<meshStandardMaterial />
</mesh>
);
}Keyboard input mapping and state management for 3D interactions.
/**
* Keyboard input mapping and state management
* @param props - Keyboard controls configuration
* @returns JSX element for keyboard controls context
*/
function KeyboardControls<T = string>(props: KeyboardControlsProps<T>): JSX.Element;
/**
* Hook for accessing keyboard control state
* @returns Keyboard controls API
*/
function useKeyboardControls(): KeyboardControlsApi;
interface KeyboardControlsProps<T = string> {
/** Key mapping configuration */
map: KeyboardControlsEntry<T>[];
/** Child components */
children: React.ReactNode;
/** DOM element to attach listeners, document */
domElement?: HTMLElement;
}
interface KeyboardControlsEntry<T = string> {
/** Control name */
name: T;
/** Key codes */
keys: string[];
}
interface KeyboardControlsApi {
/** Get current key state */
forward: boolean;
backward: boolean;
left: boolean;
right: boolean;
jump: boolean;
[key: string]: boolean;
}Usage Examples:
import { KeyboardControls, useKeyboardControls } from '@react-three/drei';
// Keyboard controls setup
const map = [
{ name: 'forward', keys: ['ArrowUp', 'KeyW'] },
{ name: 'backward', keys: ['ArrowDown', 'KeyS'] },
{ name: 'left', keys: ['ArrowLeft', 'KeyA'] },
{ name: 'right', keys: ['ArrowRight', 'KeyD'] },
{ name: 'jump', keys: ['Space'] },
];
function App() {
return (
<KeyboardControls map={map}>
<Canvas>
<Player />
</Canvas>
</KeyboardControls>
);
}
// Using keyboard input
function Player() {
const { forward, backward, left, right, jump } = useKeyboardControls();
const playerRef = useRef();
useFrame((state, delta) => {
if (playerRef.current) {
const speed = 5;
if (forward) playerRef.current.position.z -= speed * delta;
if (backward) playerRef.current.position.z += speed * delta;
if (left) playerRef.current.position.x -= speed * delta;
if (right) playerRef.current.position.x += speed * delta;
if (jump) playerRef.current.position.y += speed * delta;
}
});
return (
<mesh ref={playerRef}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
);
}Presentation-style controls optimized for showcasing 3D models and products.
/**
* Presentation-style controls for showcasing 3D content
* @param props - Presentation controls configuration
* @returns JSX element for presentation controls
*/
function PresentationControls(props: PresentationControlProps): JSX.Element;
interface PresentationControlProps extends Omit<ThreeElements['group'], 'ref'> {
/** Enable controls, true */
enabled?: boolean;
/** Global rotation constraint, true */
global?: boolean;
/** Cursor grab, true */
cursor?: boolean;
/** Snap to positions */
snap?: boolean | { mass: number; tension: number };
/** Rotation speed, 1 */
speed?: number;
/** Zoom settings */
zoom?: { min: number; max: number };
/** Polar angle limits [min, max] */
polar?: [number, number];
/** Azimuth angle limits [min, max] */
azimuth?: [number, number];
/** Config for spring animation */
config?: { mass: number; tension: number; friction: number };
}Usage Examples:
import { PresentationControls } from '@react-three/drei';
// Basic presentation controls
<PresentationControls enabled global>
<mesh>
<torusGeometry />
<meshNormalMaterial />
</mesh>
</PresentationControls>
// Constrained presentation
<PresentationControls
enabled
global
cursor
snap={{ mass: 4, tension: 1500 }}
rotation={[0, 0, 0]}
polar={[-Math.PI / 3, Math.PI / 3]}
azimuth={[-Math.PI / 1.4, Math.PI / 2]}
>
<Model />
</PresentationControls>Object selection system with multi-select support and event handling.
/**
* Object selection system with multi-select support
* @param props - Select configuration
* @returns JSX element for selection context
*/
function Select(props: SelectProps): JSX.Element;
interface SelectProps {
/** Multiple selection, false */
multiple?: boolean;
/** Selection box styling */
box?: boolean;
/** Child components */
children: React.ReactNode;
/** Selection change handler */
onChange?: (selected: Object3D[]) => void;
/** Filter function for selectable objects */
filter?: (objects: Object3D[]) => Object3D[];
}function Interactive3DHTML() {
const [selected, setSelected] = useState(null);
return (
<>
<mesh onClick={() => setSelected('sphere')}>
<sphereGeometry />
<meshStandardMaterial color={selected === 'sphere' ? 'red' : 'blue'} />
<Html position={[0, 1.5, 0]} center occlude>
<div className="label">
<h3>Interactive Sphere</h3>
<button onClick={() => setSelected(null)}>Reset</button>
</div>
</Html>
</mesh>
</>
);
}function ScrollDrivenScene() {
return (
<ScrollControls pages={4} damping={0.1}>
<Scroll>
<ScrollAnimatedContent />
</Scroll>
<Scroll html>
<div className="scroll-content">
{/* HTML content synchronized with 3D */}
</div>
</Scroll>
</ScrollControls>
);
}
function ScrollAnimatedContent() {
const data = useScroll();
const meshRef = useRef();
useFrame(() => {
if (meshRef.current && data) {
// Animate based on scroll position
meshRef.current.rotation.y = data.offset * Math.PI * 2;
meshRef.current.scale.setScalar(1 + data.range(0, 1/4) * 2);
}
});
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshNormalMaterial />
</mesh>
);
}function App() {
return (
<>
<Canvas>
<Suspense fallback={<LoadingFallback />}>
<Scene />
</Suspense>
</Canvas>
<Loader />
</>
);
}
function LoadingFallback() {
return (
<Html center>
<div className="loading-3d">
<div className="spinner"></div>
<p>Loading 3D assets...</p>
</div>
</Html>
);
}Component for keyboard-based cycling through raycasted objects.
/**
* Provides keyboard-based cycling through raycasted objects
* @param props - CycleRaycast configuration props
* @returns JSX element for raycasting controls
*/
function CycleRaycast(props: CycleRaycastProps): JSX.Element;
interface CycleRaycastProps {
/** Callback when selection changes */
onChanged?: (hits: THREE.Intersection[], cycle: number) => null;
/** Prevent default key behavior, true */
preventDefault?: boolean;
/** Enable scroll cycling, true */
scroll?: boolean;
/** Key code for cycling, 9 (Tab) */
keyCode?: number;
/** Portal element reference */
portal?: React.RefObject<HTMLElement>;
}Component for creating multiple viewports within a single canvas.
/**
* Creates multiple viewports within a single canvas
* @param props - View configuration props
* @returns JSX element for viewport rendering
*/
function View(props: ViewProps): JSX.Element;
interface ViewProps {
/** Viewport index */
index?: number;
/** Child components */
children?: React.ReactNode;
/** Viewport tracking element */
track?: React.RefObject<HTMLElement>;
}Touch and mouse controls for object presentation and interaction.
/**
* Touch and mouse controls for object presentation
* @param props - PresentationControls configuration props
* @returns JSX element for presentation controls
*/
function PresentationControls(props: PresentationControlProps): JSX.Element;
interface PresentationControlProps {
/** Snap behavior, false */
snap?: boolean | number;
/** Global controls, false */
global?: boolean;
/** Show cursor, true */
cursor?: boolean;
/** Control speed, 1 */
speed?: number;
/** Zoom factor, 1 */
zoom?: number;
/** Rotation limits */
rotation?: [number, number, number];
/** Polar angle limits */
polar?: [number, number];
/** Azimuth angle limits */
azimuth?: [number, number];
/** Damping factor, 0.05 */
damping?: number;
/** Enable controls, true */
enabled?: boolean;
/** Child components */
children?: React.ReactNode;
/** DOM element for events */
domElement?: HTMLElement;
}Install with Tessl CLI
npx tessl i tessl/npm-react-three--drei