Copying to Clipboard Using Vanilla JavaScript

We covered a great little library, Clipboard.js, that makes it easy to copy text to the clipboard without the need for Flash. We also covered vue-clipboard2 to use Clipboard.js with Vue. The thing is though, you can now pretty easily implement the feature without the need for a library at all, thanks to the the document’s execCommand method. Support for it is also really good now.

To make it work, behind the scenes we select the content to be copied, then run the copy command on that text and then finally remove the selection.

A Working Example

I recently implemented this for the style guide, to allow to easily copy color codes. Try it by clicking on the color values to copy to clipboard:

#FAE042
rgba(250,224,66,1)
#EFBB35
rgba(239,187,53,1)
#DFA612
rgba(223,166,18,1)

Can't copy, hit Ctrl+C!

Here’s the markup:

<div class="colors">
  <div class="color">
    <div style="background: #FAE042;"></div>
    <span>#FAE042</span><br>
    <span>rgba(250,224,66,1)</span>
  </div>

  <div class="color">
    <div style="background: #EFBB35;"></div>
    <span>#EFBB35</span><br>
    <span>rgba(239,187,53,1)</span>
  </div>

  <div class="color">
    <div style="background: #DFA612;"></div>
    <span>#DFA612</span><br>
    <span>rgba(223,166,18,1)</span>
  </div>
</div>

<p class="error-msg">
  Can't copy, hit Ctrl+C!
</p>

And here’s the simple script to make the copy to clipboard feature possible:

const aioColors = document.querySelectorAll('.color span');

aioColors.forEach(color => {
  color.addEventListener('click', () => {
    const selection = window.getSelection();
    const range = document.createRange();
    range.selectNodeContents(color);
    selection.removeAllRanges();
    selection.addRange(range);

    try {
      document.execCommand('copy');
      selection.removeAllRanges();

      const original = color.textContent;
      color.textContent = 'Copied!';
      color.classList.add('success');

      setTimeout(() => {
        color.textContent = original;
        color.classList.remove('success');
      }, 1200);
    } catch(e) {
      const errorMsg = document.querySelector('.error-msg');
      errorMsg.classList.add('show');

      setTimeout(() => {
        errorMsg.classList.remove('show');
      }, 1200);
    }
  });
});

Note that you'd probably want to transpile this JavaScript code using Babel because it uses a lot of new features like the const keyword and arrow functions.

Oh, and by the way, the layout is made super easy thanks to CSS grid:

.colors {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-gap: 1rem;
  text-align: center;
  margin-bottom: 2rem;
}

@media (max-width: 600px) {
  .colors {
    grid-template-columns: 1fr 1fr;
  }
}

Breaking It Down

We first get all the span elements that contain the color values:

const aioColors = document.querySelectorAll('.color span');

querySelectorAll return a nodeList, which is an array-like object. We then iterate over each node and add a click event listener:

aioColors.forEach(color => {
  color.addEventListener('click', () => {
    // ...
  });
});

Iterating over a nodeList using forEach is not yet supported across the board. For wider support, you can convert the nodeList to a real array with something like [].forEach.call(aioColors) and then call forEach.


The magic happens next with the following code:

const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(color);
selection.removeAllRanges();
selection.addRange(range);

This gets a selection object, then creates a range where we select the contents our our span element. We then make sure that the global selection doesn’t contain any range yet, and add our new range to the selection.


Next we’re ready to try and copy the selection to the clipboard using document.execCommand('copy'):

try {
  document.execCommand('copy');
  selection.removeAllRanges();

  const original = color.textContent;
  color.textContent = 'Copied!';
  color.classList.add('success');

  setTimeout(() => {
    color.textContent = original;
    color.classList.remove('success');
  }, 1200);
} catch(e) {
  // ...
}

We then remove the selection range on our selection object to deselect the text, and setup a success message that disappears after 1.2 seconds.

In case of failure, we keep the selection active, and display a message to tell the user to hit Ctrl + C:

try {
  // ...
} catch(e) {
  const errorMsg = document.querySelector('.error-msg');
  errorMsg.classList.add('show');

  setTimeout(() => {
    errorMsg.classList.remove('show');
  }, 1200);
}

More Fun With execCommand

execCommand can execute a cut, copy or a paste command, but it can also do a lot more. If you have have an element that has its conenteditable attribute set to true, or if your whole document has designMode turned on (document.designMode = 'on'), you can run commands to change the selected text.

Try it out by selecting some text in the paragraph below and the buttons:

Hello Alligator.io!

Here’s the markup for the paragraph:

<p contenteditable="true">
  Hello Alligator.io!
</p>

And the code for executing the commands looks like this:

// select our button elements
const boldBtn = document.querySelector('.bold-btn');
const deleteBtn = document.querySelector('.delete-btn');
const colorBtn = document.querySelector('.color-btn');
const sizeBtn = document.querySelector('.size-btn');

// add our event listeners
boldBtn.addEventListener('click', () => {
  document.execCommand('bold');
});

deleteBtn.addEventListener('click', () => {
  document.execCommand('delete');
});

colorBtn.addEventListener('click', () => {
  document.execCommand('styleWithCSS', false, true);
  document.execCommand('foreColor', false, 'hotpink');
});

sizeBtn.addEventListener('click', () => {
  document.execCommand('styleWithCSS', false, true);
  document.execCommand('fontSize', false, '28px');
});

The first argument to execCommand is the name of the command, the second is a boolean to specify if the default user interface should be shown or not, and the third argument is the value for the command.

Testing For Command Support

If the browser supports execCommand, you can test for support of specific commands using document.queryCommandSupported:

document.queryCommandSupported('copy'); // true

document.queryCommandSupported('something'); // false

Browser Support

Can I Use document-execcommand? Data on support for the document-execcommand feature across the major browsers from caniuse.com.

  Tweet It
✖ Clear

🕵 Search Results

🔎 Searching...