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.
Our final result
Here is the Codepen (there is an advanced, prettier version at the end, so keep on reading).
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.
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).
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).
Next, we add the basic styling to our label. For this demo, we will set a 12.5px font size.
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.
And that’s pretty much it ! You can get a lot more creative with selectors. We will cover it in the next section.
Lets get creative now! We can do even better stuff with SVGs:
In this version I only added a .icon container with an inline SVG. We absolutely position the SVG inside the parent container
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)”.
Next, when our placeholder is hidden, we transition the values in our SVG to full opacity, and to match our primary color.
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.
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:
This will make our input[type=“email”] to only lit up when there is a valid value on our field.
STILL NOT THE END
Want to SLAAAAAAAY the CSS game? We will now add behavior, CSS only. When our field gets unfocused, we will SHAKE our email icon so that it indicates an invalid value.
Easy-peasy: We chain an input:not(:valid):not(:focus) + label + icon selector to shake our input when we lose focus and has no valid value.
The Not End
Thanks for reading!