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.
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.
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>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>. Settype="email"andtype="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: centerandalign-items: center. Give the form amax-widthso 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
typebetweenpasswordandtextwhen a button is clicked. Keep the button inside the field's container, and update itsaria-pressedstate so the control is announced correctly.