Handling AbortError When the Share Sheet Is Dismissed
This guide is part of Understanding Secure Context Requirements, which itself is part of Web Share API & Security Contexts.
When navigator.share() rejects with AbortError, nothing went wrong. The native share sheet opened, the user looked at it, and they closed it without choosing a target — a swipe-down, a tap outside, or the Cancel button. This is a successful interaction with a negative decision, not an error. Treating it as one produces noisy error logs, unwanted fallback toasts, and analytics that overstate your failure rate. This guide shows how to catch AbortError precisely, keep it out of your error pipeline, and return the UI to idle without a trace.
Feature Detection Gate
Gate the invocation so that the only rejections you ever see are genuine outcomes — a cancel or a real failure — never a call that was doomed from the start:
export function shareAvailable() {
return (
window.isSecureContext === true &&
typeof navigator.share === 'function'
);
}
When shareAvailable() is false, you never call navigator.share(), so you never have to distinguish “unsupported” from “cancelled” inside the catch. That separation keeps the abort-handling logic below clean.
Solution Walkthrough
Step 1 — Catch AbortError and Return Silently
The core pattern is a single guard at the top of the catch block. If the name is AbortError, return immediately — no toast, no fallback, no error log. Everything else falls through to real error handling:
export async function shareContent(payload) {
if (!shareAvailable()) return { status: 'unsupported' };
try {
await navigator.share(payload);
return { status: 'shared' };
} catch (err) {
if (err.name === 'AbortError') {
// User opened the sheet and dismissed it. This is fine.
return { status: 'cancelled' };
}
// Anything else is a genuine failure worth surfacing.
console.error('navigator.share failed:', err.name, err.message);
return { status: 'error', error: err };
}
}
The returned status lets the caller decide what to render. A cancelled result should look identical to the user having never tapped the button. Only error and unsupported justify a fallback path such as the graceful-denial patterns in handling permission denials gracefully.
Step 2 — Record Cancellations at Debug Level Only
Dismissals are useful product signal — a high cancel rate might mean the sheet is triggered too eagerly — but they are not errors and must never enter your error budget or alerting. Route them to a debug-level analytics channel, well below the severity of a real exception:
export function reportShareOutcome(result, context) {
switch (result.status) {
case 'shared':
analytics.track('share_completed', context);
break;
case 'cancelled':
// Debug severity — expected user behaviour, not a failure.
analytics.debug('share_dismissed', context);
break;
case 'error':
analytics.error('share_error', { ...context, name: result.error.name });
break;
// 'unsupported' handled by the fallback UI, not logged as an error.
}
}
If your monitoring aggregates console.error into an error rate, a leaked AbortError can single-handedly make a healthy share feature look broken. Keeping cancellations at debug level protects both your dashboards and your on-call sanity.
Step 3 — Leave the UI in Its Idle State
After a cancel, the button should simply become tappable again. Do not flash a “Share cancelled” message — the user knows they cancelled, and a toast implies something failed. Re-enable the control and stop:
export async function onShareButtonClick(button, payload) {
if (!shareAvailable()) return;
button.disabled = true;
const result = await shareContent(payload);
reportShareOutcome(result, { source: button.dataset.source });
// For 'cancelled' and 'shared' alike, just restore the idle button.
button.disabled = false;
if (result.status === 'error') {
showFallbackChooser(payload); // only real failures get a fallback
}
}
Disabling the button during the call also prevents a double-invocation, which — as noted in fixing NotAllowedError on navigator.share() — can reject the second call while the first sheet is still open.
Failure Modes and Recovery
| Anti-pattern | What the User Sees | Correct Behaviour |
|---|---|---|
Showing an error toast on AbortError |
“Something went wrong” after they chose to cancel | Return to idle silently; no message |
Auto-retrying share() on abort |
The sheet reopens against their will | Never retry a cancel; wait for a fresh tap |
| Routing abort to a copy/SMS fallback | An unwanted fallback overrides their decision | Fallbacks are for error/unsupported, not cancel |
Logging abort via console.error |
Inflated error rate, false alerts | Log at debug level as expected behaviour |
Conflating AbortError with NotAllowedError |
Real gesture bugs hidden inside “user cancelled” | Branch on error.name; handle each distinctly |
| Leaving the button disabled after cancel | Dead button; user can’t retry | Re-enable in every settlement path |
Browser and Platform Caveat
Across Chrome, Edge, and Safari, a deliberate dismissal of the share sheet yields AbortError with error.name === 'AbortError'. iOS Safari has one wrinkle worth handling defensively: if the tab is backgrounded or loses focus while the sheet is presented, the rejection can arrive as NotAllowedError instead. Because both names can result from the same user action on iOS, if you have already confirmed the sheet opened, it is reasonable to treat a subsequent NotAllowedError as a soft cancel rather than forcing a fallback. On Android Chromium the mapping is consistent — dismissal is always AbortError — so this ambiguity is an iOS-specific concern rather than a universal one.
FAQ
Is AbortError from navigator.share() a bug I need to fix?
No. AbortError means the user opened the native share sheet and dismissed it without picking a target. It is the expected result of a cancel, so swallow it silently. Only rejections whose error.name is something other than AbortError indicate a problem you should investigate.
Should I show a fallback like copy-to-clipboard when I catch AbortError?
No. The user saw the sheet and deliberately closed it, so surfacing a fallback overrides their choice and reads as nagging. Reserve fallbacks for cases where the sheet never appeared — an unsupported browser, or a NotAllowedError from a lost gesture. On AbortError, return to idle and do nothing.
How do I tell AbortError apart from NotAllowedError?
Read error.name in the catch block. AbortError means the sheet opened and the user cancelled, so do nothing. NotAllowedError means the sheet never opened — a lost gesture or a policy block — and is a defect to fix. Never merge both into one generic handler, or you will hide real bugs behind normal cancellations.
Why does dismissing the sheet sometimes throw NotAllowedError on iOS instead of AbortError?
On iOS Safari, if the tab is backgrounded or loses focus while the sheet is up, the rejection can surface as NotAllowedError rather than AbortError. Since both can stem from the user closing the sheet on iOS, treat a NotAllowedError that follows a confirmed sheet-open as a non-fatal cancel too, instead of forcing a fallback the user did not ask for.