Blog
How to Build a Responsive Registration Form with HTML and CSS
A registration form should be clear on a phone, easy to use with a keyboard, and specific about what each field expects. In this tutorial, you’ll build a responsive sign-up form with semantic HTML, accessible labels, CSS Grid, and a small JavaScript check that confirms both password fields match.
Quick answer: Connect each visible label to its input, use native input types and required constraints, and let CSS Grid adapt the layout to narrow screens. JavaScript checks that the password fields match and displays a demo confirmation. This front-end example does not create an account or send data to a server.
What you’ll build
The example includes full name, email, password, and password confirmation fields. It has visible labels, browser validation, clear keyboard focus, a nearby password error message, and a success message for the demo. Styles are scoped to .registration-demo, so they do not change other forms or buttons in your WordPress theme.
1. Create the form with semantic HTML
Use a <form> element and a visible <label> for every input. Match each label’s for value to the input’s id. Add required, type="email", and minlength so the browser can provide basic validation. Set autocomplete to help password managers fill the right fields.
The confirmation field uses aria-describedby to connect it to an error message. A polite status region announces the demo result to assistive technology. See the HTML code widget below for the complete form markup.
2. Make the layout responsive with CSS
Give the form card a fluid width and a maximum size, then use CSS Grid for consistent spacing. minmax(0, 1fr) prevents columns from overflowing, while a media query stacks the fields on narrow screens. Keep a visible focus outline so keyboard users can see the active field.
The stylesheet is scoped under .registration-demo. Broad selectors such as form, input, or button could unexpectedly restyle other theme elements.
3. Check that the password fields match
Native browser constraints check required fields, email format, and password length. JavaScript compares the confirmation value with the password, sets a field-specific message, and prevents the demo confirmation when the values differ.
The submit handler prevents a network request and displays a message that no account was created. A production registration flow must submit over HTTPS to a trusted backend, validate data again on the server, hash passwords with a suitable password-hashing function, and include protections such as CSRF controls and rate limiting.
Try the interactive registration form
Enter sample details and submit the form to see its local confirmation. Try mismatched passwords to see the validation message. Use fictional information only: the demo does not send, store, or create an account with the values entered.
Customize the form
- Change the
minlengthvalue to match the policy enforced by your application. - Add fields by pairing each input with a visible label and a unique
id. - Keep validation messages beside the field they describe and connect them with
aria-describedby. - Replace the demo submit handler only after you have a secure server endpoint ready.
- Adjust the CSS custom properties to change colors and corner radius.
Accessibility and security notes
Do not use placeholders as the only field labels. Keep keyboard focus visible, announce dynamic feedback through a status region, and ensure text remains readable. Browser validation improves usability but is not a security boundary. Never collect real passwords with a front-end-only form; validate on the server and store passwords using a password-hashing algorithm.
Common issues
The submit button appears to do nothing
Complete the required fields and follow the browser’s validation message. The browser will not fire the submit event until built-in validation passes.
The email field accepts an address my service rejects
type="email" checks a basic format, not whether the address exists. Validate it on your server and verify ownership when required.
The form looks different inside my theme
Keep the CSS scoped under .registration-demo. Avoid global rules for input, button, or form when embedded in a theme page.
Frequently asked questions
Does this form create a real account?
No. The live example only displays a local demo message. It does not send, store, or create an account with the information entered.
Why use type="email"?
It provides a suitable keyboard on many phones and basic browser format checks. It does not prove that an address exists or belongs to the person entering it.
Can I use this as a production registration form?
Use the markup and styles as a starting point, then connect them to a secure backend or trusted form service. All security checks must also happen on the server.
Summary
This responsive registration form combines semantic HTML, native validation, mobile-friendly CSS Grid, and a short password-match check. Try the live demo, then download the ZIP to explore the complete example. Connect a secure backend before collecting real account details.
Demo only: do not enter a real password. This form stays in your browser and does not send or save the values.
const form = document.querySelector('#swc-registration-form');
const password = document.querySelector('#swc-password');
const confirmation = document.querySelector('#swc-confirm-password');
const error = document.querySelector('#swc-password-error');
const notice = document.querySelector('#swc-form-notice');
function validatePasswords() {
const mismatch = confirmation.value !== '' && confirmation.value !== password.value;
confirmation.setAttribute('aria-invalid', String(mismatch));
error.textContent = mismatch ? 'The passwords do not match.' : '';
confirmation.setCustomValidity(mismatch ? 'The passwords do not match.' : '');
}
password.addEventListener('input', validatePasswords);
confirmation.addEventListener('input', validatePasswords);
form.addEventListener('submit', (event) => {
event.preventDefault();
validatePasswords();
if (!form.reportValidity()) return;
notice.hidden = false;
});
#swc-registration-demo .registration-demo {
max-width: 680px;
margin: 20px auto;
padding: clamp(22px, 4vw, 36px);
background: #f6f8ff;
border: 1px solid #d8e1f2;
border-radius: 18px;
color: #172033;
font: 16px/1.5 system-ui, sans-serif;
}
#swc-registration-demo .registration-demo form {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
#swc-registration-demo .registration-demo .field { display: grid; gap: 6px; }
#swc-registration-demo .registration-demo .wide { grid-column: 1 / -1; }
#swc-registration-demo .registration-demo input {
width: 100%; min-height: 46px; padding: 10px 12px;
border: 1px solid #9aa8bd; border-radius: 9px; font: inherit;
}
#swc-registration-demo .registration-demo input:focus-visible,
#swc-registration-demo .registration-demo button:focus-visible {
outline: 3px solid #83b6ff; outline-offset: 2px;
}
#swc-registration-demo .registration-demo .error { min-height: 1.25em; color: #b42318; }
#swc-registration-demo .registration-demo button {
min-height: 48px; border: 0; border-radius: 9px;
background: #315fc4; color: white; font: inherit; font-weight: 700;
}
@media (max-width: 560px) {
#swc-registration-demo .registration-demo form { grid-template-columns: 1fr; }
#swc-registration-demo .registration-demo .wide { grid-column: auto; }
}
<form id="swc-registration-form">
<div class="field">
<label for="swc-full-name">Full name</label>
<input id="swc-full-name" name="name" autocomplete="name" required>
</div>
<div class="field">
<label for="swc-email">Email address</label>
<input id="swc-email" name="email" type="email" autocomplete="email" required>
</div>
<div class="field">
<label for="swc-password">Password (at least 12 characters)</label>
<input id="swc-password" name="password" type="password" minlength="12" required>
</div>
<div class="field">
<label for="swc-confirm-password">Confirm password</label>
<input id="swc-confirm-password" name="confirm-password" type="password"
aria-describedby="swc-password-error" minlength="12" required>
<p id="swc-password-error" class="error" aria-live="polite"></p>
</div>
<button type="submit">Try registration</button>
<p id="swc-form-notice" role="status" hidden>
Demo complete. No account was created and no data was sent.
</p>
</form>