How to easily copy text to clipboard with JavaScript

- 4 min read

When HTML added “design mode” for What You See Is What You Get (WYSIWYG) editing, a simple way to copy text to the device’s clipboard was introduced.

Recently, I’ve ran into multiple scenarios where copying information from a web page to the device’s clipboard has been needed. In the old days, this required use of ActiveX or Flash (or a similar process on the device that was available to the web page). However, with the relatively recent wide adoption of document.execCommand() this has become a trivial problem to solve cross browser.

First up, the code:

var tmp = document.createElement("textarea");
tmp.value = "The string that you want in the clipboard goes here";
tmp.style.height = "0";
tmp.style.overflow = "hidden";
tmp.style.position = "fixed";
document.body.appendChild(tmp);
tmp.focus();
tmp.select();
document.execCommand("copy");
document.body.removeChild(tmp);

Note, this isn’t necessarily the most robust solution, however it is perfect and simple for copying plain text to the clipboard (and can include line breaks). If the item you’re looking to copy needs to include HTML structure, images, etc there’s an experimental technology called Selection API that you’ll need to work with, rather than a simple <textarea>.

Second note, I’ve written the code in this post to work for the vast majority of browsers in the wild at time of authoring with no assumption of transpiling. If you’re using a transpiler or only support modern browsers, it would be good practice to change all instances of var to const/let (as appropriate).

Explanation of the copy to clipboard code

First a textarea element is created and a value is applied to it. This value can be any string:

var tmp = document.createElement("textarea");
tmp.value = "The string that you want in the clipboard goes here";

Then add styling to the element to prevent the browser jumping down to the textarea when we focus the it later by pinning it to the user’s view with fixed.

tmp.style.position = "fixed";

Out of an abundance of caution, also set the textarea to have a height of zero (to effectively hide it).

Hypothetically the browser could render the textarea for a brief moment if we don’t force it to be zero height, although I’ve never observed it.

tmp.style.height = "0";
tmp.style.overflow = "hidden";

Next, put the textarea within the body of the HTML document so that it can be focused/selected by the user’s browser.

document.body.appendChild(tmp);

Then focus, the textarea, select all of its text, and copy the text.

tmp.focus();
tmp.select();
document.execCommand("copy");

Lastly, remove the textarea DOM element.

document.body.removeChild(tmp);

A note about document.execCommand(“copy”)

Most browsers’ security features do not allow a page to copy data to the clipboard without the user taking an action (such as clicking on a button). Keep this in mind if you’re attempting to copy information to the clipboard without the user taking any action.

Example, bind button click to copy a block of code

<code id="a-code-snippet">
    // Copy title and URL of page (without query string/hash).
    console.log(document.getElementsByTagName("title")[0].textContent);
    console.log(document.location.origin + document.location.pathname);
</code>
<button class="copy-element-text" data-copy-from="#a-code-snippet">
    Copy code
</button>
<script>
    // Note, if you were going to put this code straight on a site, you should
    // wrap it with a self-executing function to avoid polluting global scope.
    //
    // It looks like this:
    // (function () {
    //   /* paste JavaScript here */
    // })();

    // Once we have the element we want to copy from, copy to clipboard.
    function copyTextFromEl(el) {
        var tmp = document.createElement("textarea");
        tmp.value = el.textContent;
        // Prevent text area from briefly being visible.
        tmp.style.height = "0";
        tmp.style.overflow = "hidden";
        // Fixed prevents browser from scrolling to the textarea (if it is
        // outside the current view).
        tmp.style.position = "fixed";
        document.body.appendChild(tmp);
        tmp.focus();
        tmp.select();
        document.execCommand("copy");
        document.body.removeChild(tmp);
    }

    // Handle click event on bound button.
    function clickListener(e) {
        // Using currentTarget ensures the user actually clicked on the button.
        // Events in JavaScript bubble and currentTarget ensures you receive the
        // button element your event listened for clicks on.
        var btn = e.currentTarget;
        // Since we're dealing with DOM which could be manipulated by other code
        // on the page, it is best to ensure the property that's expected exists
        // and that it was able to find the copy-from target.
        //
        // If you're unfamiliar with data-* attributes, they can be accessed from
        // JavaScript using the dataset object. Hyphens in the HTML are removed
        // and the property name is converted to camel case.
        //
        // Thus "data-copy-from" is accessed as "btn.dataset.copyFrom"
        if ("copyFrom" in btn.dataset) {
            var copyEl = document.querySelector(btn.dataset.copyFrom);
            if (copyEl) {
                copyTextFromEl(copyEl);
            }
        }
    }

    // App binding to all buttons with copy-element-text class.
    //
    // While our example only has one button, selecting all instances on the
    // page and adding listeners to each, establishes a re-usable feature on the
    // page.
    var buttons = document.querySelectorAll("button.copy-element-text");
    for (var i = 0; i < buttons.length; i++) {
        buttons[i].addEventListener("click", clickListener);
    }
</script>