Webbo3 Level 2 · JavaScript Development · Day 13 of 20
Forms, UX and Accessibility
The day you unplug your mouse. Validation people can act on, and an interface that works for everybody who visits it.
By the end of today you can
- Use the browser's own validation, and replace only its wording
- Write error messages that tell people what to do next
- Operate your whole application with the keyboard alone
- Choose the right element, so accessibility comes free
- Manage focus, so a keyboard user is never lost
- Announce changes that happen without a page reload
This is the lesson most bootcamps skip, and skipping it is why so much of the web is unusable for a lot of people.
Accessibility means building so that everyone can use what you make: people using a keyboard instead of a mouse, people using a screen reader (software that reads the page aloud for blind users), people who zoom in, or who cannot tell red from green. It is increasingly a legal requirement, and it is one of the few things a junior developer can be noticeably better at than the team they join.
1. The Browser Validates for Free
Definition
HTML attributes such as required, type="email" and pattern make the browser check a field before the form is sent. The Constraint Validation API lets your JavaScript read those checks, so you keep the browser's checking and write only better wording.
Real-Life Example
A bank form already has boxes marked "required" and "digits only". The bank clerk does not invent new rules; they just explain the existing ones kindly when you get one wrong.
Types: The Attributes
| Attribute | What it gives you |
|---|---|
type="email" | An email check, and the right keyboard on a phone |
type="tel" inputmode="numeric" | A number pad instead of a full keyboard |
required | The field cannot be left empty |
pattern="[0-9]{11}" | A shape the value must match: here, exactly 11 digits |
autocomplete="email" | The browser can fill it in: a huge usability win |
novalidate | On the form: turn off the browser's own pop-ups so you can show better ones |
How the API Works
Each field has field.validity, an object of yes-or-no answers such as valueMissing (empty but required), typeMismatch (not an email) and patternMismatch (does not match the pattern). validity.valid is true when everything passes. form.elements is every field in the form.
aria-invalid="true" tells a screen reader the field has a problem, and field.labels[0] is the label attached to it.
On a long form on a phone, an error eight fields above the screen is an error nobody sees. They press submit, nothing seems to happen, and they leave. firstBad.focus() is the difference between a form that gets finished and one that gets abandoned.
Error Messages That Help
| Instead of | Write |
|---|---|
| Invalid input | Enter 11 digits, with no spaces |
| Error | That email is already registered. Sign in instead? |
| Password too weak | Add one number. Passwords need 8 characters and a digit |
| Required field | We need your phone number to confirm the delivery |
Does the message tell them what to do next? "Invalid" describes your code's opinion. "Enter 11 digits" describes their next action.
Picture the kind bank clerk: the rules are printed on the form already, and the clerk's job is to point at the exact box and say what to write there.
2. Unplug Your Mouse
Definition
Keyboard accessibility means everything in your app can be reached and used with the keyboard alone, in a sensible order, while always showing where you are.
Real-Life Example
A lift with buttons only at the top of the wall, too high to reach from a wheelchair. It works perfectly for most people and not at all for some. A mouse-only interface is that lift.
| Key | Should do |
|---|---|
| Tab | Move to the next interactive thing, in a sensible order |
| Shift+Tab | Move back |
| Enter | Follow a link, press a button, submit a form |
| Space | Press a button, tick a checkbox |
| Escape | Close the dialog or dropdown |
| Arrow keys | Move within a group of options, a menu or a slider |
Open your Day 5 or Day 9 project. Put the mouse away, genuinely. Use the whole app with Tab, Shift+Tab, Enter, Space and the arrow keys. Write down every place you get stuck. Most people find three or four in five minutes.
Picture the lift with buttons out of reach. Every mouse-only control is a button someone cannot press.
3. The div That Should Have Been a Button
Definition
Every HTML element has a built-in role. A <button> can be focused with Tab, pressed with Enter or Space, is announced as a button by screen readers, and shows a focus ring. A <div> is just a box and does none of that, even if you attach a click listener to it.
Real-Life Example
A painted picture of a door on a wall. It looks like a door, and if you are standing right in front of it you might even push it, but it does not open, and a blind visitor feeling along the wall has no idea it is meant to be a door.
The first Tab went straight past the div to the button, and Enter pressed the button. For a keyboard user, the div does not exist.
Types: Choosing the Right Element
| If it | Use |
|---|---|
| Goes to another page or URL | <a href="..."> |
| Does something on this page | <button type="button"> |
| Submits the form | <button type="submit"> |
| Is a form control | <input>, <select> or <textarea>, with a <label> |
| Is just a box for layout | <div> |
Common Mistake: The Default Button Type Is Submit
A <button> inside a form with no type is a submit button. Put a Delete button inside a form without type="button" and clicking it submits the form.
Only the button with no type also submitted the form. Without our preventDefault, that submission would have reloaded the page. The fix is one attribute.
Picture the painted door: it may look right, but only a real door opens, for everyone. Use the real element and most of accessibility is done for you.
4. Focus, and Where It Goes
Definition
Focus is where the keyboard is pointing right now: document.activeElement. When a dialog opens, focus must move into it; when it closes, focus must go back to whatever opened it.
Real-Life Example
A waiter shows you to a side room to sign something, then walks you back to your own table. If they left you at the restaurant entrance instead, you would have to find your table all over again.
outline: none is in more stylesheets than almost any other rule, and it makes an interface impossible to use by keyboard: you cannot see where you are. If the default ring is ugly, replace it with a visible one that matches your design, using :focus-visible so it shows for keyboard users without appearing on every mouse click.
Picture the waiter walking you back to your own table. A keyboard user must always end up back where they were, and always see where they are.
5. Telling a Screen Reader What Changed
Definition
A live region is an element with role="status" (or aria-live). Screen readers read out anything that appears inside it, so a blind user hears about changes a sighted user simply sees.
Real-Life Example
An announcement over the speakers at a train station: "The 3:15 to Ibadan is now at platform 2." Passengers who cannot see the board still find out.
How It Works
- The element must already be on the page when it loads. Creating it and its message at the same moment announces nothing.
role="status"waits for a natural pause before speaking.role="alert"interrupts, so save it for errors: an interface that interrupts constantly is unusable.- Update its text, and the screen reader reads the new text.
aria-label gives the search box a name for screen readers when there is no visible label.
Picture the station announcement: the board changes for those who can see it, and the speakers say the same thing for those who cannot.
6. The Ten-Minute Audit
Run this on every project you ship from now on. It is not everything, and it catches most of what a beginner gets wrong.
- Tab through it. Can you reach everything, in a sensible order, and always see where you are?
- Every image: does the alt text say what it shows, or is it alt="" because it is only decoration? "image1.jpg" is worse than nothing.
- Every input: does clicking its label focus the field? If not, for and id do not match.
- Headings go h1, then h2, then h3, in order with no skipping. Screen reader users navigate by them.
- Colour contrast is at least 4.5 to 1 for body text. DevTools shows the ratio when you inspect a colour.
- Nothing is shown by colour alone. Add an icon or a word for the roughly one in twelve men who are colour blind.
- Zoom to 200%. Does anything overlap or disappear?
- Run Lighthouse: DevTools, Lighthouse tab, tick Accessibility. It is not the whole answer, and it finds real problems.
Do This Now
- Do the keyboard audit on your project and write down every problem.
- Fix them. Expect the list to include a div that should be a button, a missing label, and a removed focus ring.
- Rewrite every validation message so it says what to do next.
- Add a live region announcing your result count.
- Run Lighthouse and get Accessibility above 95. Then read what it still complains about, because the last few points teach you the most.
Checkpoint
Q1. Why is a clickable div worse than a button?
It cannot be reached with Tab, does not respond to Enter or Space, is not announced as a button, and has no focus ring. The button element does all four for free.
Q2. A Delete button inside a form submits the form. Why?
A button with no type is type="submit". Add type="button".
Q3. What is wrong with outline: none?
It hides the only sign of where keyboard focus is. Replace the ring rather than removing it, using :focus-visible.
Q4. Why must focus return when a dialog closes?
Otherwise it falls back to the top of the page and the keyboard user loses their place entirely.
Tick Before You Move On
- ☐ I used my whole app with no mouse and fixed everything I found
- ☐ Every interactive element is the correct HTML element
- ☐ Every button inside a form has a type
- ☐ Focus is visible everywhere and returns correctly after a dialog
- ☐ Every validation message says what to do next, and focus goes to the first problem
- ☐ Lighthouse Accessibility is above 95
Quick Recap
The browser validates for free; replace only its wording, not its checking. Focus the first invalid field, or the error is never seen. The right element gives you accessibility for free: button for actions, a for navigation. A button with no type submits its form. Never remove the focus ring: replace it, with :focus-visible. Live regions announce what a sighted user can simply see.
Final Scenario
A student using a screen reader signs up for your course, enrols, and searches the resource list, without once needing a mouse.
The form told them exactly what to fix and took them straight there. Every control was a real button. The dialog gave them back their place. And each search result count was read aloud. They never knew any of that was work; it simply worked.
Accessibility done well is invisible. That is the whole point of doing it.
Tomorrow, Day 14: debugging, testing and performance. Finding bugs instead of guessing at them, and the difference between code that works and code that is fast.