Auralinna.blogTero Auralinna's blog about intriguing world of web development. Tweaking pixels since the '90s.

I'm a Full Stackish web developer with a strong passion for a beautiful and solid front end. I have mainly focused on front-end development, responsive web design, Content Management Systems, modern web frameworks, DevOps and back-end coding with PHP, Node.js and Java.

Material Design form input text fields

How to create Material Design like form text fields with floating label and animated underline bar

10.11.2018

This post shows how to implement Material Design like form text fields with a floating label and an expanding underline bar animation. The post includes examples of how to style text fields with and without Javascript.

Material Design is a visual language that synthesizes the classic principles of good design with the innovation of technology and science.

Read more at Material.io.

There are many Material Component libraries and frameworks you can use instead if you need a complete set of ready-made components.

  • Material Components - Material Components for Android, Flutter, iOS, and the web
  • Vuetify - Material Components Framework for Vue.js
  • Vue Material - Material Design for Vue.js
  • Angular Material - Material Design components for Angular
  • MATERIAL-UI - React components that implement Google's Material Design
  • Materialize - A modern responsive front-end framework based on Material Design

Continue reading if you want to implement Material Design by yourself.

Material Design with Javascript

The following demo is a barebone example how to implement Material Design field styles. The demo doesn't contain error handling, validation etc. Javascript is used to change the state of the form field when the field is focused or it's filled with value.

Embedded content: https://codepen.io/teroauralinna/pen/rZZGpe

HTML

Let's get started by adding input and textarea field markup.

Input field

<div class="form-field">
  <div class="form-field__control">
    <label for="exampleField" class="form-field__label">Example field</label>
    <input id="exampleField" type="text" class="form-field__input" />
  </div>
</div>

Textarea field

<div class="form-field">
  <div class="form-field__control">
    <label for="exampleTextarea" class="form-field__label">Example textarea</label>
    <textarea id="exampleTextarea" class="form-field__textarea"></textarea>
  </div>
</div>

Styles

In the demo, I have used Bootstrap Reboot. Styles might require tweaking if something else is used as a style baseline.

Animations use translate and scale transform rules which are very simple to use, well supported and have good performance.

$primary-color: #b11adc;
$animation-duration: 0.4s;

@mixin label-active() {
  font-size: 0.75rem;
  transform: translateY(-14px);
}

.form-field {
  display: block;
  margin-bottom: 16px;
  
  &--is-active {
    .form-field__control {
      &::after {
        border-bottom: 2px solid $primary-color;
        transform: scaleX(150);
      }
    }
    .form-field__label {
      color: #b11adc;
      @include label-active();
    }
  }
  &--is-filled {
    .form-field__label {
      @include label-active();
    }
  }
}
.form-field__label {
  display: block;
  font-size: 1.2rem;
  font-weight: normal;
  left: 0;
  margin: 0;
  padding: 18px 12px 0 ;
  position: absolute;
  top: 0;
  transition: all $animation-duration;
  width: 100%;
}
.form-field__control {
  background: #eee;
  border-radius: 8px 8px 0 0;
  overflow: hidden;
  position: relative;
  width: 100%;
  
  &::after {
    border-bottom: 2px solid $primary-color;
    bottom: 0;
    content: "";
    display: block;
    left: 0;
    margin: 0 auto;
    position: absolute;
    right: 0;
    transform: scaleX(0);
    transition: all $animation-duration;
    width: 1%;
  }
}
.form-field__input,
.form-field__textarea {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 1px solid #999;
  color: #333;
  display: block;
  font-size: 1.2rem;
  margin-top: 24px;
  outline: 0;
  padding: 0 12px 10px 12px;
  width: 100%;
}
.form-field__textarea {
  height: 150px;
}

Trigger for the floating label and the underline bar animation

Now our form fields HTML markup and styles are ready. We still need to add a tiny bit of Javascript that changes the field state when the field is active or inactive.

Following ES6 example could be migrated to use any JS framework or library you might be using in your own project.

const setActive = (el, active) => {
  const formField = el.parentNode.parentNode
  if (active) {
    formField.classList.add('form-field--is-active')
  } else {
    formField.classList.remove('form-field--is-active')
    el.value === '' ? 
      formField.classList.remove('form-field--is-filled') : 
      formField.classList.add('form-field--is-filled')
  }
}

[].forEach.call(
  document.querySelectorAll('.form-field__input, .form-field__textarea'),
  (el) => {
    el.onblur = () => {
      setActive(el, false)
    }
    el.onfocus = () => {
      setActive(el, true)
    }
  }
)

