Formclick

How to Create a Login Form in HTML

Build a clean, accessible login form in HTML and CSS that works with password managers — plus the security basics every login page needs.

FFormclick Team4 min read
Close-up of a computer screen displaying an authentication failed message.
Photo by Markus Spiske on Pexels
On this page
  1. An important note before we start
  2. The HTML
  3. The CSS
  4. The show-password toggle
  5. Security checklist
  6. Accessibility checklist
  7. Where Formclick fits in

A login form looks simple — two fields and a button — but small details decide whether it's pleasant or painful. Does the password manager fill it in? Can people see what they typed? Does it work with a keyboard? Is it secure?

This guide builds a polished login form in HTML and CSS and explains the security basics behind it.

An important note before we start

The HTML for a login form is the easy part. Actually logging someone in — checking the password against a securely hashed copy, creating a session, rate-limiting guesses — must happen in your application's backend or an authentication provider (such as Auth0, Clerk, Supabase Auth or Firebase Auth).

A form backend like Formclick is built for collecting submissions (contact messages, registrations, feedback). It is not an authentication system, and you should never send passwords to a service that just stores and emails form data.

The HTML

<main class="auth">
  <form class="login" action="/login" method="POST">
    <h1>Welcome back</h1>
    <p class="sub">Sign in to your account</p>

    <div class="field">
      <label for="email">Email</label>
      <input id="email" name="email" type="email"
             autocomplete="username" required autofocus>
    </div>

    <div class="field">
      <div class="label-row">
        <label for="password">Password</label>
        <a href="/forgot-password">Forgot password?</a>
      </div>
      <div class="password">
        <input id="password" name="password" type="password"
               autocomplete="current-password" required>
        <button type="button" class="toggle" aria-controls="password"
                aria-pressed="false">Show</button>
      </div>
    </div>

    <label class="remember">
      <input type="checkbox" name="remember" value="1"> Keep me signed in
    </label>

    <button type="submit" class="primary">Sign in</button>

    <p class="alt">New here? <a href="/signup">Create an account</a></p>
  </form>
</main>

The details that matter

  • method="POST" — never GET, which would put the password in the URL, browser history and server logs.
  • autocomplete="username" on the email field and autocomplete="current-password" on the password field. These tell password managers exactly what to fill.
  • The toggle has type="button" so clicking "Show" doesn't submit the form.
  • "Forgot password?" sits next to the label, where people look when they're stuck.
  • Real labels, not just placeholders — they stay visible and are read by screen readers.

The CSS

.auth {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px 16px;
  background: linear-gradient(135deg, #f5f5ff, #eef2ff);
  font-family: system-ui, sans-serif;
}
.login {
  width: 100%;
  max-width: 400px;
  padding: 36px 32px;
  background: #fff;
  border-radius: 20px;
  box-shadow: 0 20px 50px -20px rgba(30, 27, 75, .25);
}
.login h1 { margin: 0; font-size: 26px; }
.sub { margin: 4px 0 28px; color: #64748b; }

.field { margin-bottom: 18px; }
.field label { display: block; font-weight: 600; margin-bottom: 6px; }
.label-row { display: flex; justify-content: space-between; align-items: baseline; }
.label-row a { font-size: 14px; color: #645cec; text-decoration: none; }

.field input {
  width: 100%;
  padding: 12px 14px;
  font: inherit;
  border: 1px solid #d4d4e0;
  border-radius: 10px;
}
.field input:focus {
  outline: none;
  border-color: #645cec;
  box-shadow: 0 0 0 3px rgba(100, 92, 236, .2);
}

.password { position: relative; }
.password input { padding-right: 70px; }
.toggle {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  padding: 6px 10px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: #645cec;
  background: transparent;
  border: 0;
  cursor: pointer;
}

.remember { display: flex; gap: 8px; align-items: center; margin-bottom: 22px; }

.primary {
  width: 100%;
  padding: 14px;
  font: inherit;
  font-weight: 700;
  color: #fff;
  background: #645cec;
  border: 0;
  border-radius: 10px;
  cursor: pointer;
}
.primary:hover { background: #4f46e5; }
.alt { margin: 22px 0 0; text-align: center; color: #64748b; }
.alt a { color: #645cec; font-weight: 600; }

The show-password toggle

Letting people check what they typed reduces failed logins, especially on phones. A few lines of JavaScript do it:

const toggle = document.querySelector('.toggle');
const input = document.getElementById('password');

toggle.addEventListener('click', function () {
  const showing = input.type === 'text';
  input.type = showing ? 'password' : 'text';
  toggle.textContent = showing ? 'Show' : 'Hide';
  toggle.setAttribute('aria-pressed', String(!showing));
});

Without JavaScript the button simply does nothing, and the form still works.

Security checklist

  • Always use HTTPS — otherwise passwords travel in plain text.
  • Use generic error messages such as "Email or password is incorrect". Saying "No account with that email" tells attackers which emails are registered.
  • Rate-limit attempts on the server and consider a CAPTCHA after several failures.
  • Hash passwords with a slow algorithm such as bcrypt or Argon2. Never store them as plain text.
  • Protect against CSRF with a token in a hidden field if you use cookie sessions.
  • Don't block paste into password fields — it breaks password managers and pushes people towards weaker passwords.
  • Offer passkeys or two-factor authentication where you can.

Accessibility checklist

  • Every field has a visible <label>.
  • Error messages are linked to their field with aria-describedby and announced.
  • Focus styles are clearly visible — never remove outlines without a replacement.
  • The whole flow works with the keyboard alone.

Where Formclick fits in

Your login page often has neighbours that are perfect for a form backend: a "Request access" form, a "Contact sales" form, or a waitlist for an app that hasn't launched yet. Those can post straight to Formclick with no server code — see How to Create an HTML Contact Form or our waitlist form template.

To make your login screen look great on every device, continue with How to Style HTML Forms Using CSS.

Found this useful? Share it.

All articles