You’ve probably seen this pattern going around. It’s an input that appears as if it has placeholder text in it, but when you click/tap into that input, that text moves out of the way and allows you to type there. It’s rather clever, I think. Brad Frost has a really good post on it, detailing the pros and cons and such.
Many of the demos I’ve seen involve JavaScript. The other day I was checking out at Nest.com, saw their technique for it, and I thought of a way I could pull that off without JavaScript. So here we are.
Here’s how the Nest.com one looks:
It’s not quite as sexy as the Nest ones, where the text is fading out as the label is sliding up. Certainly possible with some JavaScript, but we’re going to stick with pure CSS here. Still might be possible though. I’ll leave that challenge up to you.
#Some Quick Reminders
There are two reasons you might consider doing this:
- It might be able to save space. Because the input and label are combined, it takes up less space. When an input is in focus, you do still need to show both the label and input, but you can get that space by either using some of the space the input was already using, or by growing the area temporarily only for the focused input.
- It makes the input one big button. Not that inputs aren’t already, and not that labels aren’t when they have a proper
for
attribute, but there is something kinda nice about a big rectangle that tells you what it wants that you click/tap. Might make for a nice experience particularly on mobile.
I’d say, generally, that always-visible labels are probably “better” – but this is a clever idea and done right, may be useful occasionally. There is always a risk of screwing this up and hurting accessibility too, so take care. One downside to this pattern: we can’t use placeholder
in addition to the label, which can be helpful (e.g. a label of “Phone Number” and a placeholder hint of “(555) 555-5555”).
#The Trick (1 of 3) – The label is the placeholder
There is a <div>
that contains both the <label>
and <input>
(which you need to do anyway because inputs within forms need to be in block level elements) that has relative positioning. That allows absolute positioning within it, which means we can position the label and input on top of each other. If we do that with the input on top, but with a transparent background, you’ll be able to see the label right underneath it while still being able click into it.
<div>
<input id="name" name="name" type="text" required>
<label for="name">Your Name</label>
</div>
form > div {
position: relative;
}
form > div > label {
position: absolute;
}
#The Trick (2 of 3) – the :focus state and the adjacent sibling combinator
The source order of the <label>
and <input>
wouldn’t matter much here, since semantically they are tied together with the for
attribute. But if we put the input first, that means we can leverage its :focus
state and an adjacent sibling combinator (+) to affect the label when it is focused. Similar in concept to the checkbox hack.
input:focus + label {
/* do something with the label */
}
You can do whatever you want with the label. Just find a cool place to move it and style it that is out of the way of typing in the input. My example had two possibilities: one was making it smaller and moving toward the bottom of the input, the other was moving it to the far right side.
form.go-bottom label {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
transition: 0.2s;
}
form.go-bottom input:focus + label
top: 100%;
margin-top: -16px;
}
#The Trick (3 of 3) – the :valid state
Once there is actual text in the input, and the input goes back out of focus, it would be very weird (bad) to see the label and the input text on top of each other. Fortunately in CSS there is a :valid
selector that works on inputs when they are in a valid state. That valid state can be “any text at all”, assuming the only thing that makes it valid is having any value at all, which can be achieved like:
<input type="text" required>
Then remember the only reason you could see the label at all was because the input has a transparent background. To hide it, we can use an opaque background instead:
form input:valid {
background: white;
}
The rest of this is just fiddling around with design details until you have it just how you like it.