Experimenting Material Design with CSS only approach

We can build almost exactly the same functionality without Javascript using CSS only. Microsoft Edge browser is the only one I didn't get to work properly.

Embedded content: https://codepen.io/teroauralinna/pen/JeKrXe

HTML

We need to do a few modifications to the HTML code. Placeholder attribute must be added to the fields. Note that the placeholder must contain at least one space. It doesn't work if it's empty.

We also need to swap the order of input and label elements. New form-field__bar div element is also needed. Then we can target elements via siblings selector.

Input field

<div class="form-field">
  <div class="form-field__control">
    <input id="exampleField" type="text" class="form-field__input" value="" placeholder=" " />
    <label for="exampleField" class="form-field__label">Example field</label>
    <div class="form-field__bar"></div>
  </div>
</div>

Textarea field

<div class="form-field">
  <div class="form-field__control">
    <textarea id="exampleTextarea" class="form-field__textarea" placeholder=" "></textarea>
    <label for="exampleTextarea" class="form-field__label">Example textarea</label>
    <div class="form-field__bar"></div>
  </div>
</div>

Styles

Styles need few major changes without Javascript. When we use Javascript we can add a state modifier class to any element we want. But we have quite limited options when using CSS only. We can use siblings selector to style elements which are placed after the input element.

The focused field can use :focus selector. Selector :placeholder-shown seems to be the only way to target styles to the field that has value.

Edge doesn't support :placeholder-shown. There are implementations that use :valid selector but it has drawbacks with validation so I decided not to use it.

Due to Edge issue, I reversed the order of label styling. By default labels are now in "active" state and floating label animation is used only if a browser supports :placeholder-shown. So the form is still functional but there are no animated labels.

IE 11 and even IE 10 can be supported with :-ms-input-placeholder selector.

It's not possible to use ::after pseudo-element anymore because it can't be applied to input or textarea fields. CSS parent selector would be useful here. So I have created a new div element to handle the underline bar animation.

Following CSS rules are used to style labels and underline bar.

Label when input field contains text

.form-field__textarea:placeholder-shown ~ .form-field__label {
}

Label when input field is focused

.form-field__textarea:focus ~ .form-field__label {
}

Underline bar when input field is focused

.form-field__textarea:focus ~ .form-field__bar {
}

Complete styles

$primary-color: #b11adc;
$animation-duration: 0.4s;

@mixin label-inactive() {
  font-size: 1.2rem;
  transform: translateY(0);
}

@mixin label-active() {
  font-size: 0.75rem;
  transform: translateY(-14px);
}

.form-field {
  display: block;
  margin-bottom: 16px;
}
.form-field__label {
  @include label-active();
  display: block;
  font-weight: normal;
  left: 0;
  margin: 0;
  padding: 18px 12px 0;
  position: absolute;
  top: 0;
  transition: all $animation-duration;
  width: 100%;
}
.form-field__control {
  background: #eee;
  border-radius: 8px 8px 0 0;
  overflow: hidden;
  position: relative;
  width: 100%;
}
.form-field__bar {
  border-bottom: 2px solid $primary-color;
  bottom: 0;
  content: "";
  display: block;
  left: 0;
  margin: 0 auto;
  position: absolute;
  right: 0;
  transform: scaleX(0);
  transition: all $animation-duration;
  width: 1%;
}
.form-field__input,
.form-field__textarea {
  appearance: none;
  background: transparent;
  border: 0;
  border-bottom: 1px solid #999;
  color: #333;
  display: block;
  font-size: 1.2rem;
  margin-top: 24px;
  outline: 0;
  padding: 0 12px 10px 12px;
  width: 100%;
  
  // IE 10-11
  &:-ms-input-placeholder {
    ~ .form-field__label {
      @include label-inactive();
    }
  }
  // All other browsers except Edge
  &:placeholder-shown {
    ~ .form-field__label {
      @include label-inactive();
    }
  }
  &:focus {
    ~ .form-field__label {
      color: #b11adc;
      @include label-active();
    }
    ~ .form-field__bar {
      border-bottom: 2px solid $primary-color;
      transform: scaleX(150);
    }
  }
}
.form-field__textarea {
  height: 150px;
}

Well I can't say is it worth to implement this without Javascript but it was a fun experiment anyway to find out how to do it. Handling the field state in Javascript produces slightly cleaner HTML and more understandable style declarations.

Latest blog posts

Latest CodePens

View all CodePens