The Floated Labels Technique

When we’re dealing with inputs, there’s a host of techniques to consider in order to give users the best experience. We need to make sure that we’re giving users necessary information at all points in time, and that means finding the balance between too much and too little. Two pretty important pieces to the UX puzzle are:

The label element which according to the MDN, is the only way to provide explanatory information about a form field that is always appropriate The placeholder attribute for the input element, which according to the MDN, lets you specify a text that appears within the <input> element’s content area itself when empty. It’s intended to be used to show an example input, rather than an explanation or prompt.

These two elements can be used in tandem to create instructive form UI, and one pleasant way of doing so is what I call the “floated label technique”. In this UI implementation, the user encounters this flow:

They see an input with a placeholder showing some descriptive example input, and the label is initially hidden. When they focus on the input and start typing, the meaningful label animates and floats above the input. As long at the input has a value, the label remains visible and floated above the input to remind users the original meaning of the input

This UI technique does indeed slightly bend the definitions of label and placeholder listed above, in the sense that we’re giving the placeholder more initial importance in having to explain the input to the user, but it’s a tradeoff for a neat UI component, and one that I’m personally comfortable making. Here’s a quick preview of what we’re going to build.

Floating Labels With Only CSS

Registering a focus event, determining if an input has value, animating a hidden element into view, deciding whether it needs to stay in view or not depending on input value… this seems like a job for JavaScript, right? Wrong! We’re gonna use a neat CSS pseudo class, :placeholder-shown , to achieve the effect. From the MDN:

The :placeholder-shown CSS pseudo-class represents any <input> or <textarea> element that is currently displaying placeholder text.

In other words, if an input has any value in it, we can safely assume that the :placeholder-shown pseudo class will never be reached in our CSS.

Here’s the train of thought I came up with to achieve the floated labels technique using this info:

Structure my markup such that I could target my label as a adjacent sibling combinator from my input , and wrap the two elements in a containing div . Style my input and label accordingly, including the ::placeholder pseudo element. Move the label to its starting position, which is vertically centred in the containing div , and hide it. Determine when to show the label by using a combination of the :not , :focus and :placeholder-shown pseudo classes: If there’s no placeholder shown, then show the label — input:not(:placeholder-shown) + label If the input is focused and the placeholder isn’t shown, then show the label — input:focus:not(:placeholder-shown) + label

With that in mind, let’s get to the HTML & CSS!

Building it all

After digesting all this info, the build is actually pretty simple. Here’s the markup I came up with:

< div class = "Input" > < input type = "text" id = "input" class = "Input-text" placeholder = "Your first name, e.g. Nicholas" > < label for = "input" class = "Input-label" >First name</ label > </ div >

And here’s the CSS scaffolding with some comments about what’s happening at each step. The final demo down below has the full CSS, which is written up with CSS variables for easy modification and maintenance.

. Input { /** * Relative positioning on the wrapper is important, beecause we'll use the * wrapper to position the label in default and floated positions. */ position : relative ; } . Input-text { /** * Input text styles here. The font size and line height of the input are * important for determining precise positioning of the label. These will be * stored in CSS variables eventually. */ display : block ; margin : 0 ; width : 100 % ; /** * These props will be added in the demo once variables are defined: * * padding * font-size * line-height */ } . Input-text : focus { /** * Focused input styles here. */ } . Input-label { /** * The label gets absolute positioning, and a calculated set of sizes, * positioning, and transforms, based on relativity to the container element * and input text styles. */ display : block ; position : absolute ; opacity : 0 ; /** * These props will be added in the demo once variables are defined: * * bottom * left * font-size * line-height * transform * transform-origin * transition * * They will give the label default positioning and styling. */ } . Input-text : placeholder-shown + . Input-label { /** * While the placeholder is visible, we want to hide the label. */ visibility : hidden ; z-index : -1 ; } . Input-text : not ( : placeholder-shown ) + . Input-label , . Input-text : focus : not ( : placeholder-shown ) + . Input-label { /** * While the placeholder is not shown - i.e. the input has a value - we want * to animate the label into the floated position. */ visibility : visible ; z-index : 1 ; opacity : 1 ; /** * These props will be added in the demo once variables are defined: * * transform * transition * * They will give the label floated positioning and styling. */ }

Full Demo In Action

If any of the above was unclear, here’s a full working demo in action for you to take a look over!

Browser Support

Support is pretty good so far, with IE & Edge falling behind:

Chrome: 47+

47+ Firefox: 51+

51+ IE: No support

No support Edge: No support

No support Opera: 34+

34+ Safari: 9+

You can get around lack of support by using some fun CSS browser hacks to target IE and Edge browsers for some conditional styles to the input and label.

Wrap Up