Recreate the Material Design text field with HTML, CSS, and JavaScript

No doubt you’ve seen the beautiful text field if you’re one of Gmail’s 2 billion active users:

It’s fluid, it’s intuitive, it’s colorful 🎨.

It’s Material Design: the wildly popular UI design system powering YouTube, WhatsApp, and many other apps with billions of users.

Let’s embark on a journey of recreating it from scratch with pure vanilla HTML, CSS, and JavaScript.

1. Start: Create basic input and label

As always we start with the critical HTML foundation, the skeleton:

The text input, a label, and a wrapper for both:

HTML
<!-- For text animation -- soon --> <div class="input-container"> <input type="text" id="fname" name="fname" value="" aria-labelledby="label-fname" /> <label class="label" for="fname" id="label-fname"> <div class="text">First Name</div> </label> </div>

2. Style input and label

I find it pretty satisfying: using CSS to gradually flesh out a stunning UI on the backs of a solid HTML foundation.

Let’s start:

Firs the <input> and its container:

CSS
.input-container { position: relative; /* parent of .label */ } input { height: 48px; width: 280px; border: 1px solid #c0c0c0; border-radius: 4px; box-sizing: border-box; padding: 16px; } .label { /* to stack on input */ position: absolute; top: 0; bottom: 0; left: 16px; /* match input padding */ /* center in .input-container */ display: flex; align-items: center; } .label .text { position: absolute; width: max-content; }

3. Remove pointer events

It resembles a text field now, but look what happens when I try focusing:

The label is part of the text field and the cursor should reflect that:

Solution? cursor: text

CSS
.label { ... cursor: text; /* Prevent blocking <input> focus */ pointer-events: none; }

4. Style input font

Now it’s time to customize font settings:

If you know Material Design well, you know Roboto is at the center of everything — much to the annoyance of some.

We’ll grab the embed code from Google Fonts:

Embed:

Use:

CSS
input, .label .text { font-family: 'Roboto'; font-size: 16px; }

5. Style input on focus

You’ll do this with the :focus selector:

CSS

input:focus {
  outline: none;
  border: 2px solid blue;
}

✅

6. Fluidity magic: Style label on input focus

On focus the label does 3 things:

  1. Shrinks
  2. Move to top input border
  3. Match input border color

Of course we can do all these with CSS:

CSS
input:focus + .label .text { /* 1. Shrinks */ font-size: 12px; /* 2. Move to top input border */ transform: translate(0, -100%); top: 15%; padding-left: 4px; padding-right: 4px; /* 3. Match input border color */ background-color: white; color: #0b57d0; }

All we need to complete the fluidity is CSS transition:

CSS
label .text { transition: all 0.15s ease-out; }

7. One more thing

Small issue: The label always goes to the original position after the input loses focus:

Because it depends on CSS :focus which goes away on focus lost.

But this should only happen when there’s no input yet.

CSS can’t fix this alone, we’re going to deploy the entire 3-tiered army of web dev.

HTML: input value to zero.

HTML
<input type="text" id="fname" name="fname" value="" aria-labelledby="label-fname" />

CSS: :not selector to give unfocused input label same position and size when not empty:

JavaScript
input:focus + .label .text, /* ✅ no input yet */ :not(input[value='']) + .label .text { /* 1. Shrink */ font-size: 12px; transform: translate(0, -100%); /* 2. Move to top */ top: 15%; padding-left: 4px; padding-right: 4px; /* 3. Active color */ background-color: white; color: #0b57d0; }

And JavaScript: Sync initial input value attribute with user input

JavaScript
const input = document.getElementById('fname'); input.addEventListener('input', () => { input.setAttribute('value', input.value); });
JavaScript
const input = document.getElementById('fname'); input.addEventListener('input', () => { input.setAttribute('value', input.value); });

✅

That’s it! We’ve successfully created an outlined Material Design text field.

With React or Vue it’ll be pretty easy to abstract everything we’ve done into a reusable component.

Here’s the link to the full demo: CodePen



11 Amazing New JavaScript Features in ES13

This guide will bring you up to speed with all the latest features added in ECMAScript 13. These powerful new features will modernize your JavaScript with shorter and more expressive code.

11 Amazing New JavaScript Features in ES13

Sign up and receive a free copy immediately.

Leave a Comment

Your email address will not be published. Required fields are marked *