Sep 8, 2025

Designing Input Fields with Floating Labels (HTML + CSS + JavaScript)

Designing Input Fields with Floating Labels (HTML + CSS + JavaScript)

Input fields are one of the most essential components in any form. A well-designed input doesn’t just collect data — it also guides users with clear visual feedback.

Input fields are one of the most essential components in any form. A well-designed input doesn’t just collect data — it also guides users with clear visual feedback.

In this article, we’ll build a simple yet effective floating label input using plain HTML + CSS. We’ll cover the four most important states: Default, Focus, Error, and Disabled.

Default State

So the flow is:

  • When focused → blue label (for active feedback).

  • But if the user clicks outside (blur) → label returns to gray, even if the field has content.

This means we need a little JavaScript to add a class when the input is focused/blurred.


HTML Code for Default State

<!DOCTYPE html>
<html>
  <head>
    <!--<title>Hello, World!</title>-->
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <!-- required-->    
    <div class="field">
      <input type="text" placeholder=" " required>
      <label>Name *</label>
    </div>
    
    <!-- opsional-->    
    <div class="field">
      <input type="text" placeholder=" ">
      <label>Name</label>
    </div>
  <script src="script.js"></script>
  </body>
</html>

CSS Code for Default State (Create in the style.css file)

body {
  font-family: sans-serif;
  background: #f8f9fa;
  padding: 40px;
}

.field {
  position: relative;
  margin-bottom: 2rem;
  width: 350px;
}

.field input {
  width: 100%;
  padding: 14px 12px;
  border: 2px solid #cbd5e1; /* default gray border */
  border-radius: 12px;
  font-size: 16px;
  outline: none;
  transition: 0.2s;
}

.field label {
  position: absolute;
  top: -10px;
  left: 12px;
  font-size: 14px;
  color: #94a3b8; /* abu-abu default */
  background: #fff;
  padding: 0 6px;
  pointer-events: none;
  transition: 0.2s;
}

/* Saat focus aktif → label biru */
.field.active input {
  border-color: #2563eb;
}

.field.active label {
  color: #2563eb;
}

JavaScript Code for Default State (Create in the script.js file)

  const fields = document.querySelectorAll('.field input');

  fields.forEach(input => {
    input.addEventListener('focus', () => {
      input.parentElement.classList.add('active');
    });

    input.addEventListener('blur', () => {
      input.parentElement.classList.remove('active');
    });
  });

Error State

So the flow is:

  • Click on the input (blue label), don’t type anything → click outside → an error will appear (border + red label).

  • Click again (focus) — the label will remain red (the error will remain visible) until you type something; once you start typing, the error message will disappear and the color will return to normal.


HTML Code for Error State

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <!--<title>Error Input Blur</title>-->
  <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="field" id="field-name">
      <input id="name" type="text" placeholder=" " aria-describedby="err-name" />
      <label for="name">Name *</label>                
    <div id="err-name" class="error-message" aria-live="polite">This field is required.</div>
  </div>

  <script src="script.js"></script>
</body>
</html>

CSS Code for Error State (Create in the style.css file)

 :root{
      --gray-border: #cbd5e1;
      --muted: #94a3b8;
      --blue: #2563eb;
      --red: #ef4444;
      --bg: #f8f9fa;
    }

    body{
      font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
      background: var(--bg);
      padding: 40px;
    }

    .field {
     position: relative;
     margin-bottom: 2rem;
     width: 350px;
    }

    /* input box */
    .field input {
      width: 100%;
      padding: 18px 18px;
      border: 2px solid var(--gray-border);
      border-radius: 12px;
      font-size: 28px;           /* sesuai visual yang kamu tunjukkan */
      line-height: 1;
      color: #111827;
      background: #fff;
      outline: none;
      transition: border-color .15s ease, box-shadow .12s ease;
    }

    /* label that sits on the border */
    .field label {
      position: absolute;
      top: -12px;               /* menempel di border atas */
      left: 16px;
      font-size: 16px;
      color: var(--muted);
      background: #fff;
      padding: 0 8px;
      pointer-events: none;
      transition: color .12s ease;
    }

    /* active = user fokus pada input */
    .field.active input {
      border-color: var(--blue);
    }
    .field.active label {
      color: var(--blue);
    }

    /* ERROR rules (diletakkan setelah fokus rules) */
    .field.error input {
      border-color: var(--red);
      box-shadow: 0 0 0 3px rgba(239,68,68,0.06);
    }
    /* pastikan label error selalu merah walau .active ada */
    .field.error label,
    .field.error.active label {
      color: var(--red);
    }

    /* error message */
    .error-message {
      display: none;
      position: absolute;
      left: 12px;
      bottom: -22px;
      font-size: 13px;
      color: var(--red);
    }
    .field.error .error-message {
      display: block;
    }

JavaScript Code for Default State (Create in the script.js file)

 // Select the input + wrapper
    const field = document.getElementById('field-name');
    const input = field.querySelector('input');
    const err = field.querySelector('.error-message');

    // Focus -> show active (blue)
    input.addEventListener('focus', () => {
      field.classList.add('active');
      // don't remove .error here; CSS ensures .error color takes precedence
    });

    // Blur -> validate, remove active
    input.addEventListener('blur', () => {
      field.classList.remove('active');

      // validation rule: required (non-empty)
      if (input.value.trim() === '') {
        field.classList.add('error');
        // show message (CSS handles display)
      } else {
        field.classList.remove('error');
      }
    });

    // While typing: once user provides content, hide error immediately
    input.addEventListener('input', () => {
      if (input.value.trim() !== '') {
        field.classList.remove('error');
      }
    });

Disabled State

Sometimes fields are unavailable. In the disabled state:

  • The input background is gray.

  • The border and label are muted gray.

  • The field cannot be clicked or typed in.


HTML Code for Disabled State

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Disabled Field Example</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="field disabled">
    <label for="name">Name</label>
    <input type="text" id="name" value="Dwiyana Abitya" disabled>
  </div>
</body>
</html>

CSS Code for Disabled State (Create in the style.css file)

body {
  font-family: sans-serif;
  background: #fff;
  padding: 40px;
}

.field {
  position: relative;
  display: inline-block;
  width: 400px;
}

.field label {
  position: absolute;
  top: -10px;
  left: 14px;
  padding: 0 6px;
  font-size: 14px;
  color: #94a3b8; /* abu-abu kebiruan */
  background: #fff; /* biar nutup border */
}

.field input {
  width: 100%;
  padding: 14px 12px;
  border: 2px solid #94a3b8; /* abu-abu kebiruan */
  border-radius: 12px;
  font-size: 18px;
  color: #94a3b8;
  background: #fff;
}

.field input:disabled {
  cursor: not-allowed;
}

And that’s it! With a small amount of CSS, you now have clean and user-friendly inputs that guide users through every state.


Read more articles

Read more articles

Create a free website with Framer, the website builder loved by startups, designers and agencies.