Those Share Buttons Are Guessing What's On My Phone
The Row That Never Keeps Up
Scroll to the bottom of almost any blog post and you'll find the same five icons: a bird that got rebranded two years ago, a lowercase "f" in a circle, a chain-link for "copy URL," maybe an envelope. Someone built that row once, for the apps that mattered at the time, and it has been quietly lying to every reader since. It doesn't know if you have Twitter - sorry, X - installed. It doesn't know you'd rather send this to yourself on WhatsApp, or drop it in the Notes app, or AirDrop it to the laptop next to you. It just shows you five guesses and hopes one lands. There's a three-line replacement that stops guessing and asks your phone directly. Most developers have never reached for it, because for years it only half-worked. That's no longer true - and it can do something the icon row structurally can't.
The obvious fix, the one every project reaches for first, is to keep adding icons. Someone requests a WhatsApp button. Then Threads. Then someone on the team points out half your readers are on Reddit, so that's an icon too. Each one needs its own share-URL format memorized or copy-pasted from a Stack Overflow answer:
<a href="https://twitter.com/intent/tweet?text=...&url=...">Share on X</a>
<a href="https://www.facebook.com/sharer/sharer.php?u=...">Share on Facebook</a>
<a href="https://api.whatsapp.com/send?text=...">Share on WhatsApp</a>
This works, in the sense that clicking it opens something. But it's a maintenance tax with no ceiling - a new network shows up, you add a link; a network dies or rebrands, you go find every place you hardcoded its old URL scheme. And no matter how many icons you add, the row is finite while the list of apps on someone's phone isn't.
Where It Actually Falls Apart
- It can only ever be a link. Every one of those
<a>tags is built to hand off a URL. There's no icon for "share the PDF I just generated" or "share this canvas as an image" - a link-based row has no mechanism for that at all. - It never matches the reader's actual apps. Messages, Notes, AirDrop, Slack, a note-taking app, a second messenger - none of them show up, because the row was hardcoded to the networks someone thought of in advance.
- It's a tracking and consent question you didn't sign up for. Some of those share-URL patterns silently hand the destination site data about the page you're on before the reader has clicked anything.
- It ages badly, publicly. A dead rebrand icon at the bottom of a post is a small, visible signal that nobody's touched this part of the site in years.
Ask the OS Instead
The Web Share API skips the guessing entirely. Instead of a fixed row, you hand the browser a small object and let the operating system show the reader their actual share sheet - whatever's really installed:
async function shareThisPage() {
try {
await navigator.share({
title: document.title,
text: "Worth a read:",
url: location.href,
});
} catch (err) {
console.error(`${err.name}: ${err.message}`);
}
}
shareButton.addEventListener("click", shareThisPage);
navigator.share() returns a promise that resolves once the reader picks something from the sheet - Messages, WhatsApp, AirDrop, whatever they actually have. It needs two things to work: a secure context (HTTPS or localhost) and a real user gesture, so it has to be called from inside a click handler, not on page load or after some unrelated timer fires.
The Part the Icon Row Can Never Do
Here's the part that isn't just nicer UX - it's a capability gap. navigator.share() also accepts a files array, and once a browser supports it, you can hand the OS an actual File object: an image you generated on a <canvas>, a PDF you built client-side, a screenshot. No <a> tag has ever been able to do that; a link can only point at something, never carry it.
Because file support varies by platform and browser in ways JavaScript can't enumerate up front, check first with canShare() - it's synchronous, so you get a plain boolean back with no promise to unwrap:
const shareData = {
files: [imageFile],
title: "Generated chart",
};
if (navigator.canShare?.(shareData)) {
await navigator.share(shareData);
} else {
// Fall back to a download link - don't guess at what the OS will accept.
}
Don't try to hardcode your own list of "safe" MIME types for this. The spec leaves the exact allowed file types to the browser and OS, and it's shifted over time - canShare() is the one place that question gets answered correctly.
The Gotcha: Canceling Isn't a Failure
Every navigator.share() call you'll find in a hurry looks like this:
navigator.share(data).catch(() => {
showToast("Sharing failed. Please try again.");
});
Try it yourself: click share, then just tap outside the sheet to dismiss it without picking anything. That toast fires anyway - for a reader who did nothing wrong. Canceling the share sheet rejects the promise with an AbortError. That's not a bug and not a failure; it's the single most common outcome, because plenty of people open the sheet just to see what's there. Treating every rejection as an error means you're apologizing to readers for a thing they chose to do on purpose:
navigator.share(data).catch((err) => {
if (err.name === "AbortError") return; // they just closed the sheet - not an error
showToast("Sharing failed. Please try again.");
});
Ship It Without Stranding Anyone
Feature-detect before you touch any of this, and keep a fallback path - this is the one place the old row still earns its keep:
if (navigator.share) {
shareButton.addEventListener("click", shareThisPage);
} else {
// No native share sheet here - show your copy-link / mailto row instead.
legacyShareRow.hidden = false;
}
Support is solid on mobile Safari and Chrome, and it's since spread to desktop Chrome and Edge too, where it opens the OS's own share panel. Firefox has never shipped navigator.share() - check caniuse.com for the current picture before you rely on it, and keep the fallback branch rather than assuming everyone gets the native sheet.
One more thing worth knowing if you're sharing a generated file: the "user gesture" the API needs can expire if you spend too long generating that file before calling share() - keep the gap between the click and the call as short as you can.
The Row, Retired
Next time you're about to paste in another network's share URL, ask what you're actually trying to do: give the reader a way to share, or give them their way. The five-icon row was always a stand-in for an answer the OS could give directly - it just took the platform a while to offer it. Still got a Twitter bird in your footer? Go check - I'll bet it's still pointing at the old URL scheme.
Comments
No comments yet. Start the discussion.