Labels, errors and keyboard support done right.
Forms are where most websites fail people using screen readers. You can have a gorgeous homepage, a lightning-fast product page and a thoughtful color palette, and still lock someone out entirely at the exact moment they try to sign up, book an appointment or pay a bill. I have tested enough client sites with a screen reader and a keyboard to know how often it happens: unlabeled fields read aloud as “edit text, blank,” error messages that appear in red somewhere at the top of the page and are never announced, and custom dropdowns that cannot be opened without a mouse.
So I built a small library that gets the basics right by default. It is not a big design system and it is not trying to be. It is a few hundred lines of HTML patterns, CSS and plain JavaScript that make an ordinary form usable by everyone, without the developer having to remember a long list of rules every time. This is the build log.
Why another form library
There are many form libraries already. Most of them focus on validation logic or on styling, and treat accessibility as an optional add-on, something you configure if you know to ask for it. In practice, the people building forms on busy client projects rarely have time to ask. The deadline is Friday, the fields work with a mouse, and the form ships.
My goal was the opposite: the easy path and the accessible path should be the same path. If you use the library at all, you get proper labels, connected hints, announced errors and full keyboard support. You would have to work to break them.

Real labels
Every input has a visible label. Placeholders are not labels. This is the rule I repeat most often, and the one I see broken most often. A placeholder is the faint gray text inside an empty field. It looks tidy in a design mockup, but it disappears the moment someone starts typing, it is often too low in contrast to read comfortably, and some assistive technology does not treat it as a name for the field at all.
In the library, the field component simply will not render without a label. The label sits above the input, is tied to it in the markup, and stays visible at all times. If a designer truly wants a visually compact form, there is an option to make the label smaller, but not to hide it. Optional hints, like “We will only use this to send your receipt,” are connected to the field so a screen reader reads them right after the label.
Grouping related fields
Radio buttons and checkboxes get the same care. A set of choices such as “Preferred contact method” is wrapped in a group with its own legend, so a screen reader user hears the question before each option, not just a lonely “Email, radio button, one of three.” Required fields are marked in text, not only with a red asterisk, because color and symbols alone are easy to miss.
Accessible by default beats accessible by request.
Leo
Helpful errors
Errors say what went wrong and how to fix it, right next to the field. That means no more “Invalid input” at the top of the page. If someone types a ZIP code with four digits, the message under that field reads “Enter a five-digit ZIP code, for example 97205.” The field is marked as invalid in the markup, the message is connected to it, and focus moves to the first problem when the form is submitted.
For longer forms there is also a summary at the top, listing every problem as a link that jumps to the right field. Screen reader users hear the summary announced when it appears. Sighted keyboard users can tab straight through it. Mouse users can click. The same information, offered three ways.
I also tuned when errors appear. Validating every keystroke is exhausting, especially for someone using voice input or a switch device. The library waits until a person leaves a field before checking it, and only re-checks as they type once an error is already showing, so the message disappears the moment they fix it.

Keyboard support done right
The keyboard rules are boring and strict, which is exactly how they should be. Here is what every component in the library guarantees:
- Every field and button can be reached with the Tab key, in the same order it appears on screen.
- Focus is always visible, with a thick outline that meets contrast guidelines on both light and dark backgrounds.
- Custom selects, date pickers and toggles respond to the arrow keys, Enter, Space and Escape the way a native control would.
- Nothing traps focus, except a modal dialog, which returns focus to the button that opened it when it closes.
- Submitting with Enter works from any text field, just like a native form.
Wherever possible, the library simply uses native HTML elements. A real select, a real checkbox, a real button. Native controls come with years of accessibility work built in, and they behave the way users already expect. I only build custom widgets when a native element genuinely cannot do the job, and those custom widgets are tested more than anything else in the project.
Testing with real people
Automated checkers catch maybe a third of the problems I care about. They will tell you a field has no label. They will not tell you that the label says “Field 3.” So alongside automated tests, I set up a small round of paid testing with five people who use assistive technology every day, recruited through a community group I volunteer with in Portland, Oregon, the Rose City Access Collective.
Their feedback changed several things. One tester, a retired paralegal named Diane Ruiz who uses a screen reader, pointed out that my “Show password” button announced itself the same way whether the password was visible or hidden. Another, a college student who navigates with a switch device, found that my date picker required far too many presses to move between months. Both are fixed now, and both were things no automated tool flagged.
Key takeaway: labels, errors and keyboard support are not extras. They are the minimum a form needs before it can honestly be called finished.
Open source and what is next
The library is open source, with a short readme, copy-and-paste examples, and a page of plain-language notes explaining why each rule exists. I have used it on three client projects so far: a dental office’s appointment request form, a volunteer signup for a food pantry in Tacoma, Washington, and a membership renewal flow for a small arts nonprofit. In each case, the forms were built faster than usual, because nobody had to argue about the basics.
Next on the list are better support for multi-step forms, clearer handling of file uploads, and a set of tests other developers can run against their own forms. If you build websites, I would love for you to try it, break it, and tell me what you find. Forms are often the most important page on a site. They deserve to work for everyone who lands there.
Leo builds fast, accessible web apps with boring, reliable tools.





Start the conversation