跪拜 Guibai
← All articles
Frontend · iOS · Safari

iOS Safari’s Clipboard Permission Dialog Has a One-Line Fix

By Wilson王艺谋 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A permission dialog on copy breaks UI flow and confuses users. This technique removes the dialog without any polyfill, keeping async clipboard writes seamless on iOS Safari.

Summary

iOS Safari enforces a stricter clipboard security model than other browsers: `navigator.clipboard.write()` must execute synchronously within a user gesture, or the system shows a confirmation dialog. The common mistake is awaiting an async fetch before constructing the `ClipboardItem`, which drops the gesture context and triggers the prompt. The fix is to construct the `ClipboardItem` immediately inside the click handler and supply a `Promise<Blob>` as the value. The browser reserves the write at that moment and resolves the data later, skipping the permission popup. A wrapper function that accepts a string, a Promise, or a lazy function normalizes this pattern and falls back to `execCommand('copy')` when the Clipboard API is unavailable or fails.

Takeaways
`clipboard.write()` in iOS Safari must be called inside the synchronous execution context of a user click; any `await` before the call loses that context and shows a permission dialog.
Constructing a `ClipboardItem` immediately within the gesture and passing a `Promise<Blob>` reserves the write operation, letting the browser resolve the data later without a prompt.
`clipboard.writeText()` has a more lenient permission model on iOS Safari and is the simpler choice for synchronous plain-text copies.
A reusable `copyToClipboard` wrapper can accept a string, a Promise, or a lazy function, always constructing the `ClipboardItem` synchronously and falling back to `execCommand('copy')` when needed.
The same pattern applies to image copies: create the `ClipboardItem` synchronously inside the user gesture, even when the image blob is fetched asynchronously.
Conclusions

The permission dialog is not a blanket iOS restriction but a narrow timing check — the browser only cares whether the `ClipboardItem` is created during the gesture, not when the data actually arrives.

This behavior turns the `ClipboardItem` constructor into a capability-registration point: call it synchronously to claim the clipboard slot, then fill in the payload later.

Many clipboard wrappers in the wild still `await` data before calling `write()`, which means a large portion of web apps are unnecessarily showing permission prompts to iOS users.

Concepts & terms
ClipboardItem constructor Promise support
The `ClipboardItem` constructor accepts `Promise<Blob>` (and in some browsers `Promise<string>`) as a value. Creating the item synchronously inside a user gesture registers the write intent immediately; the browser waits for the Promise to resolve before placing the data on the clipboard, avoiding iOS Safari’s permission dialog.
From the discussion
Featured comments
szp2005 1 likes

There's also a pitfall on the read side: clipboard.read() also triggers the permission prompt. Switching to listening for the paste event and picking image/* from clipboardData.items to get a File bypasses that API. On the write side, we happened to step on the wrong example you mentioned — will fix it later.

Wilson王艺谋

Productive exchange [handshake]

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