beta

Globe — <mv-globe>

Interactive dotted globe on 2D canvas (orthographic projection of an embedded land mask, zero dependencies): auto-rotation, drag with inertia, pulsing markers, great-circle arcs animated like comets, and a halo and atmospheric rim in your token colors.

Category3D
TypeWeb Component (<mv-globe>)
Statusbeta
Keywordsglobe, earth, map, 3d, canvas, arcs, markers, cobe, hero

When to use

Avoid when

Install

node scripts/add.mjs globe --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["globe"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, core/canvas.js, core/dom.js, core/element.js, core/motion.js, core/pointer.js, components/globe/globe.js, components/globe/geo.js, components/globe/globe-land.js, components/globe/globe.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<section style="position:relative;display:grid;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr));align-items:center;justify-items:center;gap:1.5rem;padding:2rem clamp(1rem,4vw,3rem);min-height:560px;overflow:hidden">
  <div style="max-width:28rem;justify-self:start">
    <p style="margin:0 0 .6rem;font:600 .75rem/1 var(--mv-font-mono);letter-spacing:.12em;text-transform:uppercase;color:var(--mv-accent-fg)">Global network</p>
    <h2 style="margin:0;font:700 clamp(1.8rem,3.4vw,2.6rem)/1.08 var(--mv-font-sans);letter-spacing:-.03em">Your data, right next to your customers</h2>
    <p style="margin:1rem 0 1.4rem;color:var(--mv-fg-muted);line-height:1.6">Seven regions, 38&nbsp;ms median latency. Spin the globe or pick a region.</p>
    <div id="globe-demo-regions" style="display:flex;flex-wrap:wrap;gap:.4rem">
      <button type="button" class="mv-button" data-variant="outline" data-size="sm" data-lat="48.86" data-lng="2.35">Paris</button>
      <button type="button" class="mv-button" data-variant="outline" data-size="sm" data-lat="40.71" data-lng="-74.01">New York</button>
      <button type="button" class="mv-button" data-variant="outline" data-size="sm" data-lat="-23.55" data-lng="-46.63">São Paulo</button>
      <button type="button" class="mv-button" data-variant="outline" data-size="sm" data-lat="35.68" data-lng="139.69">Tokyo</button>
      <button type="button" class="mv-button" data-variant="outline" data-size="sm" data-lat="1.35" data-lng="103.82">Singapore</button>
      <button type="button" class="mv-button" data-variant="outline" data-size="sm" data-lat="-33.87" data-lng="151.21">Sydney</button>
    </div>
    <p id="globe-demo-out" aria-live="polite" style="margin:1rem 0 0;min-height:1.2em;font-size:.85rem;color:var(--mv-fg-muted)"></p>
  </div>
  <mv-globe id="globe-demo" label="Network regions" labels center="28,-10"
    markers="48.86,2.35,Paris; 40.71,-74.01,New York; -23.55,-46.63,São Paulo; 35.68,139.69,Tokyo; 1.35,103.82,Singapore; -33.87,151.21,Sydney; 19.08,72.88,Mumbai"
    arcs="Paris>New York; Paris>Mumbai; New York>São Paulo; Mumbai>Singapore; Singapore>Tokyo; Singapore>Sydney"></mv-globe>
</section>
<script type="module">
  const globe = document.getElementById("globe-demo");
  const out = document.getElementById("globe-demo-out");
  document.getElementById("globe-demo-regions").addEventListener("click", (e) => {
    const b = e.target.closest("button[data-lat]");
    if (!b) return;
    globe.rotateTo(b.dataset.lat, b.dataset.lng);
    out.textContent = `${b.textContent} region — 3 availability zones`;
  });
  globe.addEventListener("mv-select", (e) => { out.textContent = `Marker: ${e.detail.marker.label}`; });
</script>

API

Attributes

NameTypeDefaultDescription
markersstring“lat,lng,Label; lat,lng,Label” (the label is optional). The property also accepts an array [{ lat, lng, label }].
arcsstringAnimated arcs between markers: “0>1; Paris>Tokyo” (indices or labels) or the shortcuts hub (first to all), chain (one after another), all. The property accepts [[from, to], …].
labelsbooleanAlways show marker labels (otherwise on hover).
speednumber6Auto-rotation in degrees per second (0 = still). Disabled under reduced motion.
tiltnumber18Resting tilt (latitude facing the viewer, in degrees).
centerstring“lat,lng”: point initially facing the viewer (overrides tilt).
samplesnumber20000Points sampled on the sphere (≈ 29% land on continents): dot density.
no-dragbooleanDisables dragging and keyboard control.
labelstringEarth globeStart of the accessible name (followed by the marker list).

Properties

NameTypeDescription
markersArray<{lat, lng, label}>Read: parsed markers; write: array or string.
arcsArray<[number, number]>Resolved index pairs; write: array or string.

Methods

NameDescription
rotateTo(lat, lng)Smoothly rotates until that point faces the viewer.

Events

NameDescription
mv-selectClick on a visible marker — detail: { index, marker }.

CSS variables

NameDefaultDescription
--mv-globe-size34remMax width (the globe stays square).
--mv-globe-dotfg 62%Land dot color.
--mv-globe-markervar(--mv-accent)Color of markers and their ripples.
--mv-globe-arcvar(--mv-accent)Arc color.
--mv-globe-glowaccent 70%Halo and atmospheric rim.
--mv-globe-oceantinted backgroundSphere disc (oceans).
--mv-globe-label / --mv-globe-font-sizefg / 12pxMarker labels.

Accessibility

role=img with a name that lists the markers. Focusable: ←/→/↑/↓ rotate it (with inertia). Reduced motion: no auto-rotation or animation (solid arcs, static markers), rendered on demand; dragging still works. Paused off-screen and in hidden tabs. touch-action: pan-y so vertical scrolling is never blocked.