What does this project bring together?
The Forms lessons each fixed one part of a form. This project puts them on one page, the way they arrive in real work: a sign-up form that looks clean, passes a quick glance, and fails five different visitors. Someone who looks away cannot tell what the first field is for once they have typed in it. A screen reader user submits and hears nothing. A voice control user cannot say the name of the name field because it has no visible name. Someone with a tremor types an email address that the browser could have filled.
Fix it in the order the lessons ran: labels first, then required fields, then grouping, then autocomplete, then errors. Each layer depends on the one before. An error message can only be connected to a field that has a name, and a required state only makes sense on a field people can identify. The task check passes when every piece is in place and the **Checks** list is empty.


Why does the placeholder fail as a label?
The starting name field has placeholder="Full name *" and no <label>. The Checks flag it as named only by its placeholder. The text disappears as soon as someone types, so a person who looks away, or who has memory difficulties, comes back to a box of their own typing and no question. Placeholder grey is often below 4.5:1 contrast. Many voice control users say the visible label to move to a field, and here there is nothing persistent to say.
The fix is a <label for="name">Full name</label> above the input and no placeholder. If you want an example of the format, put it as normal text next to the label or link it with aria-describedby. Check the **Screen reader** view: with required added too, the field reads “Full name, edit text, required”.
<label for="name">Full name <span class="star" aria-hidden="true">*</span></label>
<input type="text" id="name" name="name" autocomplete="name" required>How do you make the red star mean something to everyone?
A red asterisk relies on colour and a symbol, and screen readers read it as “star” or skip it, depending on settings. The required attribute puts the state in the accessibility tree, so the field is read as “required” in every screen reader, and the browser can block an empty submit when native validation is on. Pair it with a line of visible text at the top of the form saying what the star means, so sighted visitors know too.
Hiding the star itself with aria-hidden="true" stops screen readers from saying “Full name star, edit text, required”, which repeats the same idea twice. This form uses novalidate on the <form>, because the script shows its own error message; required still sets the state for assistive technology when native validation is turned off.
Why does the membership choice need a fieldset?
Adult, Junior and Family are three answers to one question. In the starting page the question is a paragraph styled in bold, and the Checks flag the radio set as ungrouped. Wrap the three labels in a <fieldset> and make “Membership type” its <legend>. The **Screen reader** view now shows “grouping, Membership type” before the choices, and the **Keyboard** view shows the group as a single Tab stop, with the arrow keys moving between options.
The newsletter checkbox stays outside. It is a single yes or no with its whole meaning in its label, so it needs no group.
What does autocomplete add to a sign-up form?
The name field already had autocomplete="name". The email field had nothing. A sign-up form is exactly where saved details save the most effort, since the person is new and has to type everything. Add autocomplete="email" and the browser offers the saved address as soon as the field gets focus. Its type is already email, which gives phone users the right keyboard. If the club later adds a password, use autocomplete="new-password" so password managers offer to create a strong one.
How do you make the error message heard?
Submit the empty form in the **Page** view. The script writes “Enter your email address, like [email protected].” under the field, sets aria-invalid="true" and moves focus to the email input. Sighted users see red text appear. A screen reader user hears the field’s name and “invalid entry”, and no reason. Two things are missing.
First, the message must be connected to the field. Add aria-describedby="email-error" to the input. The screen reader now reads the message as the field’s description whenever focus lands on it, and the Checks stop flagging an unlinked error. Second, the message should be announced when it appears. Give the error paragraph role="alert". An alert is a live region that interrupts what is being said, which suits an error the person must fix before going on.
The live region must already be on the page, empty, before the script writes into it. Screen readers watch regions that exist when the page loads; a region created at the same moment as its text is often missed. The thank-you paragraph gets role="status", which waits politely until the screen reader finishes speaking.
<input type="email" id="email" name="email" autocomplete="email" required aria-describedby="email-error">
<p id="email-error" class="error" role="alert"></p>
<p id="done" role="status"></p>Should focus move to the field with the error?
For a single error, yes, and this script already does it with email.focus(). The keyboard user lands where the fix is needed, and the screen reader reads the field with its description. With an alert also firing, some screen readers will speak the message twice, once as an alert and once as the description. That repetition is a small cost for a message nobody can miss.
For a long form with several errors, move focus to an error summary at the top instead, with a link to each field, as the error messages lesson showed. Moving focus to the first invalid field and announcing nothing else leaves people unaware that three more fields need attention.
How should you test the finished form?
Start with the **Checks** view and clear it. Then test like a visitor. In the **Keyboard** view, press Tab from the top: Full name, Email address, the membership group as one stop, the newsletter checkbox, then the button. Every stop should show a clear focus outline.
Press Enter on the button with the form empty. Focus should land on the email field, and the **Screen reader** view should read it with “invalid entry” and the message. Type an address and submit again: the error clears and the thank-you appears. With a real screen reader, listen for the alert as the error appears, and for the status message after a good submit. If you test in a browser with saved contact details, focus the email field and pick the saved address.
What do AI-written sign-up forms usually miss?
The common pattern is a stylish form with placeholders and no labels, a red star with no required, radio buttons in a <div>, and an error shown by adding a red border or a toast that vanishes after three seconds. Generated scripts often create the error element at the moment of the error with document.createElement, which many screen readers miss, and set aria-invalid without aria-describedby.
Review generated forms against this lesson’s list: a visible label per field, required where there is a star, a fieldset per question, autocomplete on personal details, and an error region that exists before it is filled and is tied to its field.
- Every field has a visible
<label> - Starred fields have
required - Radio groups have a
<fieldset>and<legend> - Personal fields have
autocompletetokens - Errors use
aria-describedbyand a live region present from the start
Field notes
- A placeholder is no substitute for a visible
<label>. - Pair a red star with
requiredso its meaning reaches everyone. - Three choices that answer one question belong in a
<fieldset>with a<legend>. - An error message must be tied to its field and announced when it appears.
Easy to get wrong
A placeholder vanishes when people type and often fails contrast. A label stays visible, names the field for screen readers and gives voice users words to say.
Marking a field invalid tells people something is wrong, not what. The message has to be connected with aria-describedby, and a live region makes it heard when it appears.
Many screen readers only watch live regions that already exist. A new role="alert" is often announced anyway, but an empty region already in the HTML works in more places, so put it there and change its text when the error happens.
Questions people ask
Why use novalidate if the fields are required?
This form shows its own error message, which is easier to word clearly and style. novalidate stops the browser’s own popup, and required still tells screen readers the field must be filled.
Is role="alert" too loud for form errors?
For an error that stops the form, an interruption is fair. For a passing message, like the thank-you, use role="status", which waits its turn.
Should the newsletter checkbox be ticked by default?
No. A pre-ticked box is easy to miss for screen reader and low vision users, and in many countries consent to email must be an active choice.
Where should the error message go on the page?
Right after the field, or between the label and the field, so it is close for people using zoom. Its place on screen makes less difference to screen readers once aria-describedby connects it.