Using Placeholders and Input Fields

Using Placeholders and Input Fields Banner

A popular pattern in web design uses the input field, a placeholder that slides up on focus or while a user is typing. Different frameworks that are based on material design implement it in different ways, but each of them uses JavaScript to handle changes in the input field. Can we solve this problem with no JS code?

With CSS4-Selectors, we are ready to implement it just with CSS. This solution can involve framework agnostics.

So, Let’s Hype!

…and style it with variables, Level 4 selectors, and Bootstrap 4 Alpha.

Note: This solution works well in Chrome.

Where can you use it right now?

  • Chrome extensions.

  • An application on Cordova with integrated Chromium webview.

  • Developing desktop application on Electron.

Another note: The demo and code are available free to use right now in CodePen.

HTML Markup for Element

It’s not very pretty, but we have to include a label after the input in HTML because we can get access to the next sibling element in CSS, but not to the previous.

<label class='material-form-group'>
    <input type='text' placeholder='M'>
    <label>Regular input field</label>

Also, we need to set a placeholder attribute that must have text. It will help if there's some text in the input (unfortunately, :empty does not apply to input tags).

To use Bootstrap, we need to wrap it in framework’s classes:

<label class="form-group material-form-group">
    <input type="text" placeholder="M" class="form-control">
    <label>Regular input field</label>

This solution will work in Bootstrap version 3 and 4 well. (Thanks to developers that save class names the same.)

Dive Into CSS Code

Define the variables in :root. Follow some name conventions to isolate our variables with the prefix "mi" (material input).

:root {
    --mi-animation-speed: 0.314s;
    --mi-label-default-color: rgba(0, 0, 0, 0.314);
    --mi-indent-top: 15px;
    --mi-label-focus-color: dodgerblue;
    --mi-invalid-color: firebrick;
    --mi-bg-color: white; /* should match parent container color */

This is the main container relative to building our layout. Reserve some space above the container for the rising up label.

.material-form-group {
    margin-top: var(--mi-indent-top);
    position: relative;

Default styles of the label will be positioned on the top. This will be OK for browsers that can't support our main solution. Disable selecting the text of this element because if a user selects text in the label, the focus will not handle it.

.material-form-group label {
    position: absolute;
    transform-origin: 0 150%;
    margin-left: var(--mi-indent-left);
    transition: var(--mi-animation-speed);
    -webkit-user-select: none;
    user-select: none;
    color: var(--mi-label-default-color);

The selector input:placeholder-shown will detect that placeholder in the input field is shown. Then, we choose label next sibling to it. (If we had the functionality to choose the previous sibling label, we shouldn’t put label after input; it could be more semantic.)

.material-form-group input:placeholder-shown + label {
    top: 50%;
    transform: scale(1, 1) translateY(-50%);
    background: transparent;

Align the label vertically to the relative element with technic translateY and top:50% and it will not depend on itself or container heights. We need to have a placeholder in the layout, but it should be invisible. Hide it with transparent colour.

.material-form-group input::-webkit-input-placeholder {
    color: transparent;

Focus on typing the highlight input field.

.material-form-group input:focus + label {
    color: var(--mi-label-focus-color);

Style active state means that the input is focused or has no placeholder (the placeholder should be declared above this code). Move the label to the top and make it smaller.

.material-form-group input:focus + label,
.material-form-group label {
    top: -100%;
    transform: scale(0.8, 0.8) translateY(100%);
    background: var(--mi-bg-color);

We can finish here, but let’s style the field when it is invalid. Content in styles is not good practice, but the main goal is to solve this task with CSS only.

.material-form-group input:invalid + label:after {
    color: var(--mi-invalid-color);
    content: "(invalid)";
    margin-left: 0.314rem;


With time, CSS has become more accurate, can describe behavior concisely, and looks very handsome. Unfortunately, we can’t use it worldwide. My dream is to have tools like Babel in JavaScript to transform new CSS to cross-browser, but I think we can’t do without artificial intelligence.


The artcile was specially written for

November 14, 2016