Using CSS, it’s possible to prevent users from selecting text within an element using user-select: none
. Now, it’s understandable why doing so might be considered “controversial”. I mean, should we be disabling standard user behaviors? Generally speaking, no, we shouldn’t be doing that. But does disabling text selection have some legitimate (albeit rare) use-cases? I think so.
In this article we’ll explore these use cases and take a look at how we can use user-select: none
to improve (not hinder) user experiences. It’s also worth nothing that the user-select
property has other values besides none
that can be used to alter the behavior of text selection rather than disable it completely, and another value that even enforces text selection, so we’ll also take a look at those.
user-select
values
Possible Let’s kick things off by running through the different user-select
values and what they do.
Applying user-select: none;
to an element means that its text content and nested text content won’t be functionally selectable or visually selectable (i.e. ::selection
won’t work). If you were to make a selection that contained some non-selectable content, the non-selectable content would be omitted from the selection, so it’s fairly well implemented. And the support is great.
This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.
Desktop
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
4* | 2* | 10* | 12* | 3.1* |
Mobile / Tablet
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
115 | 115 | 2.1* | 3.2* |
Conversely, user-select: text
makes the content selectable. You’d use this value to overwrite user-select: none
.
user-select: contain
is an interesting one. Applying it means that if a selection begins within the element then it must end within it too, containing it. This oddly doesn’t apply when the selection begins before the element, however, which is probably why no browser currently supports it. (Internet Explorer and earlier versions of Microsoft Edge previously supported it under the guise of user-select: element
.)
With user-select: all
, selecting part of the element’s content results in all of it being selected automatically. It’s all or nothing, which is very uncompromising but useful in circumstances where users are more likely to copy content to their clipboard (e.g. sharing and embedding links, code snippets, etc.). Instead of double-clicking, users will only need to click once for the content to auto-select.
Be careful, though, since this isn’t always the feature you think it is. What if users only want to select part of the content (e.g. only the font name part of a Google Fonts snippet or one part of a code snippet)? It’s still better to handle ”copy to clipboard” using JavaScript in many scenarios.
A better application of user-select: all
is to ensure that quotes are copied entirely and accurately.
The behavior of user-select: auto
(the initial value of user-select
) depends on the element and how it’s used. You can find out more about this in our almanac.
Now let’s turn to exploring use cases for user-select: none
…
Stripping non-text from the selection
When you’re copying content from a web page, it’s probably from an article or some other type of long-form content, right? You probably don’t want your selection to include images, emoji (which can sometimes copy as text, e.g. “:thinkingface:”), and other things that you might expect to find wrapped in an <aside>
element (e.g. in-article calls to action, ads, or something else that’s not part of the main content).
To prevent something from being included in selections, make sure that it’s wrapped in an HTML element and then apply user-select: none
to it:
<p>lorem <span style="user-select: none">🤔</span> ipsum</p>
<aside style="user-select: none">
<h1>Heading</h1>
<p>Paragraph</p>
<a>Call to action</a>
</aside>
In scenarios like this, we’re not disabling selection, but rather optimizing it. It’s also worth mentioning that selecting doesn’t necessarily mean copying — many readers (including myself) like to select content as they read it so that they can remember where they are (like a bookmark), another reason to optimize rather than disable completely.
Preventing accidental selection
Apply user-select: none
to links that look like buttons (e.g. <a href="/whatever" class="button">Click Me!</a>
).
It’s not possible to select the text content of a <button>
or <input type="submit">
because, well, why would you? However, this behavior doesn’t apply to links because traditionally they form part of a paragraph that should be selectable.
Fair enough.
We could argue that making links look like buttons is an anti-pattern, but whatever. It’s not breaking the internet, is it? That ship has sailed anyway, so if you’re using links designed to look like buttons then they should mimic the behavior of buttons, not just for consistency but to prevent users from accidentally selecting the content instead of triggering the interaction.
I’m certainly prone to selecting things accidentally since I use my laptop in bed more than I care to admit. Plus, there are several medical conditions that can affect control and coordination, turning an intended click into an unintended drag/selection, so there are accessibility concerns that can be addressed with user-select
too.
Interactions that require dragging (intentionally) do exist too of course (e.g. in browser games), but these are uncommon. Still, it just shows that user-select
does in fact have quite a few use-cases.
Avoiding paywalled content theft
Paywalled content gets a lot of hate, but if you feel that you need to protect your content, it’s your content — nobody has the right steal it just because they don’t believe they should pay for it.
If you do want to go down this route, there are many ways to make it more difficult for users to bypass paywalls (or similarly, copy copyrighted content such as the published work of others).
Blurring the content with CSS:
article { filter: blur(<radius>); }
Disabling the keyboard shortcuts for DevTools:
document.addEventListener("keydown", function (e) {
if (e.keyCode == 123) e.preventDefault();
else if ((e.ctrlKey || e.metaKey) && e.altKey && e.keyCode == 73) e.preventDefault();
else if ((e.ctrlKey || e.metaKey) && e.altKey && e.keyCode == 74) e.preventDefault();
else if ((e.ctrlKey || e.metaKey) && e.altKey && e.keyCode == 85) e.preventDefault();
});
Disabling access to DevTools via the context menu by disabling the context menu itself:
document.addEventListener("contextmenu", e => e.preventDefault())
And of course, to prevent users from copying the content when they’re not allowed to read it at the source, applying user-select: none
:
<article style="user-select: none">
Any other use cases?
Those are the three use cases I could think of for preventing text selection. Several others crossed my mind, but they all seemed like a stretch. But what about you? Have you had to disable text selection on anything? I’d like to know!
The apart about blocking users from “stealing” paywalled content doesn’t really apply. A user can still open the DevTools and copy the text via selecting the elements; even if one is to randomize and auto-generate classes and div-s to scramble the content, that can still be overridden via an OCR program.
Yeah of course, but that’s only assuming that the user even knows what DevTools is (most don’t).
Mate if you’re sending paywalled content over the wire a little bit of anti-user CSS ain’t gonna protect it. You need to NOT send it, at all, because the countermeasures you’ve listed are pretty useless.
Even assuming I can’t just open devtools via the browser’s menu (which I can), curl exists. If keeping your content secret is that important you need to actually keep it secret, not have it available in the source of a public URL.
Except that the average visitor has no idea what DevTools/curl/etc. is.
lmao. The average visitor isn’t going to steal your content either way.
Folks who want to steal your content know how to get to it. If you don’t want that, don’t make it available at a public URL.
Also, all your little tricks fall flat as soon as I switch to Reading Mode, stripping of all your fancy CSS and JavaScript—literally on the press of a button.
Re: Avoiding paywalled content theft
Ctrl-l Ctrl-a Ctrl-c – I have the url
Ctrl-w Ctrl-t – I have a new tab, empty
Ctrl-Shift-i – I have devtools open
Ctrl-l Ctrl-v enter – I have your precious page open with devtools already open.
Trying to block people from getting content they’re literally looking at already is bound to fail. There are too many ways to get the content. So this is not a valid use-case.
Thanks for the reminder re: the shortcuts (I forgot about cmd-l).
However, it’s a valid use-case for the majority that aren’t as tech-savvy as us.
Unfortunately, I’ve seen websites that detect devtools and prevent content from loading at all if devtools are open. :(
I wish there was some kind of “invisible mode” for devtools that makes them undetectable. Probably not all features would work, but it would still be very useful.
Disabling for draggable elements may be appropriate.
Yep! Games, for example. Personally I’m not really a fan of complex interactions otherwise, though.
I look at this very differently. I want to control user experience and users seeing unneeded highlights on elements when using the site for any reason is not desirable. So I up user-select:none; on the body and then add it back in on elements like page content users might like to copy. I don’t want them copying the heading of the sidebar. I don’t want highlight boxes showing up on random elements because a user dragged a little bit. Getting rid of user selection was one of my favorite discoveries and I consider management of that behavior to be a part of what I do well.
I think I agree with this approach, but worry that I would forget to enable selection for elements that need it. I wonder if the way that browsers handle selection is just all wrong, and this behaviour should be standard.
Assertion: Disabling blur filters and JS event handlers to view content on my computer in a way the publisher doesn’t want => stealing.
Counter-Assertion: Placing unreadable content on my computer without my permission or consent => trespassing.
I actually had a good reason for doing this once.
I was working on a drawing app and when I tried drawing with an iPad, it would just select the drawing area as something you could copy.
Using user-select none fixed that.
The only time I use
user-select: none;
is to enhance the user experience.a code example might have the line-numbers as unselectable so the code can be copied directly without having to remove line-numbers
a command-line example might have the prompt-string as unselectable making it easier to copy out the command(s) the user might want to run
a bit of content duplicated for purposes of visual effect might have one copy marked as unselectable so that copying it doesn’t contain duplicates
one might debate the benefits of unselectable
<aside>
elements as they might intrude on selection of the main text, but often the content of an<aside>
is there specifically because it has worthwhile material that a user might want to copy. So I strongly advise against this.But putting a tiny speed-bump up to deter users from copying content? And mucking with keyboard interactions to boot? Please don’t be user-hostile like that.
I’m curious, what do you consider hostile? DRM? CCTV? Passwords? These are just necessary precautions to protect property.
Totally hear you on everything else though, you’ve provided some great examples of when to use it and when you might not want to use it on
<aside>
.Back in March I made a bookmarklet to override when sites disable text selection (somebody on Twitter suggested how to prevent users from copying text and just no):
https://adrianroselli.com/2022/03/youre-unselectable.html
With WCAG 2.2 getting closer to completion, both Success Criterion 3.3.7: Accessible Authentication and Success Criterion 3.3.9: Redundant Entry identify the user’s ability to select and copy text as a method to satisfy them. Disabling the ability to select text, on top of being user-hostile, could also produce WCAG errors in some contexts.
What if the text is blurred, clearly not meant to be accessed (because it’s not free, or whatever), links aren’t focusable, etc.? Should it still be considered text? Should it be considered anything?
I just don’t understand the hostility thing. If somebody is trying to hack their way around not paying for something, what should they expect to be met with if not hostility?
Daniel, if the text is not meant to be accessed, then blurring it does not prevent it from being accessed. Dev tools or view-source will get around that pretty quickly. Screen readers would still get to it without any hint it is blurred and still find those non-focusable links (barring some ARIA trickery).
If it is sent over the wire, then it is for the user. If a site wants the user to pay to get that content, then don’t send that content to their computer. Don’t waste the user’s bandwidth and CPU cycles just to blame them for “hacking” what you already chose to send them.
I build a lot of SPAs and part of my minimal boilerplate is body {user-select: none;} i.e. text selection disabled by default.
Then I add it back when I specifically want the user to be able to select content… which I find is not all that common.
That paywall snippet is awesome! Its a five minute solution that works for 99% of use cases. Thank you.
Yeah, I’d say about 99% of use-cases is correct. Devs can get around paywalls easily, but they’re such a small minority.
For me, I find myself using a lot of user-select: none when developing some app with Electron for example, when I try to get the behavior of a native application, instead of the usual internet behavior of being able to select everything on screen.
For instance, for text that is part of the own application’s UI (like a toolbar, or a side menu), I think it’s ok to not allow the text to be selectable.
But for other areas like the message feed of a chat application for example, I would allow my users to be able to select the text.
Makes perfect sense! :)
user-select: none
is NOT a copy protection by any stretch of the imagination.The only thing it can do is to annoy your potential customers by disabling perfectly legitimate browser features.
A paywall should be implemented server-side and you should not send the paywalled content down the wire to everyone. If you work in a team, you should discuss this with backend people. If you are looking to protect the content on your own site and you can’t modify the server yourself, consider hiring someone who can, or use Patreon or similar sites to host your paid content.
If by design you want to provide a blurred preview of the paywalled content there are ways to do it without sending actual content to everyone. (Render a blurred image server-side/send meaningless text instead of the actual content/etc. Now you can legitimately use
user-select: none
on it because selecting and copying it will make no sense anyway).Either way, do not implement “copy protection” just on the frontend, it won’t work. There is a reason why big corporations spend billions of dollars on a complex tech like Widevine (which still requires a server). CSS and JS can’t be used to implement DRM in a browser.
The context menu contains many more features than mere “copy”. Disabling it by any reason besides replacing it with your own context menu containing features relevant to your app is user-hostile.
Using
user-select: none
by itself is a bad idea most of the time and leads to broken websites and apps.It’s briefly mentioned that “selecting doesn’t necessarily mean copying”, but you have no idea. Off the top of my head, preventing selection of text makes harder to look up a word in a dictionary or search it (because you would need retype the word or phrase yourself).
I do, and when I copy things (to a private note-taking app) I usually want them as verbatim as possible. Images, emoji, etc. all are parts of the content. I’ll leave it to a receiving app to decide what it supports.
And things like “links as buttons is anti-pattern or whatever so let’s make them even harder to use” are shocking to say the least.