Copy Button

@use 'sprucecss/scss/spruce' as *;

.copy-field {
  @include layout-stack('xs');

  &__input {
    font-family: config('font-family-mono', $typography);
  }
}

.copy-button {
  flex-shrink: 0;
  min-inline-size: 6.5rem;

  &.is-copied {
    background-color: color('success', 'alert');
    border-color: color('success', 'alert');
  }
}
<div class="copy-field">
    <label class="form-label" for="install-command">Install command</label>
    <div class="form-group--stacked">
        <input class="form-control copy-field__input" id="install-command" type="text" value="npm install sprucecss" readonly>
        <button class="btn btn--primary copy-button" type="button" data-copy-target="install-command">
            <svg class="btn__icon copy-button__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><rect width="14" height="14" x="8" y="8" rx="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
            <span class="copy-button__label">Copy</span>
        </button>
    </div>
    <p class="sr-only" aria-live="polite" data-copy-status></p>
</div>
(() => {
  document.querySelectorAll('[data-copy-target], [data-copy-text]').forEach((button) => {
    const label = button.querySelector('.copy-button__label') ?? button;
    const status = button.closest('.copy-field')?.querySelector('[data-copy-status]');
    const original = label.textContent;

    button.addEventListener('click', async () => {
      const target = document.getElementById(button.dataset.copyTarget);
      const text = button.dataset.copyText ?? target?.value ?? target?.textContent ?? '';

      try {
        await navigator.clipboard.writeText(text);
        label.textContent = 'Copied!';
        button.classList.add('is-copied');
        if (status) status.textContent = 'Copied to clipboard.';

        setTimeout(() => {
          label.textContent = original;
          button.classList.remove('is-copied');
        }, 2000);
      } catch (error) {
        target?.select?.();
        if (status) status.textContent = 'Copy failed. The text is selected; press Ctrl+C to copy it.';
      }
    });
  });
})();

Copy Button

On this page

A button that copies a value (an install command, an API key, a link) to the clipboard and confirms it.

Technical Details

  • It uses the async Clipboard API (navigator.clipboard.writeText), which needs a secure context (HTTPS or localhost); if it fails, the text is selected so the user can copy it manually.
  • The visible label changes to "Copied!" for two seconds, and the result is announced in a polite live region.
  • Point the button to any element with data-copy-target="id": it copies the value of form fields or the text of other elements. Or set the text directly with data-copy-text.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.