Floating input labels help usability, especially when page real-estate is at a premium. Many studies and medium articles debate their merits and flaws. Instead of debate, let’s explore how to build a version of it.

What is a floating label input field? A floating label is a text label which appears inside the input field at full font-size. When interacted with, the label “floats” above, making room for the user to input a value.

The label “floats” above the input value

Building from scratch, you may look into the css pseudo classes: ::before and ::after . Input fields cannot use these pseudo classes as they cannot contain other elements. A way around this is to place the input inside another element, such as a <div> and emulate the look of an input field. Removing the <input> ‘s border and outline, we place them on the <div> .

.float-container {

border: solid 1px #ccc;

} .float-container input {

border: none;

outline: 0;

} <div id="floatContainer" class="float-container">

<input type="text">

</div>

We can now add a text label to the input field. While any element can be used, a <label> element is the best choice. Labels provide accessibility and focus on their associated <input> when clicked. Remember to add an id to the input and matching for attribute to the label.

<div id="floatContainer" class="float-container">

<label for="floatField">

<input id="floatField" type="text">

</div>

Without styling, the label will appear above the input. Adding an absolute position to the label will make it appear to be inside the input field. Adding position: relative to the container will keep the label inside and not outside.

.float-container {

border: solid 1px #ccc;

position: relative;

} .float-container input {

border: none;

outline: 0;

} label {

position: absolute;

} <div id="floatContainer" class="float-container">

<label for="floatField">

<input id="floatField" type="text">

</div>

Input with a label and value

With the label inside the container, entering text appears to be a jumble. Creating space inside the container will help as well as setting the font size for both the label and input.

.float-container {

border: solid 1px #ccc;

padding: 0 8px;

position: relative;

} .float-container input {

border: none;

font-size: 16px;

outline: 0;

padding: 16px 0 10px;

} label {

font-size: 16px;

position: absolute;

}

Adding space to the container helps readability

Adding space helped see the label and text. Currently the label is in about the right place to see the value of the field. We want it to start in the same position as the value and move up. The label should also reduce it’s text size when the value is present to not take away from the importance of the value. We could style the margin or top properties. Instead, we use the transform property with translate. Translate will set the x/y coordinates. The label won’t move left or right, so we keep the y setting at 0. X will be set to position it down, overlaying the real input field. As this is the default setting for the label we also want to set the initial scale to 1, which means 100% of it’s original size.

label {

font-size: 16px;

position: absolute;

transform: translate(0, 16px) scale(1);

}

Changing the state of the field, we create an “active” class. We want the label to change it’s size when the user clicks the input field. An active class on the container, allows elements within, to use the active state. When active, the label will move up, in this case will move up 12px. We also want the label to be a bit smaller when floating. Instead of changing the font-size, we only need to change the scale to .75 which means 75% of the original size.

.float-container.active label {

transform: translate(0, 4px) scale(.75);

}

Our label is now float…uh oh

Transforming the x coordinate and scale worked as intended, yet the positioning is a bit off. We could update the y coordinate, but that isn’t the problem. The label changed size and by default, used the center point of the label. Adjusting the origin point to be the top-left corner should work. While we’re at it, we’ll add animation. Transition sets the animation properties, duration, and the speed curve.

label {

font-size: 16px;

position: absolute;

transform-origin: top left;

transform: translate(0, 16px) scale(1);

transition: all .1s ease-in-out;

} .float-container.active label {

transform: translate(0, 4px) scale(.75);

}

With our active class, we need to use javascript to add and remove the class when clicked (es6 will be used in the examples). Input fields have 2 events which will help our interaction that are not ‘click’. These events are ‘focus’ and ‘blur’. Clicking or tabbing into (pressing the tab key), fires the ‘focus’ event . The label field when clicked, will focus on it’s associated input. Blur is the opposite of focus. Blur will fire when the user clicks outside of the focussed input or tabs off.

const floatField = document.getElementById('floatField');

const floatContainer = document.getElementById('floatContainer'); floatField.addEventListener('focus', () => {

floatContainer.classList.add('active');

}); floatField.addEventListener('blur', () => {

floatContainer.classList.remove('active');

});

