Call document.execCommand('copy') synchronously inside the click handler first; use navigator.clipboard.writeText only as a fallback. The viewer delegates clipboard-write, but old browsers and other embedders can still withhold the async API. Awaiting it first can lose the user gesture.
Build an off-screen textarea, select the whole value, and remove it after the attempt. A 16px font avoids iOS zoom. Give the button id="copy" and the text block id="prompt":
function copyText(text) {
const area = document.createElement('textarea');
area.value = text;
area.style.cssText = 'position:fixed;left:-9999px;top:0;width:1px;height:1px;opacity:0;font-size:16px';
area.setAttribute('readonly', '');
const focused = document.activeElement;
let copied = false;
try {
document.body.appendChild(area);
area.select();
area.setSelectionRange(0, text.length);
area.removeAttribute('readonly');
copied = document.execCommand('copy');
} catch (_) {
// Try the async API only after the synchronous attempt.
} finally {
area.remove();
if (focused && focused.focus) focused.focus({ preventScroll: true });
}
if (copied) return Promise.resolve();
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
}
return Promise.reject(new Error('Copy unavailable'));
}
const button = document.querySelector('#copy');
button.addEventListener('click', () => {
// Read the text now; do not await a fetch before calling copyText.
copyText(document.querySelector('#prompt').textContent).then(
() => { button.textContent = 'Copied ✓'; },
() => { button.textContent = 'Could not copy — try another browser'; }
);
});
Show “Copied” only after a successful write. Merely selecting the text leaves phone readers stuck: programmatic selection raises no native Copy menu on touch. Show an explicit failure if both writes fail. A copy button needs clipboard-write only, not clipboard-read.
Was this page helpful?