How to Create a Login Form in HTML and CSS

This recipe builds an accessible, centered login card with a properly labeled email/username field, a masked password field, and a submit button, all validated by native HTML5 attributes before any server ever sees the request.

What a Login Form Actually Needs

A login form looks simple, but it has one job that matters more than any visual polish: getting the right credentials to your server without confusing the person typing them in. That means every input needs a visible label, the right input type so browsers and password managers can help out, and enough built-in validation that obviously empty or malformed submissions never leave the browser.

  • A <form> element wrapping everything, so Enter key submission and browser autofill work correctly
  • A <label> tied to each input via matching for/id attributes, not just placeholder text
  • type="email" (or text, if you accept usernames) for the identifier field
  • type="password" so the field masks characters as they're typed
  • autocomplete hints (username, current-password) so password managers fill the form correctly
  • A required attribute on both fields to block empty submissions client-side

Example

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
<style>
  * { box-sizing: border-box; }
  body {
    font-family: system-ui, sans-serif;
    background: #f3f4f6;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    margin: 0;
    padding: 1rem;
  }
  .login-card {
    background: #fff;
    padding: 2rem;
    border-radius: 12px;
    box-shadow: 0 10px 25px rgba(0,0,0,0.08);
    width: 100%;
    max-width: 360px;
  }
  .login-card h1 {
    font-size: 1.5rem;
    margin: 0 0 1.5rem;
    text-align: center;
  }
  .field {
    margin-bottom: 1.25rem;
  }
  .field label {
    display: block;
    font-size: 0.875rem;
    font-weight: 600;
    margin-bottom: 0.375rem;
  }
  .field input {
    width: 100%;
    padding: 0.625rem 0.75rem;
    border: 1px solid #d1d5db;
    border-radius: 8px;
    font-size: 1rem;
  }
  .field input:focus {
    outline: 2px solid #2563eb;
    outline-offset: 1px;
    border-color: #2563eb;
  }
  .submit-btn {
    width: 100%;
    padding: 0.75rem;
    background: #2563eb;
    color: #fff;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: 600;
    cursor: pointer;
  }
  .submit-btn:hover {
    background: #1d4ed8;
  }
</style>
</head>
<body>
  <form class="login-card" action="/login" method="post">
    <h1>Sign In</h1>
    <div class="field">
      <label for="login-email">Email</label>
      <input type="email" id="login-email" name="email" autocomplete="username" required>
    </div>
    <div class="field">
      <label for="login-password">Password</label>
      <input type="password" id="login-password" name="password" autocomplete="current-password" required minlength="8">
    </div>
    <button type="submit" class="submit-btn">Log In</button>
  </form>
</body>
</html>

Notice that every <label> has a for attribute pointing at the matching input's id. This isn't decorative: clicking the label text moves focus to the input, and screen readers announce the label whenever that field receives focus. Placeholder text is not a substitute for a label — placeholders disappear the moment someone starts typing, and many screen readers don't announce them the same way.

Note: Use autocomplete="username" on the identifier field and autocomplete="current-password" on the password field. This is a separate concern from the name attribute, and it's what lets browsers and password managers correctly recognize a login form instead of guessing.

Building the Card Layout

The .login-card class centers the form both by using flexbox on the body and by capping the card's own width with max-width. Keeping the card narrow (around 360px) keeps line lengths for labels and inputs short, which is easier to scan than a full-width form stretched across a wide screen. The box-shadow gives the card just enough separation from the background to read as a distinct surface, without needing a heavy border.

AttributeWhy it's used
type="email"Triggers email-style keyboards on mobile and lets the browser flag malformed addresses before submit
autocomplete="username"Tells password managers which field holds the identifier, even when it's an email address
autocomplete="current-password"Distinguishes a login field from a new-password field, so managers offer to fill rather than generate
minlength="8"Blocks obviously-too-short passwords early, though the server must enforce the real minimum

A small usability improvement worth adding is a show/hide password toggle, since a masked password field makes typos hard to catch. This only needs a positioned button and a few lines of JavaScript that flip the input's type attribute between password and text.

Example

<!DOCTYPE html>
<html>
<head>
</head>
<body>

<div class="field">
  <label for="login-password">Password</label>
  <div style="position: relative;">
    <input type="password" id="login-password" name="password"
           autocomplete="current-password" required
           style="width: 100%; padding: 0.625rem 4.5rem 0.625rem 0.75rem; border: 1px solid #d1d5db; border-radius: 8px; font-size: 1rem;">
    <button type="button" id="toggle-password"
            style="position: absolute; right: 6px; top: 6px; bottom: 6px; border: none; background: #e5e7eb; border-radius: 6px; padding: 0 0.75rem; cursor: pointer; font-size: 0.8rem;">
      Show
    </button>
  </div>
</div>

<script>
  const toggleBtn = document.getElementById('toggle-password');
  const passwordInput = document.getElementById('login-password');

  toggleBtn.addEventListener('click', () => {
    const isHidden = passwordInput.type === 'password';
    passwordInput.type = isHidden ? 'text' : 'password';
    toggleBtn.textContent = isHidden ? 'Hide' : 'Show';
    toggleBtn.setAttribute('aria-label', isHidden ? 'Hide password' : 'Show password');
  });
</script>

</body>
</html>
Note: Always keep an aria-label (or update it, as this example does) on icon-only or text-only toggle buttons like "Show"/"Hide", so screen reader users know what the button currently does before they press it.

Frequently Asked Questions

How do I create a login form in HTML?
Use a <form> with an email input, a password input and a submit button, each paired with a <label>. Set type="email" and type="password" so browsers validate the address and mask the password automatically.
How do I center a login form on the page?
Make the body a flex container with min-height: 100vh, justify-content: center and align-items: center. Give the form a max-width so it stays a readable width on large screens instead of stretching.
How do I add show and hide password to a login form?
Toggle the input's type between password and text when a button is clicked. Keep the button inside the field's container, and update its aria-pressed state so the control is announced correctly.