Float on

Our code so far:

== css == .float-container {

border: solid 1px #ccc;

padding: 0 8px;

position: relative;

} .float-container input {

border: none;

font-size: 16px;

margin: 16px 0 10px;

outline: 0;

} label {

font-size: 16px;

position: absolute;

transform-origin: top left;

transform: translate(0, 16px) scale(1);

transition: all .1s ease-in-out;

} .float-container.active label {

transform: translate(0, 4px) scale(.75);

} == html == <div id="floatContainer" class="float-container">

<label for="floatField">

<input id="floatField" type="text">

</div> == javascript == const floatField = document.getElementById('floatField');

const floatContainer = document.getElementById('floatContainer'); floatField.addEventListener('focus', () => {

floatContainer.classList.add('active');

}); floatField.addEventListener('blur', () => {

floatContainer.classList.remove('active');

});

At this point we have an animated floating label on an input field. Our work isn’t finished. We need to think about all the use cases of this input field. Currently, the field returns the label back to it’s original position on ‘blur’. It doesn’t take into account if the input has a value. Our label will also currently overlap any placeholder text in the field. Finally, our input field also only works with 1 input field on the page. We will need to build up our code so multiple input fields will use a floating label.

Our floating label script will use a module pattern handle our use-cases. We will capture the input fields we want to apply the float label. We will also bind the focus and blur events to these fields. These events will also take into account the condition of the field if it has a value or not.

Here’s our initial structure:

const FloatLabel = (() => { const handleFocus = () => {

}; const handleBlur = () => {

}; const bindEvents = () => {

}; const init = () => {

}; return {

init: init

};

})(); FloatLabel.init();

Our first action is to find all the inputs and bind the events. We collect the inputs which have the class float-container . We store them in an array and loop through them using forEach .

const init = () => {

const floatContainers = document.querySelectorAll('.float-container');



floatContainers.forEach((element) => {

bindEvents(element);

});

};

For each element, we add the same event listeners for ‘focus’ and ‘blur’ we used before. Our new listeners will call functions instead of defining them within the listeners.

const bindEvents = (element) => {

const floatField = element.querySelector('input');

floatField.addEventListener('focus', handleFocus);

floatField.addEventListener('blur', handleBlur);

};

Focussing on the input calls the handleFocus function which receives the focussed event. The target variable stores the focussed element. We decided to place the active class on the input’s container, but we don’t know the id or index of the class. We could pass the index, but we can also select the parent element of our target input field and apply the class.

const handleFocus = (e) => {

const target = e.target;

target.parentNode.classList.add('active');

};

Exiting the input field, we want to remove the active class but only if there’s no value. We will keep the container active if there is a value.

const handleBlur = (e) => {

const target = e.target;

if(!target.value) {

target.parentNode.classList.remove('active');

}

};

A field can contain a value on page load. We need to add the active class on the input field but before the user takes any actions. We will do this within the init function. As we loop through each container, we find it’s input and checks if it has a value.

const init = () => {

const floatContainers = document.querySelectorAll('.float-container');



floatContainers.forEach((element) => {



if (element.querySelector('input').value) {

element.classList.add('active');

}



bindEvents(element);

});

};

Placeholder values help users know more about what to enter in an input field. These placeholders appear when a field has no value entered and disappear when a user enters a value. A placeholder text would appear under the label, we need to manage when it shows. We don’t want to use the placeholder attribute on the input field, but we can use a data attribute to store the text:

<input type="text" id="floatField" data-placeholder="some text">

When focussed, add the placeholder attribute with the data attribute value.

const handleFocus = (e) => {

const target = e.target;

target.parentNode.classList.add('active');

target.setAttribute('placeholder', target.getAttribute('data-placeholder'));

};

When exiting the field, we remove the placeholder attribute itself.

const handleBlur = (e) => {

const target = e.target;

if(!target.value) {

target.parentNode.classList.remove('active');

}

target.removeAttribute('placeholder');

};

Our final module looks like this:

Codepen with floating labels in action:

https://codepen.io/steamforge/pen/NBqMYj