Sign in with a planet.

An interactive globe that detects a visitor's timezone, language and location — so your app greets them right from the first second.

$ npm i @planetlogin/planetlogin
↻ drag the globe or search a place

Why PlanetLogin

A locale picker that feels like magic.

One pick on a real 3D globe and you know who just arrived — no IP lookups, no consent banners, no third-party SDK.

A real globe

Orthographic projection via d3-geo — proper hemisphere clipping, drag with inertia, wheel zoom, click a country.

TZ · language · place

Returns IANA timezone, BCP-47 language and ISO country from a single pick. ~150 countries mapped to a language.

No key, no tracking

Keyless geocoding (Open-Meteo + OSM Nominatim). No analytics, no cookies beyond your own. AGPL-3.0.

Drops in anywhere

Ships as a standard Web Component, a class and a factory. React, Vue, Svelte, Angular or plain HTML. ~17 kB gzip.

Accessible

Keyboard-controllable globe (arrows, +/-, Enter), focus ring, ARIA labels, and respects prefers-reduced-motion.

TypeScript-first

Typed API, ESM + UMD builds, and a tiny readable codebase you can fork and own.

Quickstart

Two lines to a localized welcome.

Listen for one locale event and wire it into your i18n and date handling.

import '@planetlogin/planetlogin';

<!-- anywhere in your HTML -->
<planet-login accent="#f6a13c"></planet-login>

const el = document.querySelector('planet-login');
el.addEventListener('locale', (e) => {
  const { language, timezone, country } = e.detail;
  i18n.setLanguage(language);
});
import { useEffect, useRef } from 'react';
import { createPlanetLogin } from '@planetlogin/planetlogin';

function GlobeLogin({ onLocale }) {
  const ref = useRef(null);
  useEffect(() => {
    const g = createPlanetLogin(ref.current, { onLocale });
    return () => g.destroy();
  }, []);
  return <div ref={ref} style={{ height: 440 }} />;
}
import { onMounted, onBeforeUnmount, ref } from 'vue';
import { createPlanetLogin } from '@planetlogin/planetlogin';

const el = ref();
let g;
onMounted(() => { g = createPlanetLogin(el.value, { onLocale: l => emit('locale', l) }); });
onBeforeUnmount(() => g?.destroy());
import { onDestroy } from 'svelte';
import { createPlanetLogin } from '@planetlogin/planetlogin';

let el, g;
$: if (el && !g) g = createPlanetLogin(el, { onLocale });
onDestroy(() => g?.destroy());

The payload

Everything you need, on one event.

Delivered three ways: the locale DOM event, the on('locale', …) listener, or the onLocale option.

interface PlanetLocale {
  lat: number; lon: number;
  country: string;        // ISO 3166-1 alpha-2
  timezone: string;       // "Europe/Madrid" or "UTC±N"
  language: string;       // "es"
  label: string;          // "Barcelona, Spain"
}

Open source

Free, copyleft, community-built.

AGPL-3.0 with a small attribution term. Free for any use, including commercial. Issues and PRs welcome.