Introduction

Lately I've been working on an internal CRM for the agency where I work at. As we mostly use Bootstrap for our projects as a starting point, I'm kinda getting tired of the look of Bootstrap. That's why I try to build a complete theme which doesn't expose as Bootstrap-based. As you can imagine a CRM has lots of form functionality like creating, editing, deleting contacts, departments, offers and so on. So the UI is very heavily loaded in concerns of form elements like text-inputs, selects, checkboxes and so on. So one of my goals was (and still is) to change the default form elements Bootstrap ships with.

I wanted to achieve the effects of Google's design language: Material Design. Before you ask: Yes I know, that there are already libraries on the market like: Material Design for Bootstrap or Material Design Lite by Google itself. I've worked with these before. I like them. However for this one I wanted to be more flexible by building it up myself. Also that's the only way, one can learn something, right? A major issue I noticed was, that these libraries make heavy use of javascript - which in my opinion - is not even necessary. At least when it comes to styling and animating textfields.

In this post I wanna show you how you can create a textfield with a floating label, like specified by Google.

The markup

Let's begin by putting together a bit of HTML:

<div class="md-textfield">
    <input type="text" class="md-textfield-input" id="my-input" name="my-input" required autocomplete="off">
    <label for="my-input">What's your name?</label>
    <div class="indicator"></div>
</div>

The markup explained

OK, let me give you some explanations:

  • As the label will be positioned absolute later, we need the .md-textfield-DIV as a wrapper which will be positioned relative. Also this DIV helps us to change the width of the whole construction, if we want to.
  • The input-element should be clear. The interesting thing here are the attributes. autocomplete="off" is set here, since the autocomplete dialog kinda breaks the feeling of the final result. But if you want you can ommit it. The required-attribute however is required, as without it the CSS-selector :valid wouldn't work as expected (the :valid would always match; now it only matches if the input's value is not empty).
  • As we will use the +-selector in CSS to address the label, when the input is focused the label has to be put after the input-element itself (This is no problem, when we use the for-attribute to tie it to the input-element).
  • .indicator will be used for animating the bottom border of the input-element when it's focused. The necessity of this single DIV arises since using :after on form elements is not supported, although this would be a nicer solution, since we wouldn't need to put this empty DIV in here...

The basic CSS


.md-textfield { display: inline-block; height: 60px; padding-top: 25px; position: relative; } .md-textfield .md-textfield-input { appearance: none; -moz-appearance: none; -webkit-appearance: none; background: transparent; border: 0; border-bottom: 1px solid #9E9E9E; font-family: 'Roboto', sans-serif; font-size: 13px; padding: 4px 0; position: relative; z-index: 1; } .md-textfield .md-textfield-input:focus { outline: none; } .md-textfield label { color: #9E9E9E; font-family: 'Roboto', sans-serif; font-size: 13px; left: 0; position: absolute; top: 29px; transition: top .1s ease-in 0s; z-index: 0; }

This basic CSS positions the label underneath the transparent input-element and reserves the needed space above the input-element to move the label there, when focused. The sizes (paddings, font-sizes) are also defined like demanded by Google's specification.

Adding different states

The next thing we wanna do is to move the label up and colorize it when the input field is focused. For this we are using the adjacent sibling selector-selector (+):

.md-textfield .md-textfield-input:focus + label {
    color: #3F51B5;
    top: 12px;
}

Note that due to the animation-property we set ealier on the label element the label moves up nice and smooth.

Now let's take care of the indicator:

.md-textfield .md-textfield-input + label + .indicator  {
    border-bottom: 2px solid #3F51B5;
    height: 0;
    position: absolute;
    top: 47px;
    transition: width .1s ease-in 0s;
    width: 0;
    z-index: 2;
}

.md-textfield .md-textfield-input:focus + label + .indicator    {
    width: 100%;
}

When focused we want the bottom-border of our input element to highlight as well. We're setting up the basics first however hiding it by setting the width to 0 and when the input element is focused the width is animated to 100% which let's it look like it's been sliding in from the left.

One last thing to do

When the input field contains a value but is not focused the label would move back down and lie under the input's value. To get rid of this we are using the :valid selector in conjunction with the required-attribute on the input element to keep the label on top of the input element. The color however is reset in comparison to the :focus-state:

.md-textfield .md-textfield-input:valid + label {
    top: 12px;
}

Note that this only works, when there's a required-attribute on the input element. A different solution would be to add a class on the input element, when it contains a value. However therefore you would need some javascript and as the title of this blogpost says: with pure CSS.

Browser support

Some words on browser support: Check out the links below. The only interesting things are the _ adjacent sibling_-, the :focus- and :valid-selectors. Everything else is nothing really new or special. Since these selectors are pretty well supported you can give it a try. And if everything else fails you can wrap your CSS rules with a @supports-conditional (see my blogpost CSS enhanced hyphens fix for an example).