bottega

three

R3F Globe

A rotating wireframe globe rendered with @react-three/fiber, composed over canvas-wrapper so it inherits WebGL feature-detect, lazy mount, and a reduced-motion / no-WebGL poster fallback. The <Canvas> scene is dynamic-imported ssr:false (R3F can't SSR), reads contract color tokens onto its material, and disposes its geometry/material on unmount.


Install

1. Register the namespace (once per project):

json
// components.json — register the @bottega namespace once
{
  "registries": {
    "@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
  }
}

2. Add the component:

bash
npx shadcn add @bottega/r3f-globe

Usage

Usagetsx
<R3fGlobe />

Props

PropTypeDefaultDescription
classNamestring
aspectstring1 / 1Aspect-ratio of the mount box. Default "1 / 1".
minHeightstringMin-height of the mount box, forwarded to canvas-wrapper. Default "20rem". Set "0" when the parent already constrains height so the globe stays square (tracks `aspect`) instead of being floored at 20rem in a tight container.

Source

r3f-globe.tsxtsx
"use client";

import dynamic from "next/dynamic";
import { CanvasWrapper } from "@/registry/three/canvas-wrapper/canvas-wrapper";
import { GlobePoster } from "./globe-poster";

// R3F cannot SSR — load the <Canvas> scene client-side only. While it loads
// (and on every guarded path) the poster is what shows.
const GlobeScene = dynamic(() => import("./globe-scene"), {
  ssr: false,
  loading: () => <GlobePoster />,
});

export interface R3fGlobeProps {
  className?: string;
  /** Aspect-ratio of the mount box. Default "1 / 1". */
  aspect?: string;
  /**
   * Min-height of the mount box, forwarded to canvas-wrapper. Default "20rem".
   * Set "0" when the parent already constrains height so the globe stays square
   * (tracks `aspect`) instead of being floored at 20rem in a tight container.
   */
  minHeight?: string;
}

/**
 * A rotating wireframe globe rendered with @react-three/fiber, composed over
 * canvas-wrapper so it inherits the full 3D contract: WebGL feature-detect,
 * lazy mount, and a reduced-motion / no-WebGL poster fallback. The scene is
 * dynamic-imported ssr:false (R3F can't SSR) and disposes its GPU resources on
 * unmount.
 */
export function R3fGlobe({ className, aspect = "1 / 1", minHeight }: R3fGlobeProps) {
  return (
    <CanvasWrapper
      className={className}
      aspect={aspect}
      minHeight={minHeight}
      poster={<GlobePoster />}
    >
      <GlobeScene />
    </CanvasWrapper>
  );
}

Dependencies

  • three
  • @react-three/fiber
  • @bottega/canvas-wrapper
  • @bottega/tokens