We implement Bootstrap’s Forms, with some tweaks to the visual appearance to add increased focus styling and a red asterisk for required fields. Also, ally.js is used to add an underline to labels when the corresponding input has focus. The documentation for Bootstrap Forms is applicable, along with the Accessibility Notes below.
Accessibility Notes
Instructions and other helpful information are necessary to avoid errors and help
users successfully complete an online form.
Custom Form Elements
We do not recommend using the custom Bootstrap form elements (or any custom form element) unless you are very
familiar with the ARIA authoring practices.
Form Control Attributes and Validation
Form control attributes, such as an input element’s type attribute, can be used to restrict the input value to help avoid errors. HTML5 input types can also help by giving hints to the browser about what type of keyboard to display on-screen. However, because there are so many usability and accessibility problems when using <input type="number">, we recommend using <input type="text" inputmode="numeric" pattern="[0-9]*"> instead.
Using the correct input type along with other input attributes (pattern, size, maxlength, required, etc.) utilizes HTML’s built-in validation features to help avoid input error: Read the MDN Web Docs on Form Data Validation
HTML Autocomplete Attributes
The autocomplete attributes are only for input fields collecting information
about the user. By using this autocomplete attribute, inputs can be auto-filled by the browser which makes it easier for everyone to fill out a form.
Add the appropriate autocomplete attribute (and its corresponding type attribute) to each form input field that requires user
information.
Clearly identifying required fields helps prevent user errors and it reduces the
chance that users will neglect to fill out all necessary fields in a form. The general requirements for
identifying required fields are:
The aria-required=”true” indicator or the HTML5 required attribute should be set on the input element.
A supplemental visible indicator should be available to sighted users (we use a red asterisk).
The example below demonstrates the use of the autocomplete and required attributes.
It also shows the style you can add for a required field, using class=”label-required” on the corresponding label
element.
<form><divclass="form-group"><labelfor="exampleInputEmail1"class="label-required">Email address</label><inputtype="email"requiredautocomplete="email"class="form-control"id="exampleInputEmail1"aria-describedby="emailHelp"placeholder="Enter email"><smallid="emailHelp"class="form-text text-muted">We'll never share your email with anyone else.</small></div><divclass="form-group"><labelfor="exampleInputPassword1"class="label-required">Password</label><inputtype="password"requiredautocomplete="current-password"class="form-control"id="exampleInputPassword1"placeholder="Password"></div></form>
Focus Order
Focus order should be logical. Screen reader and keyboard users use the tab key to
navigate the focusable/interactive elements on a page. The sequential order of those elements can help a
user understand the page.
Labels, Instructions, and Fieldsets
HTML Label Element
Using the HTML label element
explicitly is the preferred method when labeling form input fields. Example of using the HTML label
element explicitly:
Using the HTML label element makes getting into the associated input field easier for
users, as clicking on the label sends focus into that input field.
Input Field Instructions
A screen reader user may not hear non-focusable text inside a form if it is not
programmatically associated with an input. Using aria-describedby allows for the programmatic association of
the instructions to the input.
Fieldsets
Groups of related form elements should be grouped together in fieldsets with legends.
Example of using a fieldset and legend:
<fieldsetclass="form-group"><legend>Radio buttons in a fieldset with a legend</legend><divclass="form-check"><labelclass="form-check-label"><inputtype="radio"class="form-check-input"name="optionsRadios"id="optionsRadios1"value="option1"checked="">
Option one is this and that—be sure to include why it's great
</label></div><divclass="form-check"><labelclass="form-check-label"><inputtype="radio"class="form-check-input"name="optionsRadios"id="optionsRadios2"value="option2">
Option two can be something else and selecting it will deselect option one
</label></div><divclass="form-check disabled"><labelclass="form-check-label"><inputtype="radio"class="form-check-input"name="optionsRadios"id="optionsRadios3"value="option3"disabled="">
Option three is disabled
</label></div></fieldset>
Although it is possible to create a group label with <fieldset> and
<legend>, there is no easy way to create instructions that are associated with a group (adding
aria-describedby to either the <fieldset> or <legend> element doesn’t work). There are some ways
to do this:
Add the instructions to the legend if they are short. Some screen readers
will read the legend every time the user goes to a new form field in that group. If the legend is
very long, this can be annoying.
Associate the instructions with one of the fields (usually the first field is
best) within the group, using aria-describedby. This way, the group instructions are only read
once.
Put the instructions before the start of the whole form. This is not the
most ideal since the user may forget the instructions by the time they get to the group of fields they
need instructions for.
Validation Messages
Users need to know of any input errors or if their form submission was successful. We recommend using the constraint validation API to check the validity of the input values and creating custom error messages (do not use browser default error messages).
Options for implementing validation messages
Upon form submission, send user focus to a validation message as a summary.
The summary should include a count of errors so that users get a sense of the scope of the
problem. The summary should also include a link to the first field that has an error. Ensure the summary
container has a tabindex=”-1” and has an ARIA role=”alert”.
Upon form submission, send user focus to the first input field with an
error.
Inline (live) validation of error messages. We discourage using this
method since it is tricky to implement with timing the aria-live announcements of error messages.
Must haves for all error messages
Set aria-invalid=”true” on input fields with errors.
Associate error messages with form fields using aria-describedby so that screen
reader users know how to fix the problem
Ensure error messages are visible and adjacent to the inputs so that screen
magnification users can easily see which messages belong to which fields.
If the form submission causes a new page to load (or the same page to reload):
Update the page <title> to reflect the error or success confirmation
status. The title could say something like, “There were 2 errors in the form” or “Success! Your
application has been submitted.” The page <title> is the first thing screen reader users hear when
the page loads, so it is the fastest way to give them feedback on the form submission status.
Provide a quick way to reach the error or success message. For example, provide
a “skip to main content” link that takes the users to the message.
Below is an example of validating user input and setting the focus to the first field with an error. For more examples of how to validate user input, check out:
<formname="validationForm"><divrole="alert"id="successMessage"class="alert alert-dismissible alert-success"style="display:none;"><buttontype="button"class="close"data-dismiss="alert"aria-label="Close"><spanaria-hidden="true">×</span></button>
Thank you for your submission!</div><divclass="form-group"><labelclass="form-control-label label-required"for="email">Email Address</label><inputtype="email"requiredclass="form-control"name="email"aria-invalid="false"id="email"aria-describedby="emailFeedback"autocomplete="email"><divid="emailFeedback"class="invalid-feedback"></div></div><divclass="form-group"><labelclass="form-control-label label-required"for="username">Username</label><inputtype="text"requiredclass="form-control"name="username"aria-invalid="false"id="username"aria-describedby="usernameFeedback"autocomplete="username"><divid="usernameFeedback"class="invalid-feedback"></div></div><buttontype="button"class="btn btn-secondary"id="submitButton">Submit</button></form><script>document.getElementById("submitButton").addEventListener("click",function(){leterrors=falseletallErrors=[]letmessageDiv=document.getElementById('successMessage')letemailInputField=document.getElementById("email")letemailValid=emailInputField.checkValidity()letusernameInputField=document.getElementById("username")letusernameValid=usernameInputField.checkValidity()if(!emailValid){errors=trueemailInputField.setAttribute("class","form-control is-invalid")emailInputField.setAttribute('aria-invalid',true)letemailError=document.getElementById("emailFeedback")emailError.innerHTML='Error: Email must be filled out'allErrors.push(emailInputField)}else{emailInputField.setAttribute("class","form-control is-valid")emailInputField.setAttribute('aria-invalid',false)}if(!usernameValid){errors=trueusernameInputField.setAttribute('class',"form-control is-invalid")usernameInputField.setAttribute('aria-invalid',true)letusernameError=document.getElementById("usernameFeedback")usernameError.innerHTML='Error: Username must be filled out'allErrors.push(usernameInputField)}else{usernameInputField.setAttribute('class',"form-control is-valid")usernameInputField.setAttribute('aria-invalid',false)}if(errors){allErrors[0].focus()messageDiv.style.display='none'}else{messageDiv.style.display='block'emailInputField.setAttribute('class',"form-control is-valid")emailInputField.setAttribute('aria-invalid',false)usernameInputField.setAttribute('class',"form-control is-valid")usernameInputField.setAttribute('aria-invalid',false)}});</script>
Examples of Form Controls
<form><divclass="form-group"><labelfor="exampleSelect1">Example select</label><selectclass="form-control"id="exampleSelect1"><option>1</option><option>2</option><option>3</option><option>4</option></select></div><divclass="form-group"><labelfor="exampleSelect2">Example multiple select</label><selectmultiple=""class="form-control"id="exampleSelect2"><option>1</option><option>2</option><option>3</option><option>4</option><option>5</option></select></div><divclass="form-group"><labelfor="exampleTextarea">Example textarea</label><textareaclass="form-control"id="exampleTextarea"rows="3"></textarea></div><divclass="form-group"><labelfor="exampleInputFile">File input</label><inputtype="file"class="form-control-file"id="exampleInputFile"aria-describedby="fileHelp"><smallid="fileHelp"class="form-text text-muted">This is some placeholder block-level help text for the above input. It's a bit lighter and easily wraps to a new line. </small></div><divclass="form-group"><fieldset><labelclass="control-label"for="readOnlyInput">Readonly input</label><inputclass="form-control"id="readOnlyInput"type="text"placeholder="Readonly input here…"readonly=""></fieldset></div><divclass="form-group has-success"><labelclass="form-control-label"for="inputValid">Valid input</label><inputtype="text"value="correct value"class="form-control is-valid"id="inputValid"><divclass="valid-feedback">Success! You've done it.</div></div><divclass="form-group has-danger"><labelclass="form-control-label"for="inputInvalid">Invalid input</label><inputtype="text"value="wrong value"class="form-control is-invalid"id="inputInvalid"><divclass="invalid-feedback">Sorry, that username's taken. Try another?</div></div><divclass="form-group"><labelclass="col-form-label col-form-label-lg"for="inputLarge">Large input</label><inputclass="form-control form-control-lg"type="text"placeholder=".form-control-lg"id="inputLarge"></div><divclass="form-group"><labelclass="col-form-label"for="inputDefault">Default input</label><inputtype="text"class="form-control"placeholder="Default input"id="inputDefault"></div><divclass="form-group"><labelclass="col-form-label col-form-label-sm"for="inputSmall">Small input</label><inputclass="form-control form-control-sm"type="text"placeholder=".form-control-sm"id="inputSmall"></div></form>
Examples of Custom Forms
<form><divclass="custom-control custom-checkbox"><inputtype="checkbox"class="custom-control-input"id="customCheck1"><labelclass="custom-control-label"for="customCheck1">Check this custom checkbox</label></div><divclass="custom-control custom-checkbox"><inputtype="checkbox"class="custom-control-input"id="customCheckDisabled"disabled><labelclass="custom-control-label"for="customCheckDisabled">Check this disabled checkbox</label></div><fieldset><legend>Custom Radio Buttons</legend><divclass="custom-control custom-radio"><inputtype="radio"id="customRadio1"name="customRadio"class="custom-control-input"><labelclass="custom-control-label"for="customRadio1">Toggle this custom radio</label></div><divclass="custom-control custom-radio"><inputtype="radio"id="customRadio2"name="customRadio"class="custom-control-input"><labelclass="custom-control-label"for="customRadio2">Or toggle this other custom radio</label></div><divclass="custom-control custom-radio"><inputtype="radio"id="radio3"name="customRadio"id="customRadioDisabled"class="custom-control-input"disabled><labelclass="custom-control-label"for="customRadioDisabled">Disabled Radio Button</label></div></fieldset><fieldset><legend>Inline Custom Radio Buttons</legend><divclass="custom-control custom-radio custom-control-inline"><inputtype="radio"id="customRadioInline1"name="customRadioInline1"class="custom-control-input"><labelclass="custom-control-label"for="customRadioInline1">Toggle this custom radio</label></div><divclass="custom-control custom-radio custom-control-inline"><inputtype="radio"id="customRadioInline2"name="customRadioInline1"class="custom-control-input"><labelclass="custom-control-label"for="customRadioInline2">Or toggle this other custom
radio</label></div></fieldset><divclass="form-group"><labelfor="customSelect">Custom Select</label><selectid="customSelect"class="custom-select"><optionselected>Open this select menu</option><optionvalue="1">One</option><optionvalue="2">Two</option><optionvalue="3">Three</option></select></div><divclass="custom-file"><inputtype="file"class="custom-file-input"id="customFile"><labelclass="custom-file-label"for="customFile">Choose file</label></div><divclass="custom-control custom-switch"><inputtype="checkbox"class="custom-control-input"id="customSwitch1"><labelclass="custom-control-label"for="customSwitch1">Toggle this switch element</label></div><divclass="custom-control custom-switch"><inputtype="checkbox"class="custom-control-input"disabledid="customSwitch2"><labelclass="custom-control-label"for="customSwitch2">Disabled switch element</label></div><divclass="form-group"><labelfor="customRange1">Example range</label><inputtype="range"class="custom-range"id="customRange1"></div></form>