Blog

Blog · Templates

Free Login Page Template (HTML, CSS & JS, 2026)

A clean, accessible login page template — a centered card with labelled email and password fields, show-password toggle and inline validation — responsive and ready to wire to your backend.

XCODX Team · 4 min read

This free login page template is a clean, accessible sign-in form: a centered card with properly labelled email and password fields, a show-password toggle, inline validation and a clear submit button. It’s responsive, dependency-free, and built with accessibility in mind so it works for everyone. Wire the form to your own authentication backend and you’re done.

What you get

  • A centered, responsive login card that looks right on any screen.
  • Accessible fields: real labels, autocomplete, focus states, and error messaging with aria-live.
  • A show/hide password toggle and lightweight inline validation.
  • Clean CSS-variable theming and zero dependencies.

The HTML

Save as index.html. Every input has a label and an autocomplete attribute so browsers and password managers behave correctly.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Sign in</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <main class="auth">
    <form class="card" id="login" novalidate>
      <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="email"
               required placeholder="[email protected]">
      </div>

      <div class="field">
        <label for="password">Password</label>
        <div class="pw">
          <input id="password" name="password" type="password"
                 autocomplete="current-password" required minlength="8">
          <button type="button" class="toggle" data-toggle aria-label="Show password">Show</button>
        </div>
      </div>

      <p class="error" id="error" role="alert" aria-live="polite"></p>

      <button type="submit" class="btn">Sign in</button>
      <p class="alt">No account? <a href="#">Create one</a></p>
    </form>
  </main>

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

The CSS

:root {
  --bg: #0b0d14; --card: #141824; --text: #e8ecf4; --muted: #9aa4b8;
  --accent: #6ea8ff; --border: #2a3040; --error: #ff8a7a; --radius: 12px;
  color-scheme: dark;
}
* { box-sizing: border-box; margin: 0; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  background: var(--bg); color: var(--text); }
.auth { min-height: 100vh; display: grid; place-items: center; padding: 20px; }
.card { width: 100%; max-width: 380px; background: var(--card);
  border: 1px solid var(--border); border-radius: 16px; padding: 32px; }
h1 { font-size: 24px; }
.sub { color: var(--muted); margin: 6px 0 24px; }
.field { margin-bottom: 16px; }
label { display: block; font-size: 14px; font-weight: 600; margin-bottom: 6px; }
input { width: 100%; padding: 12px 14px; border-radius: var(--radius);
  border: 1px solid var(--border); background: #0e1119; color: var(--text); font-size: 15px; }
input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; }
.pw { position: relative; display: flex; }
.toggle { position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  background: none; border: 0; color: var(--muted); font-size: 13px; cursor: pointer; padding: 6px; }
.error { color: var(--error); font-size: 14px; min-height: 20px; margin-bottom: 8px; }
.btn { width: 100%; padding: 13px; border: 0; border-radius: var(--radius);
  background: var(--accent); color: #06080f; font-weight: 700; font-size: 15px; cursor: pointer; }
.btn:hover { filter: brightness(1.06); }
.alt { text-align: center; color: var(--muted); margin-top: 18px; font-size: 14px; }
.alt a { color: var(--accent); }

The JavaScript

Save as login.js. This handles the password toggle and basic client-side validation — the server must still validate and authenticate.

const form = document.getElementById('login');
const error = document.getElementById('error');

// show / hide password
document.querySelector('[data-toggle]').addEventListener('click', (e) => {
  const input = document.getElementById('password');
  const showing = input.type === 'text';
  input.type = showing ? 'password' : 'text';
  e.target.textContent = showing ? 'Show' : 'Hide';
  e.target.setAttribute('aria-label', showing ? 'Show password' : 'Hide password');
});

form.addEventListener('submit', (e) => {
  e.preventDefault();
  error.textContent = '';
  const { email, password } = form.elements;
  if (!email.value || !email.validity.valid) {
    error.textContent = 'Enter a valid email address.';
    email.focus();
    return;
  }
  if (password.value.length < 8) {
    error.textContent = 'Password must be at least 8 characters.';
    password.focus();
    return;
  }
  // TODO: send to your API over HTTPS
  // fetch('/api/login', { method: 'POST', body: JSON.stringify({ email: email.value, password: password.value }) })
  console.log('Ready to submit');
});

Wire it to a backend safely

  • Always HTTPS. Never send credentials over plain HTTP.
  • Validate on the server too. Client-side checks are for UX; the server is the source of truth.
  • Never store plain passwords. Hash them with a strong algorithm (bcrypt/argon2) — see Node.js best practices.
  • Consider a provider. Auth.js, Clerk or your platform’s auth save you from building (and securing) this yourself.
  • Rate-limit and add 2FA for real applications to resist brute-force attacks.

Frequently asked questions

Is this login template secure?
The template gives you an accessible, correct front end, but security lives on the server. Always serve it over HTTPS, validate and authenticate on the backend, hash passwords with bcrypt or argon2 (never store them in plain text), rate-limit login attempts, and consider two-factor auth. For most apps, using an auth provider (Auth.js, Clerk) is safer than rolling your own.
How do I connect the login form to my backend?
Replace the console.log in login.js with a fetch POST to your authentication endpoint over HTTPS, sending the email and password, and handle the response (set a session/token on success, show an error on failure). See our guide on building REST APIs for the server side.
Is the login page accessible?
Yes. Every field has a real <label> tied by for/id, inputs use autocomplete hints so password managers work, focus states are visible, and validation errors are announced to screen readers via role="alert" and aria-live. These are the fundamentals that make a form usable for everyone.
Can I add a "remember me" or social login?
Yes. Add a checkbox with a label for "remember me" and read it on submit, and add social-login buttons that redirect to your OAuth provider’s flow. Keep social buttons clearly labelled and accessible, and handle the OAuth callback on your server.

Build and test this login form live in XCODX Studio. See also the contact form template, dashboard template and accessibility best practices.