LogoLogo
  • FAC Curriculum
  • archive
    • Node & npm introduction
    • developer
      • Programming Paradigms
    • handbook
      • Asking for help
      • Coaching groups
      • Code review
      • Course rules
      • Debugging
      • Employment schedule and material delivered in FAC20
      • GitHub Workflow
      • Glossary or terms
      • Presentation Guidance
      • Equality, Diversity and Inclusion
      • Installation guide
      • Learning circles
      • Mentoring guidance
      • What to expect from your mentors
      • One-day projects
      • Pair programming
      • Portfolio
      • Questions for problem solving
      • Progress Logs
      • Final project report
      • Managing software projects
      • Project Presentations
      • Project roles
      • Projects
      • Retrospectives
      • Role Circles
      • Safeguarding policy
      • Technical Spikes
      • System requirements
      • Tech for Better
      • User Manuals
      • Wellbeing Support
      • project-docs
        • What makes a mentor?
        • Product Handover
        • Sprint Planning
        • Tech for Better Presentations
        • User Research & Usability Testing
    • foundation
      • full-stack
        • Learning Outcomes
        • project
      • testing
        • project
        • spikes
  • docs
    • Contributing to the curriculum
    • Curriculum intent
    • Curriculum process
  • src
    • About our curriculum
    • course
      • Code of Conduct
      • Docker
      • .NET and Umbraco
      • Getting started
      • Founders and Coders coursebook
      • KSB's
      • Mini projects
      • Revision checklist
      • Svelte
      • TypeScript
      • handbook
        • Software Developer Handbook
        • Software Foundation Handbook
      • precourse
        • Before you start the course
        • Installation List
      • syllabus
        • developer
          • app
            • learning-outcomes
            • project
            • schedule
            • spikes
          • introduction
            • learning-outcomes
            • project
            • resources
            • schedule
          • week00-pre-course
            • We'd like you to spend some time before starting the course working on useful fundamentals.
            • spikes
          • week01-project01-basics
            • Employability introduction
            • Homework
            • learning-outcomes
            • Week of September 9th
            • project
            • resources
            • schedule
            • spikes
          • week02-project02-chatbot
            • employability
            • Homework
            • learning-outcomes
            • Week of September 16th
            • project
            • resources
            • schedule
            • spikes
          • week03-project03-server
            • Learning Outcomes
            • Week of September 23th
            • The Amazin' Quizzer API Backend
            • resources
            • schedule
          • week04-project03-frontend
            • learning-outcomes
            • Week of September 30th
            • UI for Quizzer App
            • resources
            • schedule
          • week05-project03-test-deploy
            • Testing and deployment
            • Week of October 7th
            • project
            • resources
            • schedule
          • week06-project04-databases
            • learning-outcomes
            • Week of October 14th
            • project
            • Databases
            • schedule
          • week07-project04-authentication
            • Learning Outcomes
            • Week of October 21st
            • project
            • resources
            • schedule
          • week08-project04-test-deploy
            • Learning Outcomes
            • Week of October 28th
            • project
            • resources
            • schedule
          • week09-reading-week
            • Learning Outcomes
            • overview
            • Project
            • Resources
            • schedule
          • week10-project05-DOTNET-intro
            • Learning Outcomes
            • overview
            • project
            • Resources
            • schedule
          • week11-project05-DOTNET-testing
            • Testing and deployment
            • Week of November 18th
            • project
            • Resources
            • schedule
          • week12-project05-DOTNET-deploy
            • Learning Outcomes
            • Week of November 25th
            • project
            • Resources
            • schedule
            • Spikes
          • week13-TFB-design
            • Learning Outcomes
            • overview
            • Project
            • Resources
            • schedule
            • Design Week Spikes
          • week14-TFB-build
            • Learning Outcomes
            • overview
            • Project
            • DevOps Resources
            • schedule
            • Spikes
          • week15-TFB-build
            • Learning Outcomes
            • overview
            • Project
            • Resources
            • schedule
            • Spikes
          • projects
            • in-house-design
              • Learning Outcomes
              • Project
              • Resources
              • schedule
              • Design Week Spikes
        • foundation
          • Obsolete-full-stack
            • project
          • post-course
            • Homework
            • schedule
        • portfolio
          • fruit-shop
            • learning-outcomes
            • project
            • resources
          • game
            • learning-outcomes
            • project
            • resources
          • hobby-page
            • learning-outcomes
            • project
            • resources
          • movie-data
            • learning-outcomes
            • project
            • resources
          • project-gallery
            • learning-outcomes
            • project
            • resources
          • website
            • learning-outcomes
            • project
            • JavaScript
        • tfb
          • week 1
            • Introduction (45 minutes)
            • Further reading
          • week 10
            • content
            • resources
          • week 11
            • What will we be doing this week?
            • resources
          • week 12
            • What will we be doing this week?
            • Further reading
          • week 2
            • Discover (90 minutes)
            • resources
          • week 3
            • content
            • resources
          • week 4
            • Mapping the user journey (90 minutes)
            • resources
          • week 5
            • Figma Workshop 1 (90 minutes)
            • Further reading
          • week 6
            • Figma Workshop 2 (90 minutes)
            • resources
          • week 7
            • Product pitches & Selection (90 minutes)
            • resources
          • week 8
            • content
            • resources
          • week 9
            • content
            • resources
    • learn
      • DOTNET
        • Introduction to .NET
      • auth
        • Authenticating web apps
      • database
        • Persisting data with SQLite and Node
      • dotnet-two
        • Dependency injections and interfaces in .NET
      • form-validation
        • Form validation
      • react
        • Building client-side apps with React
      • server
        • HTTP servers with Node & Express
      • typescript
        • TypeScript
    • mentoring
      • design-week
        • Analysis Workshop
        • Code planning
        • Definition Workshop
        • Discovery Workshop
        • Figma introduction
        • Usability testing
        • User Research
    • resources
      • http
        • introduction
    • workshops
      • cookie-auth
        • index
      • creating-promises
        • index
      • css-layout
        • index
      • cypress-testing
        • index
      • database-testing
        • index
      • dev-tooling
        • Developer tooling
      • dom-challenge
        • index
      • dom-rendering
        • index
      • es-modules
        • index
      • express-middleware
        • Express middleware
      • first-class-functions
        • index
      • form-validation
        • index
      • functions-callbacks-async
        • Functions, callbacks, & async JavaScript
      • git-intro
        • Introduction to Git
      • git-terminal
        • Using Git in the terminal
      • git-workflow
        • Git workflow
      • github-projects
        • GitHub Projects Workflow Workshop
      • heroku-sql-challenge
        • index
      • html-forms
        • index
      • learn-a11y
        • index
        • starter-files
          • solution
            • Accessibility solution explanation
      • learn-fetch
        • index
      • learn-integration-testing
        • index
      • learn-testing
        • Learn testing in JavaScript
      • learn-unit-testing
        • index
      • node-error-handling
        • Node error-handling
      • node-express-server
        • Node and Express HTTP server
      • node-npm-intro
        • Node & npm introduction
      • node-postgres
        • Learn Postgres with Node
      • node-scripting-challenge
        • index
      • password-security
        • index
      • promise-practice
        • index
      • react-components
        • React components
      • react-fetch
        • index
      • react-forms
        • React forms
      • react-refactor-classes
        • index
      • react-state-effects
        • React state & effects
      • real-world-fetch
        • index
      • scope-challenge
        • Scope debugging challenge
      • semantic-html
        • index
      • server-side-forms
        • Server-side forms
      • session-auth
        • Session authentication
      • sql-intro
        • index
      • tdd-array-methods
        • index
Powered by GitBook
On this page
  • Communicating requirements
  • HTML5 validation
  • Enhancing with JavaScript
  • Even more enhancement
  • Styling
Export as PDF
  1. src
  2. learn
  3. form-validation

Form validation

Learn how to validate user input in the browser and present error messages accessibly.

Previousform-validationNextreact

Last updated 2 years ago

Client-side validation is important for a good user experience—you can quickly give the user feedback when they need to change a value they've entered. For example if passwords must be a certain length you can tell them immediately, rather than waiting for the form to submit to the server and receive an invalid response.

Communicating requirements

It's important to tell the user what you expect them to do. As always you need to present information visually and programmatically, so user's of assistive technologies like screen readers can access it. At a bare minimum this means each form field need an associated label.

<!-- `for` attribute associates label with input by ID -->
<label for="name">What is your name?</label>
<input id="name" />

If the field will be validated you also need to communicate those requirements to the user ahead of time. There's nothing more frustrating than having a submission rejected for an unknown reason.

Required fields

If the user must provide a value the common convention is to put a * character after the label. You could also use the word "required".

It's important not to duplicate information for assistive technology users. For example if your field is already programmatically marked as required (e.g. via the required attribute), then hearing the * character read out is at best superfluous and at worst confusing. It's a good idea to hide this symbol from non-sighted users in this case:

<label for="name">
  What is your name?
  <span aria-hidden="true">*</span>
</label>
<input id="name" required />

More specific instructions

A field can have stricter validation than just "required"—for example a new password field might check the length and complexity of the value. In these cases you will need to provide the requirements after the label. To make sure this is available to assistive tech users you must also associate the element with the field. You can do this using the attribute on the field—this should be set to the ID of the element containing the instructions:

<label for="password">New password</label>
<p id="passwordHelp">Your password must be at least 10 characters long</p>
<input id="password" aria-describedby="passwordHelp" />

This description will be available to assistive tech users when they focus the input; screen readers will usually read it out after the label.

HTML5 validation

Now we've communicated our requirements to the user we need to actually enforce them. We need a way to check the values the user entered match our expectations, and prevent the form from submitting if they don't. Luckily browsers natively support lots of different types of validation via different HTML attributes.

If a form containing invalid values is submitted the browser will prevent the request from being sent, and instead will show a message for each invalid field telling the user what they did wrong.

Requiring values

The required attribute will stop the user submitting the form if they haven't entered this value yet.

<input required />

Types of values

Browsers will validate certain input types to make sure the value looks correct. For example:

<!-- checks the value is an email string -->
<input type="email" required />
<!-- checks the value is a URL string -->
<input type="url" required />

Some browsers (especially on smartphones) will even change their input method to match. For example the keyboard may show the @ key for an "email" input.

Matching a pattern

We can specify a regular expression the value must match using the pattern attribute. For example this input will be invalid if it contains whitespace characters:

<input type="text" pattern="\S" />

Other validation


Enhancing with JavaScript

It's great that we can get a base level of client-side validation working with just HTML—if our JS fails to load (or breaks) the user gets basic validation. This makes it quick and simple to provide a helpful experience to users. However it has a few downsides:

  • We cannot style the message bubbles that the browser shows for invalid fields.

  • Required inputs are marked invalid as soon as the page loads (since they are empty).

We can improve this user experience by enhancing our validation using JavaScript.

Disabling default form behaviour

First we need to tell our form not to do its own validation, since we're going to trigger this ourselves using JS. We can do this by setting the novalidate attribute:

const form = document.querySelector("form");
form.setAttribute("novalidate", "");

Trigger validation from JS

To recreate the default behaviour we need to listen for the form's submit event, then prevent submission if there are any invalid fields. We can check all fields using the form element's .checkValidity() method. This returns true if all fields are valid, otherwise it returns false.

form.addEventListener("submit", (event) => {
  const allValid = form.checkValidity();
  if (!allValid) {
    event.preventDefault();
  }
});

Marking invalid fields

Our "enhancement" is currently worse than the default, since it prevents submission without telling the user which fields are invalid. We need to provide feedback to the user so they can fix their mistakes.

First we need to tell the browser/assistive tech whether the field is valid or not. We can use the aria-invalid attribute for this. Each field should have aria-invalid="false" set at first, since it can't be invalid until we check it.

const fields = form.querySelectorAll("input"); // you probably want to include <select>, <textarea> etc too
fields.forEach((field) => {
  field.setAttribute("aria-invalid", "false");
});

Now we need to know when the field fails validation, so we can update this attribute to "true". Luckily calling checkValidity() causes invalid fields to fire an "invalid" event that we can listen for:

fields.forEach((field) => {
  // ...
  field.addEventListener("invalid", () => {
    field.setAttribute("aria-invalid", "true");
  });
});

Providing feedback

<p id="passwordHelp">Your password must be at least 10 characters long</p>
<input id="password" aria-describedby="passwordHelp passwordError" />
<p id="passwordError"></p>
fields.forEach((field) => {
  // ...
  const feedback = document.createElement("p");
  const id = field.id + "Error";
  feedback.setAttribute("id", id);

  // don't overwrite any existing aria-describedby
  const prevIds = field.getAttribute("aria-describedBy");
  const describedBy = prevIds ? prevIds + " " + id : id;
  field.setAttribute("aria-describedBy", describedBy);

  field.after(feedback);
  // ...
});
fields.forEach((field) => {
  // ...
  field.addEventListener("invalid", () => {
    // ...
    const message = field.validationMessage;
    feedback.textContent = message;
  });
});

We have now replaced the default HTML experience, with all the problems we listed fixed.


Even more enhancement

Since we're validating using JS we can add more features if they make sense. Right now our form only validates on submission. This means users will not get feedback as they fill in the form, and fields will not get re-validated until the user submits again.

Re-validation

It would be nice to clear the invalid state when the user edits a field. We can do this by listening for the "input" event on the field and reversing the steps from before:

fields.forEach((field) => {
  // ...
  field.addEventListener("input", () => {
    field.setAttribute("aria-invalid", "false");
    feedback.textContent = "";
  });
});

Validating more often

On longer forms it might be helpful for the user to see validation as they fill in fields, rather than waiting until they submit at the end. There is a balance here though—many apps validate on every key press, which often leads to fields being marked as invalid while the user is halfway through typing a valid value.

fields.forEach((field) => {
  // ...
  field.addEventListener("blur", () => {
    field.checkValidity();
  });
});

Styling

We have a functional, accessible solution now, but it could be improved with some styling. It's common to style validation messages with a "danger" colour like red. Relying on colour alone will not work for all users, so you should also mark invalid inputs with a visual change like a different coloured border or an icon.

[aria-invalid="true"] {
  border-color: red;
}

/*
 * attr$="value" matches the _end_ of the attribute.
 * e.g. this matches id="passwordError"
 * but doesn't match id="passwordHelp".
 * You could also just add a className ¯\_(ツ)_/¯
 */
[aria-invalid="true"] + [id$="Error"] {
  color: red;
}

[aria-invalid="true"] + [id$="Error"]::before {
  content: "⚠️ ";
}

There are several other , which work for different kinds of inputs.

The messages are .

Marking fields as invalid isn't enough. We also need to provide the validation message that the browser previously showed. First we need to add this element to the DOM every field, and associate them using aria-describedby again. We want the DOM to end up like this before any validation runs:

Then when a field is invalid we need to grab the default message from the field's property and display it:

It's usually less annoying to validate when the user's focus leaves the field. We can do this by listening for the "blur" event, then triggering the validation using the field's method:

You can target elements in CSS , which is helpful for targetting invalid inputs:

aria-describedby
validation attributes
not properly exposed to most screen readers
after
validationMessage
checkValidity()
using their attributes