Accessible CSS-Only Floating Labels

Looks matter, but accessibility does too.

Floating labels are a simple design pattern that can help make your forms livelier and more dynamic, in the right conditions. Today we will be looking at how to make CSS-only floating labels without losing readability by accessibility tools.

CSS-Only Floating Labels

Our final result

Here is the Codepen (there is an advanced, prettier version at the end, so keep on reading).

Our HTML

We will set up our form in such a way where both our <input> and <label> tags are in the same container. It is of utmost importance that our <label> comes AFTER our <input>. We will add a .floating-label class to our container to work our CSS with it, and add a placeholder property to our inputs.

To make it accessible, we make sure our inputs have the name property and our labels have the for property set for their specific input, so that screen readers have no trouble processing our markup.

Our markup

And that’s it ! That’s all we need for our markup. (I know our Codepen version is prettier, but we’ll keep it simple here).

Our CSS

Like my mother would say, “¡Achis Achis Los Mariachis!”. Here comes the “difficult” part. It is not difficult at all but it requires some CSS knowledge, and that’s why we are here.

We style our input however we want. For this input I went for a single bottom-border thin line. I fix the height on the vertical padding plus the font-size so our input stays put.

(for the sake of simplicity, I didn’t specify the input[type] in the selector. I strongly suggest you do).

Input’s basic CSS

Next, we add the basic styling to our label. For this demo, we will set a 12.5px font size.

Label’s basic styling

Next, using our .floating-label selector, we set “position: relative;”. This is important because we will position our label with “position: absolute;” for the transform.

We position our label on the middle with using the .floating-label label selector and the “top: calc(50% + half the font size);” property. The opacity is also needed so that the label only shows after the the user has inputed text, and we add a transition for a nicer effect.

The next two selectors are what make most of the magic work:

Remember we had a 20px padding on top and a 20px padding on bottom (40px sum) of our input? Well, we are going to shift the padding so that our input size remains the same, while we make a safe space for the label to be floated on.

We use the .floating-label input:not(:placeholder-shown) selector to set the new padding when the placeholder is not shown. We’ll set 28px on top and 12px on bottom (still adds to 40 pixels).

Finally, when the label is not shown, we will also select the adjacent label to change its opacity and transform its vertical position, with the .floating-label input:not(:placeholder-shown) + label selector.

Floating label styling

And that’s pretty much it ! You can get a lot more creative with selectors. We will cover it in the next section.

Advanced version

Lets get creative now! We can do even better stuff with SVGs:

The GIF

Codepen here

In this version I only added a .icon container with an inline SVG. We absolutely position the SVG inside the parent container

New markup for our icon

Now lets take a look at our advanced styles. This time I’ll be using SCSS:

Here we add an .icon class to our styles. We will be using position: absolute; for our icon container, and set it to top: 0 and left: 0. We will set an arbitrary width, and the same height as our input, as well as initial styles.

We will set a width for our form, say 300px, and an input width of “calc(100% - our icon’s width, 44px)”.

New styles for our icon

Next, when our placeholder is hidden, we transition the values in our SVG to full opacity, and to match our primary color.

Our SVG transition

This makes our icons look toned down when no input is on the field. And also makes them look “on” when there is a value in our input.

The Screenshot

Not the end...

We could actually 1up this form, and make it even better. Want the icon to ONLY show when the input’s value is valid? Then chain a :valid selector: