Daniel O’Connor

Prevent an element from being selected and copied with CSS

I was recently helping a coworker build a code diff viewer that supports line numbers, wraps text properly, and prevents line numbers from being copied when selecting the code.

Adding line numbers and wrapping text is easy: just use a table and make a tr for each line of code.

Preventing the line numbers from being selected and accidentally copied to the clipboard is much trickier.

An elegant solution using pseudo-elements

Combining pseudo-elements and the CSS content property, we can prevent text from being selected and copied to the clipboard.

<p data-pseudo-content="Lorem Ipsum"></p>
[data-pseudo-content]::before {
content: attr(data-pseudo-content);

We can take this example a bit further and support both ::before and ::after.

[data-pseudo-content--after]::after {
content: attr(data-pseudo-content);

Why the pseudo-element works

Content displayed on a page using the CSS content property is never added to the DOM. This prevents the text from being selected or copied without the use of (-prefix-)user-select: none.

Despite this, you can access the content values in JavaScript with getComputedStyle. The best approach, however, is to use element.getAttribute() to grab the value of the data attributes directly from the HTML.

Accessibility concerns

There are a handful of articles online stating that screen-reader support for the content property is not consistent. Keep that in mind when using this technique.

What about (-prefix-)user-select: none?

All major browsers (including IE 10+) support (-prefix-)user-select: none. This prevents users from selecting an element.

.unselectable {
-moz-user-select: none;
webkit-user-select: none;
ms-user-select: none;

This does not, however, prevent the text from being copied to the clipboard in all browsers. Also, it is not part of the CSS standard and there are no plans to add it.

A live example

Here is a stripped down recreation of the code diff viewer I helped my coworker build:

(You could use CSS counters to generate the line numbers, but that is out of scope for this example.)

Notice that the line numbers can't be selected or copied and the text wraps if it gets too long. Mission accomplished!

Optimizely is hiring! Reach out on Twitter or check out our jobs page.