Give every field a visible label
A placeholder disappears as soon as someone types. Keep the name of the field visible and connect it to the input using for and id. Put a hint beside the field when a format or constraint needs explanation.
<form action="/subscribe" method="post">
<label for="email">Email address</label>
<p id="email-hint">Used only for the monthly reading list.</p>
<input
id="email"
name="email"
type="email"
autocomplete="email"
aria-describedby="email-hint"
required
>
<button type="submit">Subscribe</button>
</form>Make the backend part of the design
This markup assumes that you have implemented a POST endpoint at /subscribe. HTML alone does not store the address or send a confirmation email. Build and test the server behavior before publishing the form, and explain what someone should expect after submission.
Validate on both sides
The browser can catch an empty required field or an obviously invalid email address. It cannot replace server validation. Treat client input as untrusted, apply size limits, and return a clear error without losing the values someone has already entered.
Keep errors close to the field
When the server renders an error, associate its text with the relevant input using aria-describedby and set aria-invalid="true". Say what went wrong and what to change. Avoid identifying an error through color alone; text and a visible border give more people a usable signal.
HTML forms specification ↗