Production-Grade Forms
Prerequisites: Better forms: hints, errors, and feedback, Sanitizing user content
Forms on real sites face attackers, bots, password managers, slow networks, and double clicks. The HTML side of handling them well:
CSRF tokens
Cross-site request forgery: another site makes a visitor's browser submit a form to your site, with the visitor's cookies attached. The standard defense is a secret token in every state-changing form, checked by the server:
<form action="/account/email" method="post">
<input type="hidden" name="_token" value="c2f1e9a0b7d44f6e8a1d3b5c7e9f0a2b">
<label for="new-email">New email</label>
<input id="new-email" name="email" type="email" autocomplete="email" required>
<button>Update email</button>
</form>
Frameworks generate this for you (@csrf in Laravel,
{% csrf_token %} in Django). SameSite cookies help too. Never change
data with GET requests — links and image tags can trigger those.
Login and sign-up forms that work with password managers
<form action="/signup" method="post">
<label for="su-email">Email</label>
<input id="su-email" name="email" type="email" autocomplete="username" required>
<label for="su-pw">Password</label>
<input id="su-pw" name="password" type="password" autocomplete="new-password" minlength="12" required aria-describedby="su-pw-hint">
<p id="su-pw-hint">At least 12 characters.</p>
<button>Create account</button>
</form>
autocomplete="username"on the identifier (even if it's an email),current-passwordon login,new-passwordon sign-up and change forms — password managers then offer to generate and save a password.- Don't block pasting, and don't split codes into one box per digit without handling paste.
- For passkeys,
autocomplete="username webauthn"lets the browser offer saved passkeys in the username field.
Bots: a honeypot field
<form action="/contact" method="post">
<div class="visually-hidden" aria-hidden="true">
<label for="website">Leave this field empty</label>
<input id="website" name="website" tabindex="-1" autocomplete="off">
</div>
<label for="c-msg">Message</label>
<textarea id="c-msg" name="message" required></textarea>
<button>Send</button>
</form>
Simple bots fill every field; the server discards submissions where
website isn't empty. It stops only unsophisticated bots — pair it with
rate limiting.
File uploads
acceptonly filters the file picker. The server must check the file type (by content, not name), size, and store uploads outside the web root.- Show the maximum size in the label.
Double submission and feedback
Disable the submit button (with JavaScript) once the form is submitted, and show progress. After a successful POST, the server should redirect to a result page (the Post/Redirect/Get pattern), so reloading doesn't submit again.
Keeping forms usable
- One column, labels above fields, and
autocompleteeverywhere. - Mark optional fields rather than required ones when most are required.
- Keep values after a server-side error, and focus the error summary.
enterkeyhint="next"/"send"improves phone keyboards.