Build pages › Copy buttons
Docs › Build pages › Copy buttons

Copy buttons

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?