Chapter 8: Building Accessible Forms Without a Framework
How to create real-world, inclusive forms with just HTML and CSS and no JavaScript bloat
Forms are where your users take action.
They’re also where accessibility breaks the most.
The biggest irony in frontend today?
We reach for entire frameworks to build forms… that HTML already knows how to do.
This chapter will teach you how to build accessible, semantic, and responsive forms without libraries just by using native HTML and a bit of CSS knowledge.
Section 8.1: The Anatomy of a Proper Form
Every form has three essential parts:
- Field: input, select, textarea, etc.
- Label: explains what the input is for
- State: required, invalid, described by help text, etc.
A complete example:
<form>
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
required
aria-describedby="email-help"
/>
<small id="email-help">We'll never share your email.</small>
<button type="submit">Subscribe</button>
</form>
This form:
- Is fully accessible
- Has a programmatically associated label
- Describes the field with help text
- Supports native HTML validation
No React needed. No schema. Just semantics.
Section 8.2: Why label
Is Non-Negotiable
If you skip <label>
, screen readers can’t see it.
Even if you think a placeholder
is enough, it’s not.
<label for="username">Username</label>
<input id="username" type="text" />
OR:
<label>
Username
<input type="text" />
</label>
Both work.
Avoid styling with
display: none;
. Use.visually-hidden
so labels are still read by assistive tech.
Section 8.3: Use fieldset
and legend
for Groups
Grouping related inputs (like checkboxes or radios)? Use fieldset
.
<fieldset>
<legend>Choose your plan</legend>
<label><input type="radio" name="plan" /> Basic</label>
<label><input type="radio" name="plan" /> Premium</label>
</fieldset>
This gives screen readers a grouped context, not just a list of isolated options.
Section 8.4: Validations Without JavaScript
Use HTML5’s built-in attributes:
required
type="email"
/type="url"
/type="number"
min
,max
,pattern
step
,maxlength
, etc.
The browser handles validation, shows messages, and prevents submission without a single line of JS.
You can style invalid fields with
:invalid
and:valid
.
input:invalid {
border: 2px solid red;
}
Section 8.5: Describe Help Text and Errors with ARIA
Use aria-describedby
to link help text or error messages to fields.
<input id="zip" aria-describedby="zip-help" />
<small id="zip-help">Enter your 5-digit ZIP code.</small>
Screen readers will read both the input label and the help text giving full context.
Section 8.6: Keyboard Navigation Best Practices
Tab
should move between all interactive elementsEnter
should submitEsc
should cancel (if implemented)
Avoid:
- Custom dropdowns that trap focus
- Form fields that break on copy/paste
- Losing focus when the DOM updates
Bonus: Use tabindex="-1"
on alert messages so you can focus them with JS when needed.
Section 8.7: Visual Tips Without Breaking Accessibility
Use :focus-visible
:
input:focus-visible {
outline: 2px solid blue;
}
Only shows focus ring for keyboard users.
Use :has()
for parent styling:
.form-group:has(input:invalid) {
border-color: red;
}
(Chrome & Safari only)
Section 8.8: Forms That Just Work on Mobile
- Use correct
type
to trigger better keyboards:type="email"
shows @ keytype="tel"
shows number pad
- Use
autocomplete
attributes:<input autocomplete="email" /> <input autocomplete="postal-code" />
- Use
inputmode
for fine control:<input type="text" inputmode="numeric" />
Summary
- Use semantic HTML:
form
,label
,input
,fieldset
,legend
- Validate with native attributes before writing JS
- Use ARIA attributes for help/error text
- Don’t remove labels. Use
.visually-hidden
- Style with
:invalid
,:focus-visible
, and:has()
- Use mobile-friendly inputs
You don’t need a form library to build a great form; you just need to respect what HTML already does well.
Resources
Coming Up Next
In Chapter 9, we’ll explore real-world responsive design with layout patterns that scale from mobile to desktop, no frameworks or grid systems required.