JavaScriptNavigator & User Agent

The navigator object

window.navigator is a grab-bag of information about the browser, the device and the user's environment. It is the entry point for features like geolocation, clipboard, sharing, media devices and connectivity status. A lot of navigator was historically used to sniff the user agent — a practice you should avoid today. Feature-detect instead.

The userAgent string — and why to ignore it

JS
console.log(navigator.userAgent);
// e.g. "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/605.1.15 ..."

// Don't do this:
if (navigator.userAgent.includes("Chrome")) {
  enableFancyFeature();
}
Avoid user-agent sniffing
UA strings lie. Every browser pretends to be every other browser. They change across versions. Always **feature-detect** the API itself:

JS
// Good — test the feature, not the brand:
if ("share" in navigator) {
  navigator.share({ title: "Hi", url: "/" });
}

if ("clipboard" in navigator && navigator.clipboard.writeText) {
  navigator.clipboard.writeText("copied!");
}

Browsers are also shipping User-Agent Client Hints (navigator.userAgentData) as a structured, privacy-respecting replacement. Adoption is mixed, especially on Safari/iOS, so still feature-detect.

Language and locale

JS
navigator.language;    // "en-GB" — top preferred language
navigator.languages;   // ["en-GB", "en", "fr-FR"] — full ordered list

// Use these as a *default*, not as a lock:
const formatter = new Intl.NumberFormat(navigator.language);
formatter.format(1234567.89);
1,234,567.89
Online / offline status

navigator.onLine reports whether the browser thinks it has a network connection. It is a hint — true does not guarantee the internet is reachable; the device could be on a LAN with no upstream. Pair it with the online/offline events and a real request when it matters.

JS
console.log("online?", navigator.onLine);

addEventListener("online",  () => render({ banner: null }));
addEventListener("offline", () => render({ banner: "You are offline" }));
The Clipboard API

JS
// Write — requires a user gesture (click, key) in most browsers:
async function copy(text) {
  try {
    await navigator.clipboard.writeText(text);
    toast("Copied to clipboard");
  } catch {
    toast("Copy failed — clipboard permission denied");
  }
}

// Read — requires explicit permission, often a permission prompt:
async function paste() {
  const text = await navigator.clipboard.readText();
  console.log("From clipboard:", text);
}
Note
Safari/iOS is the strictest: `writeText` only works while a user gesture is still on the stack, and `readText` is gated behind a permission prompt. Always wrap calls in try/catch.
Web Share API

JS
async function shareThis() {
  if (!navigator.share) {
    fallbackCopyLink();
    return;
  }
  try {
    await navigator.share({
      title: "Check this out",
      text: "Great article on web APIs",
      url: location.href,
    });
  } catch (err) {
    // User cancelled — that throws AbortError; ignore.
    if (err.name !== "AbortError") console.error(err);
  }
}

Web Share is well supported on mobile and macOS Safari. On desktop Chrome/Firefox it may be missing — always feature-detect and provide a fallback (copy link, mailto).

Geolocation

JS
if ("geolocation" in navigator) {
  navigator.geolocation.getCurrentPosition(
    (pos) => {
      const { latitude, longitude, accuracy } = pos.coords;
      console.log(`${latitude}, ${longitude} (±${accuracy}m)`);
    },
    (err) => console.error("geolocation error", err.message),
    { enableHighAccuracy: true, timeout: 5000, maximumAge: 60_000 }
  );
}

Detailed coverage on the Geolocation page.

Media devices: camera and microphone

JS
async function startCamera() {
  const stream = await navigator.mediaDevices.getUserMedia({
    video: { facingMode: "user" },
    audio: true,
  });
  document.querySelector("video").srcObject = stream;
}

// List devices (labels are empty until permission is granted):
const devices = await navigator.mediaDevices.enumerateDevices();
console.log(devices.map((d) => `${d.kind}: ${d.label}`));
What to sniff (and what not to)
  • Sniff features, not browsers. if ("share" in navigator) is good. if (isChrome) is not.

  • Use navigator.language only for defaults. Let users override the locale in your UI.

  • Treat navigator.onLine as a hint, not a guarantee.

  • Avoid navigator.userAgent for capability decisions. Use it at most for analytics, and prefer userAgentData where available.

  • Request permissions on user actions, not on page load. Browsers (especially Safari) will silently deny permissions otherwise.

Browser quirks worth knowing
Safari and iOS WebKit lag on several navigator APIs: `navigator.clipboard.readText` is restricted, `navigator.share` is only available on https origins, and `navigator.userAgentData` is not implemented. Plan for graceful fallbacks instead of assuming Chrome behaviour.