10LOC
Web Platformintermediate

The Web Share API for a native share sheet

Published December 29, 2026

export const shareOrCopy = async (data: ShareData) => {
  if (!navigator.canShare?.(data)) {
    await navigator.clipboard.writeText(data.url ?? data.text ?? "");
    return "copied";
  }

  try {
    await navigator.share(data);
    return "shared";
  } catch (err) {
    if ((err as Error).name === "AbortError") return "cancelled";
    throw err;
  }
};

What

shareOrCopy opens the device's native share sheet (the same one a native app gets) with a title, text, and URL, and falls back to copying the URL to the clipboard on browsers that don't support sharing.

Why it matters

navigator.share() hands off to whatever the OS offers — Messages, Mail, AirDrop, a social app, "copy link" — instead of the page building its own "share to X, Y, Z" menu and maintaining a list of icons for services that change constantly. But support isn't universal: Chrome, Safari, and mobile Firefox implement it, desktop Firefox still doesn't. Calling navigator.share() where it doesn't exist throws immediately, so any real usage needs a fallback path, not just a feature check that silently does nothing.

navigator.canShare() conveniently does double duty here: it's undefined (and so optional-chains to undefined, a falsy value) in browsers without the API at all, and it's a real false in browsers that have the API but were given data they can't share — so one check covers both "unsupported" and "unshareable" and routes both into the same fallback.

How it works

  • navigator.canShare?.(data) is the single feature-and-validity check: ?. skips the call entirely (short-circuiting to undefined) where navigator.canShare doesn't exist, and the call itself returns false for data the browser recognizes but can't share.
  • When that check fails, the fallback copies data.url (or data.text) to the clipboard with navigator.clipboard.writeText() and returns "copied" so the caller can show "Link copied" instead of a share sheet.
  • When it passes, navigator.share(data) triggers the OS share sheet. It must be called from within a user gesture (a click handler, not a setTimeout or a promise callback) — browsers require transient activation for this specific API.
  • The catch block treats AbortError as expected, not a failure: it's exactly what navigator.share() rejects with when the user dismisses the share sheet without picking anything. Every other rejection re-throws, so real failures (bad permissions policy, malformed data) aren't silently swallowed.
  • This is 13 lines against the repo's 6-11 target, hence lineWaiver: true — the AbortError branch is exactly the kind of error handling this blog's readers will copy-paste as-is, and dropping it would make every user-cancelled share look like a bug.

Gotchas

  • Requires a secure context and, per the Permissions Policy spec, the web-share feature not being disabled by an embedding page — sharing from inside a cross-origin iframe can silently fail that policy check.
  • canShare() validates the shape of data (e.g. rejecting an empty object, or files the browser can't handle) — it's not just an availability check, so always gate the real share() call behind it rather than a bare typeof navigator.share === "function" test.

Related

  • navigator.clipboard.writeText() — the fallback used here, and the right primary choice when there's no native share sheet to defer to at all.
  • The File System Access API — a different native-integration API, for reading/writing local files rather than handing content to another app.