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 toundefined) wherenavigator.canSharedoesn't exist, and the call itself returnsfalsefor data the browser recognizes but can't share.- When that check fails, the fallback copies
data.url(ordata.text) to the clipboard withnavigator.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 asetTimeoutor a promise callback) — browsers require transient activation for this specific API. - The
catchblock treatsAbortErroras expected, not a failure: it's exactly whatnavigator.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— theAbortErrorbranch 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-sharefeature not being disabled by an embedding page — sharing from inside a cross-origin iframe can silently fail that policy check. canShare()validates the shape ofdata(e.g. rejecting an empty object, orfilesthe browser can't handle) — it's not just an availability check, so always gate the realshare()call behind it rather than a baretypeof 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.