Accessibility pattern · Buttons and actions

Copy to clipboard

Each button keeps one name, such as Copy API key, while its face turns to a tick and Copied for two seconds. A polite status says Copied to clipboard; if the browser blocks the clipboard, the text is selected and a message says how to copy it by hand.

WCAG criteria
6
Keyboard rules
3
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Connect the checker

Install the package, then paste your key into its settings.

Install command

npm install --save-dev axe-core

Keep it secret: anyone with this key can send reports to your account.

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
TabMoves through the read-only fields and their Copy buttons in reading order.
Enter or SpaceOn a Copy button, copies its text; the button shows Copied for two seconds and the result is announced.
CtrlC or ⌘CCopies by hand. After a blocked copy the text is already selected, so this is the only step left.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Focus reaches the API key's buttonCopy API key, button
The copy worksCopied to clipboard
The clipboard is blockedCouldn't copy automatically. The text is selected: press Ctrl+C, or ⌘C on a Mac.
Focus reaches the API key fieldAPI key, edit text, read only, ak_live_7Hq2Ns9Vx4Lm8Pz3Rt6Wb1Ky. Keep it secret: anyone with this key can send reports to your account.

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-copy-button" data-ap-copy-button>
  <div class="ap-copy-button__top">
    <h3 class="ap-copy-button__label">Connect the checker</h3>
    <p class="ap-copy-button__lead">Install the package, then paste your key into its settings.</p>
  </div>

  <div class="ap-copy-button__item">
    <p class="ap-label">Install command</p>
    <div class="ap-copy-button__code">
      <pre class="ap-copy-button__pre"><code id="copy-button-cmd">npm install --save-dev axe-core</code></pre>
      <button type="button" class="ap-copy-button__btn" data-ap-source="copy-button-cmd" aria-label="Copy install command">
        <svg class="ap-copy-button__icon ap-copy-button__icon--copy" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>
        <svg class="ap-copy-button__icon ap-copy-button__icon--done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
        <span class="ap-copy-button__word">Copy</span>
      </button>
    </div>
    <p class="ap-copy-button__fail" hidden>Couldn't copy automatically. The text is selected: press Ctrl+C, or ⌘C on a Mac.</p>
  </div>

  <div class="ap-copy-button__item">
    <label class="ap-label" for="copy-button-key">API key</label>
    <div class="ap-copy-button__group">
      <input class="ap-input ap-copy-button__value" id="copy-button-key" type="text" value="ak_live_7Hq2Ns9Vx4Lm8Pz3Rt6Wb1Ky" readonly aria-describedby="copy-button-key-hint" />
      <button type="button" class="ap-copy-button__btn" data-ap-source="copy-button-key" aria-label="Copy API key">
        <svg class="ap-copy-button__icon ap-copy-button__icon--copy" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>
        <svg class="ap-copy-button__icon ap-copy-button__icon--done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
        <span class="ap-copy-button__word">Copy</span>
      </button>
    </div>
    <p class="ap-hint" id="copy-button-key-hint">Keep it secret: anyone with this key can send reports to your account.</p>
    <p class="ap-copy-button__fail" hidden>Couldn't copy automatically. The text is selected: press Ctrl+C, or ⌘C on a Mac.</p>
  </div>

  <div class="ap-copy-button__item">
    <label class="ap-label" for="copy-button-link">Share link</label>
    <div class="ap-copy-button__group">
      <input class="ap-input ap-copy-button__value" id="copy-button-link" type="text" value="https://auricartisan.com/r/q4-audit" readonly />
      <button type="button" class="ap-copy-button__btn" data-ap-source="copy-button-link" aria-label="Copy share link">
        <svg class="ap-copy-button__icon ap-copy-button__icon--copy" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>
        <svg class="ap-copy-button__icon ap-copy-button__icon--done" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg>
        <span class="ap-copy-button__word">Copy</span>
      </button>
    </div>
    <p class="ap-copy-button__fail" hidden>Couldn't copy automatically. The text is selected: press Ctrl+C, or ⌘C on a Mac.</p>
  </div>

  <p class="ap-copy-button__say" role="status"></p>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    Every value has a visible label tied to it, and the key's warning is joined to its field with aria-describedby.

  • 1.4.1 Use of Color Level A

    A copy is confirmed by a tick and the word Copied, not only by the button turning green.

  • 2.1.1 Keyboard Level A

    Every copy works from the keyboard, and the read-only fields can be selected and copied by hand.

  • 2.5.3 Label in Name Level A

    Each button's name starts with the word on it, Copy, so a voice user can say Copy API key.

  • 4.1.2 Name, Role, Value Level A

    The name stays Copy API key while the face changes, so the button is always announced as the same control.

  • 4.1.3 Status Messages Level AA

    Copied to clipboard and the fallback instructions come from a polite status, announced without moving focus.

Usage

When to use it

Use it

  • Strings people paste somewhere else and easily mistype: commands, keys, tokens, links and codes.
  • Long or random values that are hard to select exactly on a touch screen.

Use something else

  • Short text that is just as easy to select, where a button only adds clutter.
  • Copying without a clear request: never write to the clipboard on page load, on focus or on hover.

Common failures

How it usually goes wrong

  • Silent success

    A copy that changes nothing on screen and says nothing leaves everyone guessing whether it worked. The button shows Copied and a status announces it.

  • Renaming the button to Copied

    Changing the name of a focused button is announced unreliably, and voice users lose the name they were about to say. The name stays; the status speaks.

  • A page of buttons all called Copy

    In a screen reader's list of buttons, Copy, Copy, Copy is no help. Each name says what it copies.

  • Failing quietly

    The clipboard API is refused on insecure pages, in some frames and when permission is denied. Here the text is selected and a message says how to copy it.

  • Color as the only signal

    Green alone is missed by many people. The tick and the word Copied carry the same news.

Notes

Building it

  • navigator.clipboard.writeText() needs a secure context and a user action; call it straight from the click handler.
  • document.execCommand("copy") is deprecated, so it is not used here; selecting the text and asking for Ctrl+C works everywhere.
  • The status is emptied and filled again on every copy, so copying the same thing twice is announced twice.
  • The aria-label starts with the visible word, Copy. While the face says Copied the name does not change, which is the point: the status reports the result.
  • Turn on Block the clipboard above to see the fallback: the message appears under the value and its text is selected. A field also takes focus, because its text can only be selected while it has focus.

Sources: W3C: Clipboard API and events · Understanding WCAG 2.2: Status Messages

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction