Skip to content

Forms Project

DodaTech 3 min read

title: "Forms Project — Build a Complete Accessible Registration Form" description: "Build a production-ready accessible registration form applying all form accessibility patterns including labels, errors, focus management, and live regions." weight: 15 date: 2026-06-28 lastmod: 2026-06-28 tags: [accessibility, forms]


Apply everything you learned by building a complete accessible multi-step registration form with validation, error handling, and success confirmation.

## What You'll Learn

How to combine labels, ARIA, validation, focus management, and live regions into a single production-ready form.

## Why It Matters

A complete registration form is the most common form type on the web. Mastering it ensures users can sign up for your service without barriers.

## Real-World Use

A user registers for a DodaTech account. The form asks for email, password, and profile details across two steps, with inline validation, clear error messages, and a success confirmation that the screen reader announces.

## The Complete Form

```html
<form id="registration" novalidate aria-label="User registration">
  <div role="alert" id="form-errors" aria-live="assertive"></div>

  <fieldset>
    <legend>Account credentials</legend>

    <label for="reg-email">Email address</label>
    <input type="email" id="reg-email" name="email" required
           autocomplete="email"
           aria-describedby="reg-email-hint reg-email-error"
           aria-invalid="false">
    <p id="reg-email-hint">We will send a confirmation email</p>
    <p id="reg-email-error" role="alert"></p>

    <label for="reg-password">Password</label>
    <input type="password" id="reg-password" name="password" required
           minlength="8" autocomplete="new-password"
           aria-describedby="reg-password-rules reg-password-error"
           aria-invalid="false">
    <ul id="reg-password-rules">
      <li>At least 8 characters</li>
      <li>One uppercase letter</li>
      <li>One number</li>
    </ul>
    <p id="reg-password-error" role="alert"></p>
  </fieldset>

  <button type="submit">Create account</button>
</form>

<div id="success-message" role="status" aria-live="polite" hidden>
  <h2 tabindex="-1">Account created successfully</h2>
  <p>Check your email to verify your account.</p>
</div>

JavaScript Validation

document.getElementById('registration').addEventListener('submit', async function(e) {
  e.preventDefault();
  let hasError = false;
  const fields = this.querySelectorAll('[aria-invalid]');

  fields.forEach(field => {
    const errorEl = document.getElementById(field.getAttribute('aria-describedby').split(' ').pop());
    if (!field.validity.valid) {
      field.setAttribute('aria-invalid', 'true');
      errorEl.textContent = field.validationMessage;
      hasError = true;
    } else {
      field.setAttribute('aria-invalid', 'false');
      errorEl.textContent = '';
    }
  });

  if (hasError) {
    const firstError = this.querySelector('[aria-invalid="true"]');
    firstError.focus();
    return;
  }

  const form = this;
  form.hidden = true;
  const success = document.getElementById('success-message');
  success.hidden = false;
  success.querySelector('h2').focus();
});

Common Mistakes

1. Skipping form-level error summary

Always provide a summary at the top for screen readers to get an overview of all errors.

2. No validation on required fields before submission

Client-side validation prevents unnecessary server round-trips but must not bypass server validation.

3. Forgetting autocomplete on all fields

Every field that collects personal data needs an appropriate autocomplete attribute.

4. Not hiding the form after success

Leaving the form visible after submission risks double-submission and confusion.

5. No server-side validation fallback

Client-side validation is for UX, not security. Always validate on the server.

Practice Questions

1. What should happen after successful form submission? The form should hide, a success message should appear, and focus should move to the success message heading.

2. Why is novalidate used on the form element? novalidate disables browser-native validation so we can provide accessible custom error messages.

3. What is the purpose of the form-level error summary div? It provides an overview of all errors for screen reader users and sighted users scanning the form.

Challenge: Extend the registration form to include a multi-step profile section (name, address, preferences) with proper focus management between steps.

FAQ

Should I use novalidate on every form?

Use novalidate when implementing custom accessible validation. Without it, the browser shows its own error tooltips which are not always accessible.

How do I handle server-side validation errors?

Return errors from the server, associate them with fields using aria-describedby, and move focus to the first server-side error field.

Should the form be reset after success?

Yes, resetting the form prevents accidental double-submission. Use form.reset() after successful submission.

What if the form submission takes a long time?

Show a progress indicator with role=progressbar and aria-valuenow, aria-valuemin, aria-valuemax attributes.

How do I test the complete form?

Use the Form Tester lesson: automated tools, keyboard-only, and screen reader testing for the complete workflow.

Mini Project

Build the complete registration form and deploy it. Then create a second form of your choice (contact form, checkout form, survey form) applying all the patterns from this tutorial series.

What's Next

Now explore Accessible Navigation to learn how to build navigation systems that all users can operate.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro