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.
| Category | 3D |
|---|---|
| Type | Web Component (<mv-globe>) |
| Status | beta |
| Keywords | globe, earth, map, 3d, canvas, arcs, markers, cobe, hero |
When to use
- A hero section for a global product needs an interactive earth with markers and animated arcs
- Visitors should drag or keyboard-rotate the globe to explore locations
- Clicking a location marker should trigger an action, such as showing office details
Avoid when
- The page already runs other canvas or WebGL animations and low-end mobile is a key audience
Install
node scripts/add.mjs globe --out ./src/marvelousAI 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 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
| Name | Type | Default | Description |
|---|---|---|---|
markers | string | “lat,lng,Label; lat,lng,Label” (the label is optional). The property also accepts an array [{ lat, lng, label }]. | |
arcs | string | Animated 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], …]. | |
labels | boolean | Always show marker labels (otherwise on hover). | |
speed | number | 6 | Auto-rotation in degrees per second (0 = still). Disabled under reduced motion. |
tilt | number | 18 | Resting tilt (latitude facing the viewer, in degrees). |
center | string | “lat,lng”: point initially facing the viewer (overrides tilt). | |
samples | number | 20000 | Points sampled on the sphere (≈ 29% land on continents): dot density. |
no-drag | boolean | Disables dragging and keyboard control. | |
label | string | Earth globe | Start of the accessible name (followed by the marker list). |
Properties
| Name | Type | Description |
|---|---|---|
markers | Array<{lat, lng, label}> | Read: parsed markers; write: array or string. |
arcs | Array<[number, number]> | Resolved index pairs; write: array or string. |
Methods
| Name | Description |
|---|---|
rotateTo(lat, lng) | Smoothly rotates until that point faces the viewer. |
Events
| Name | Description |
|---|---|
mv-select | Click on a visible marker — detail: { index, marker }. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-globe-size | 34rem | Max width (the globe stays square). |
--mv-globe-dot | fg 62% | Land dot color. |
--mv-globe-marker | var(--mv-accent) | Color of markers and their ripples. |
--mv-globe-arc | var(--mv-accent) | Arc color. |
--mv-globe-glow | accent 70% | Halo and atmospheric rim. |
--mv-globe-ocean | tinted background | Sphere disc (oceans). |
--mv-globe-label / --mv-globe-font-size | fg / 12px | Marker 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.