RegionGlobe
Tilted 3D dotted globe that spins to the selected server region.
RegionGlobe is the ornamental half of region picking: a Three.js globe (via globe.gl) with three land treatments via variant: relief (default — the Natural Earth topology map as both grayscale texture and height map, tinted by muted-foreground so it stays monochrome in either theme), dots (the house dot-matrix land bitmap on a plain sphere), and outlines (Natural Earth country borders). The atmosphere glow is tunable too: glow={false} removes it, glowColor/glowAltitude restyle it. Changing regions emits a travel effect after globe.gl’s emit-arcs example: a dashed primary arc flies from the previous region while rings ripple out of both endpoints. Region markers are clickable dots with a hover popover (name, mono region id, and a color-coded ping: x ms when latencies is provided); while spin is on the globe idles on a slow auto-rotate, and choosing a region flies the camera to it on a downward tilt. Colors are probed live from the theme tokens, and under prefers-reduced-motion both the spin and the flight are disabled. Pair it with RegionSelect as the input and useRegionPing for a live latency caption.
ping: 92 ms
Install
npx shadcn@latest add https://ui.neon.com/r/region-globe.json
Usage
import { RegionGlobe } from "@/components/neon-ui/region-globe";
import { useRegionPing } from "@/hooks/use-region-ping";
const { pings } = useRegionPing({
"aws-us-east-1": "https://us-east-1.aws.example.com/health",
});
<RegionGlobe regions={regions} value={region} />
<p>~{pings[region]} ms from your location</p>
Wiring to Neon
The globe takes the same ServerRegion[] as RegionSelect, built on the server from the active regions:
import { createNeonClient } from "@/lib/neon-client";
const neon = createNeonClient(process.env.NEON_API_KEY);
const { data } = await neon.regions.list();
const regions = (data ?? [])
.filter((region) => region.geo_lat && region.geo_long)
.map((region) => ({
id: region.region_id,
name: region.name,
provider: region.region_id.split("-")[0].toUpperCase(),
lat: Number(region.geo_lat),
lng: Number(region.geo_long),
}));
Latency is measured in the browser, not fetched: pass the regions to useRegionPing in a client component and hand the result back as latencies. The hook is its own registry item, so anything that needs a ping can install it without the globe.
Props
regions?ServerRegion[]
Regions rendered as markers on the globe.
ServerRegion[]value?string
Region id the globe spins to; its marker renders larger.
stringonValueChange?(value: string) => void
Makes the marker dots clickable; called with the picked region id.
(value: string) => voidlatencies?Record<string, number | null>
Measured round-trips by region id; shown color-coded in each marker's hover popover.
Record<string, number | null>spin?boolean
Keep the globe slowly turning, e.g. until the user picks a region.
booleanfalsetilt?number
Camera latitude lead in degrees; a slight downward tilt.
number12variant?"relief" | "dots" | "outlines"
Land treatment: monochrome height map, the house dot-matrix bitmap, or country borders. Fixed at mount — remount (key) to switch.
"relief" | "dots" | "outlines""relief"glow?boolean
Set false to remove the atmosphere glow entirely.
booleantrueglowColor?string
Glow color; defaults to the theme's primary token.
stringglowAltitude?number
Glow reach as a fraction of the globe radius.
number0.12countriesUrl?string
Country borders GeoJSON for the outlines variant; defaults to globe.gl's Natural Earth 110m dataset (CDN).
stringtextureUrl?string
Grayscale surface texture; defaults to the Natural Earth topology map (CDN, relief variant only).
stringbumpUrl?string
Height map for the relief; defaults to the same topology map.
stringuseRegionPing
Install separately (https://ui.neon.com/r/use-region-ping.json) or let a block pull it in. The hook measures browser round-trips with opaque no-cors HEAD requests — a warmup pays DNS + TLS, then the best of samples sequential timings wins. Endpoints just need to be reachable; the response is never read.
urls?Record<string, string>
Endpoint per region id to measure against.
Record<string, string>samples?number
Timed requests per region after the warmup.
number3enabled?boolean
Set false to defer, then call refresh() when ready.
booleantrueReturns { pings, status, refresh } — pings maps region ids to milliseconds (or null when unreachable), status walks idle → measuring → done.
Accessibility
- The globe is decorative (
aria-hiddencanvas); dot clicks are a pointer-only enhancement, so selection state must live in a real input like RegionSelect. - Idle spin, camera flight, and the arc/ring travel effect are disabled under
prefers-reduced-motion— the globe jumps to the region instead. - The default texture loads from the three-globe CDN; pass
textureUrl/bumpUrlto self-host. - Note the size trade-off: globe.gl brings Three.js (~160 kB gzip); use RegionSelect’s flat map where that’s too heavy.
- Caption ping readouts should be
aria-live="polite", as in the demo.