Forms
Pedantic form rules are exact: the label sits above the field, the help text sits directly under the label, and button labels are Title Case. Nothing is left to convention.
The pattern
- Label above the field — visible, bold, and
bound with
for/id. Required fields carry an asterisk in the label; the label carries it, so help text never talks about “required”. - Help text under the label — one short line
that says what to enter and why. It sits between the label and the
field, and the field points to it with
aria-describedby. - The field — full width up to 34rem, 2px border,
visible focus ring. Native types (
email,tel,date) so mobile keyboards and autofill work. - Title Case buttons — the submit button names the outcome: “Send Message”, not “Submit”.
Live example — contact form
<form action="/contact" method="post" novalidate>
<div class="pds-field">
<label for="full-name">Full Name <span class="pds-required" aria-hidden="true">*</span></label>
<p class="pds-help" id="full-name-help">Your first and last name, as you sign it.</p>
<input class="pds-input" id="full-name" name="full-name" type="text"
autocomplete="name" required aria-describedby="full-name-help">
</div>
<div class="pds-field">
<label for="email">Email Address <span class="pds-required" aria-hidden="true">*</span></label>
<p class="pds-help" id="email-help">We reply to this address about your message.</p>
<input class="pds-input" id="email" name="email" type="email"
autocomplete="email" required aria-describedby="email-help">
</div>
<div class="pds-field">
<label for="topic">Topic</label>
<p class="pds-help" id="topic-help">Choose the closest match.</p>
<select class="pds-select" id="topic" name="topic" aria-describedby="topic-help">
<option>General Question</option>
<option>Project Inquiry</option>
<option>Support</option>
</select>
</div>
<div class="pds-field">
<label for="message">Message <span class="pds-required" aria-hidden="true">*</span></label>
<p class="pds-help" id="message-help">A few sentences is plenty.</p>
<textarea class="pds-textarea" id="message" name="message" required
aria-describedby="message-help"></textarea>
</div>
<fieldset class="pds-fieldset pds-field">
<legend>Preferred Contact Method</legend>
<p class="pds-help">How we should reply to you.</p>
<div class="pds-check">
<input id="contact-email" name="contact-method" type="radio" value="email" checked>
<label for="contact-email">Email</label>
</div>
<div class="pds-check">
<input id="contact-phone" name="contact-method" type="radio" value="phone">
<label for="contact-phone">Phone</label>
</div>
</fieldset>
<div class="pds-check">
<input id="newsletter" name="newsletter" type="checkbox" value="yes">
<label for="newsletter">Send me the aCo newsletter</label>
</div>
<p><button class="pds-btn pds-btn--primary" type="submit">Send Message</button></p>
</form>Error state
Errors are text, placed under the field, wired in with
aria-describedby, and flagged with
aria-invalid="true". The message says what to fix and
shows the expected shape — it never just says “invalid”.
On submit, focus moves to an error summary at the top of the form
that lists each problem and links to its field.
Live example — field with an error
We reply to this address about your message.
Enter a complete email address, like name@example.com.
<div class="pds-field">
<label for="email-error">Email Address <span class="pds-required" aria-hidden="true">*</span></label>
<p class="pds-help" id="email-error-help">We reply to this address about your message.</p>
<input class="pds-input" id="email-error" name="email" type="email" autocomplete="email"
required aria-invalid="true" aria-describedby="email-error-help email-error-msg"
value="name@example">
<p class="pds-error" id="email-error-msg">Enter a complete email address, like name@example.com.</p>
</div>Rules
Do
Keep help text to one short line: what to enter, and why it is needed.
Use autocomplete attributes for names, emails,
addresses, and phone numbers.
Group related choices in a fieldset with a
legend that asks the question.
Don’t
Don’t put help text below the field, inside the field as a placeholder, or in a tooltip that touch users can’t open.
Don’t mark fields “optional” one by one — required fields carry the asterisk, and that is the whole system.
Don’t split a single question across two fields when one
field with the right type will do.
Accessibility notes
- Every control has exactly one visible label; clicking the label focuses the field, which also enlarges the target.
- Required is conveyed by the asterisk plus the programmatic
requiredattribute — never by color alone. - Checkboxes and radios are at least 20px with their label as part of the target.
- Errors survive a refresh: re-render the form with values and error text intact, and move focus to the summary.