Installation
pnpm dlx shadcn@latest add https://dithercn.dev/r/dither-globe.jsonUsage
import { DitherGlobe } from "@/components/dithercn/dither-globe";
const LONDON = [51.51, -0.13] as const;
const TOKYO = [35.68, 139.69] as const;
export function Global() {
return (
<DitherGlobe
markers={[{ at: LONDON }, { at: TOKYO }]}
arcs={[{ from: LONDON, to: TOKYO }]}
className="aspect-square w-full max-w-md"
/>
);
}The globe is drawn on a 2D canvas at dither-cell resolution: every cell on the sphere gets a tone from the land mask and the light, then an ordered threshold decides whether it's inked. Land is bright on the day side and fades to a sparse grid past the terminator; the sea is a faint dither that thickens towards the rim. It uses no WebGL context, so it doesn't count against the page's shader budget.
The globe stays round and centred in its box and takes 84% of the shorter side. Make the box larger than its section and offset it to show only the top of the globe, as in the first example.
Visitors can turn the globe. Dragging sideways spins it and dragging up or down tilts it, with the land staying under the pointer. Let go mid-drag and it coasts to a stop, then picks its idle spin back up. On phones, a horizontal swipe turns it and a vertical swipe still scrolls the page. When it has keyboard focus, the arrow keys turn it. Set draggable={false} for a globe that should only spin on its own.
Land comes from the shared world-land mask (Natural Earth, public domain), installed with the component. Nothing is fetched at runtime.
Examples
Horizon behind a headline
A globe 150% of the section's width, pushed below the bottom edge so only its lit top shows.
Beside copy
A square globe in a two-column section. Drag it to spin.
Turned to one place, in a card
speed={0} and start at the marker's longitude hold one region in view while the marker keeps pulsing.
Props
| Prop | Type | Default |
|---|---|---|
| color Colour of the land, sea and atmosphere. | Type: DitherColor | Default "ink" |
| accent Colour of markers and arcs that don't set their own. | Type: DitherColor | Default "orange" |
| markers Pulsing locations. Markers on the far side are hidden. | Type: { at: [lat, lon]; color?: DitherColor }[] | Default [] |
| arcs Great-circle arcs lifted off the surface, with a stretch of light running along them. | Type: { from: [lat, lon]; to: [lat, lon]; color?: DitherColor }[] | Default [] |
| speed Degrees of rotation per second. Negative spins the other way. | Type: number | Default 6 |
| start Longitude facing the viewer at the start, in degrees. | Type: number | Default 10 |
| tilt How far the north pole leans towards the viewer, in degrees. | Type: number | Default 18 |
| light Where the light comes from, in degrees clockwise from the top. | Type: number | Default 300 |
| ocean Brightness of the sea on the lit side, 0 to 1. | Type: number | Default 0.18 |
| atmosphere A thin dithered glow around the lit rim. | Type: boolean | Default true |
| draggable Drag or use the arrow keys to turn and tilt the globe. It coasts after a drag, then eases back into its idle spin. | Type: boolean | Default true |
| cell Size of one dither cell in CSS pixels. | Type: number | Default 2 |
| matrix Threshold matrix. | Type: "bayer2" | "bayer4" | "bayer8" | "blue-noise" | Default "bayer4" |
| animate Spin and pulse. false draws one still frame. | Type: boolean | Default true |
Notes
- A draggable globe is focusable and announced as an image labelled "Globe. Drag it or use the arrow keys to turn it." Pass your own
aria-labelto say what it shows. Withdraggable={false}it's decorative and hidden from assistive technology, so say what it shows in the copy next to it. - With reduced motion turned on, it draws one still frame. Dragging and the arrow keys still turn it, without the coasting.
- It pauses when scrolled out of view or when the tab is hidden. A 600px globe at 2px cells is about 70,000 cells per frame at 30 fps, well within budget on a phone; raise
cellfor very large globes.