Using CSS to Control Text Selection

CSS lets you control how text selection behaves and appears on your pages. This can help you improve usability in certain situations and add a little bit of visual flair. Let’s dive in!

Select All

Sometimes it’s nice to have all the text in an element automatically selected when you click on it. This is particularly handy for text that is copied/pasted in full (code snippets, one-time passwords, promotional codes, etc.).

You can accomplish this with some simple CSS. No JavaScript required!

div {
    -webkit-user-select: all; /* for Safari */
    user-select: all;
}

Here’s a demo. Bad news, it doesn’t work on iOS. Good news, it degrades gracefully, so the text is still selectable.

See the Pen Select All by Will Boyd (@lonekorean) on CodePen.

Select All… Then Select Some

While this works as expected, you may notice something annoying: it is impossible to select anything less than the entire code snippet. Wouldn’t it be nice if the first click selected all, but you could still click again and select just a portion? CSS can do this.

First, use tabindex to make the element holding the text focusable. This gives the CSS a way to know when the element has been clicked.

<code tabindex="0">code snippet goes here</code>

Then comes the CSS.

code {
    -webkit-user-select: all;
    user-select: all;
}

code:focus {
    animation: select 100ms step-end forwards;
}

@keyframes select {
    to {
        -webkit-user-select: text;
        user-select: text;
    }
}

The idea is to have user-select: all on the element initially, then switch to the “normal” user-select: text after the element has focus so that text can be freely re-selected. The tricky part is the timing. Do the switch immediately upon focus and user-select: all is gone before it has a chance to do its job. That’s where animation comes in.

Yes, user-select is animatable! More specifically, it is discretely animatable, meaning there is no gradual interpolated animation, but rather an immediate cut from one state to another. Armed with this knowledge, we can use animation to delay the change in select behavior until 100ms after focus. Perfect.

Again, the “select all” bit doesn’t work on iOS. Meanwhile, desktop Safari keeps the text as “select all”. This trick seems to work fine elsewhere, though.

See the Pen Select All... Then Select Some by Will Boyd (@lonekorean) on CodePen.

Preventing Text Selection

You can also use CSS to make text in an element unselectable.

label {
    -webkit-user-select: none;
    user-select: none;
}

This is probably a bad idea for body text, but I’ve found it useful for controls that might be toggled quickly or “rage clicked” in desktop browsers, since double clicking causes text to be selected and highlighted, which can look a little weird sometimes.

See for yourself in the following demo. Notice how the toggle on the left becomes highlighted when rapidly clicked, while the one on the right doesn’t.

See the Pen Preventing Text Highlights from Rage Clicking by Will Boyd (@lonekorean) on CodePen.

This technique also works on disclosure widgets. Fake buttons — like a <div> with a click handler on it — are another candidate. Bear in mind that using a real <button> is preferable, not only for semantics and accessibility, but also because text in a <button> is unselectable by default, avoiding the issue to begin with.

Selectively Selecting Text

Unselectable text can be mixed into selectable text. The unselectable bits are simply skipped over when text is selected and will be omitted when the selection is copied/pasted.

The demo below uses user-select: none on the numerical footnote markers. So when you copy/paste, the markers are automatically removed for you.

See the Pen Declaring Bits of Unselectable Text by Will Boyd (@lonekorean) on CodePen.

Sadly, some browsers won’t play along. Safari (iOS and desktop) and Android Chrome will still copy the markers.

Styling the Selection

You can style text selections by targeting the ::selection pseudo-element. However, your options are limited to 3 properties: color, background-color, and text-shadow (there are more defined in the spec, but browsers don’t support them).

Here’s an example that styles the selected text in a <p>.

p::selection {
    color: #fffaa5;
    background-color: #f38630;
    text-shadow: 2px 2px #31808c;
}

Try selecting some text in the demo below to see the result. Unfortunately, iOS is the holdout once again, but everyone else should see fancier colors.

See the Pen Styling Text Selections by Will Boyd (@lonekorean) on CodePen.

Odds and Ends

There’s another declaration, user-select: contain, that is supposed to confine text selections to within an element, like you’d see with a <textarea>. Oddly enough, IE11 was the last browser to support it. No modern browsers support it currently.

That said, all editable elements (such as <textarea>) are treated as if they had user-select: contain. And the ::before and ::after pseudo-elements are unselectable, as if they had user-select: none. You cannot override these behaviors.

Going Further

This article is about CSS, but I would be remiss if I didn’t mention the relevant JavaScript.

If you want full control over text selections, with the ability to create and modify them at will, then check out the JavaScript Selection API. If the end goal is to copy/paste text, then you should know that JavaScript also allows you to interact with the clipboard.

Hey, you reached the end!

Feel free to check out my other blog posts or subscribe to my RSS feed. You can also click a tag below to see related blog posts.