Login Form in HTML & JavaScript
The same accessible login screen built with plain HTML, CSS, and vanilla JavaScript — no framework. A side-by-side reference for the React version.
Source code
<!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="card">
<header class="card__header">
<h1>Welcome back</h1>
<p>Sign in to your account to continue</p>
</header>
<!-- The form is the source of truth: the browser owns the input values. -->
<form id="login-form" novalidate>
<div class="field">
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
required
placeholder="you@example.com"
autocomplete="email"
/>
</div>
<div class="field">
<label for="password">Password</label>
<input
id="password"
name="password"
type="password"
required
minlength="6"
placeholder="••••••••"
autocomplete="current-password"
/>
</div>
<button type="submit">Sign in</button>
</form>
<!-- Swapped in on success; role="status" announces it to screen readers. -->
<p id="success" class="success" role="status" hidden></p>
</main>
<script src="app.js"></script>
</body>
</html>Walkthrough
This is the same login screen as the React example, rebuilt with nothing but HTML, CSS, and a few lines of JavaScript. Reading the two side by side is the fastest way to see what a framework actually adds — and what the platform already gives you for free.
Coming from the React version? The cross-link at the bottom of either page jumps straight to the other. Same UI, two stacks.
1. The markup is the state
In React we held the field values in useState. Here there is no separate state at
all — the <form> and its <input> elements are the source of truth. The browser
keeps track of what each field contains.
<input id="email" name="email" type="email" required />
<input id="password" name="password" type="password" required minlength="6" />type="email", required, and minlength are declarative validation — the same
rules React relied on, but written once in the HTML.
2. Handling submit
React gave us an onSubmit prop; here we attach a listener. preventDefault() does the
same job in both worlds — it stops the browser's full-page reload.
form.addEventListener("submit", (event) => {
event.preventDefault();
if (!form.checkValidity()) {
form.reportValidity();
return;
}
// ...handle the sign-in
});checkValidity() runs every constraint from step 1 in one call, and reportValidity()
shows the browser's native error bubbles — no extra error state to manage.
3. Reading the values
Because there's no controlled state, we read the values at submit time with
FormData instead of tracking every keystroke:
const data = new FormData(form);
const email = data.get("email");This is the biggest difference from React. React re-renders on every keypress so it always has the latest value; the vanilla version simply asks the form for its values when it needs them.
4. The success state
React swapped JSX based on a submitted boolean. Here we toggle the hidden attribute
on two existing elements:
form.hidden = true;
success.hidden = false;
success.textContent = `Signed in as ${email} 🎉`;The role="status" on the success paragraph makes screen readers announce it — exactly
like the React version.
What the framework bought us
Nothing here is less capable, but notice what React handled for you: synchronised state across re-renders, declarative conditional rendering, and component reuse. On a single static form the vanilla version is shorter; as the UI grows and shares logic, that's where React's model starts to pay off. Open the React example to compare.
Compare: Login Form in another stack
The same login screen built two ways. Compare the React component with the plain HTML + JavaScript version to see exactly what the framework does for you.
Login Form with HTML & JavaScript
Step 1 of 2Build a login screen with plain HTML and JavaScript, then add real input validation — same stack, end to end.
Related examples
Login Form UI
A clean, accessible login form component for React with email, password, and social sign-in.
View example