Got an Angular form? Cool, how about Accessibility?

September 20, 2024 | 16 Minute Read

How do people without arms, people who can’t see well, people who can’t hear, or who have other disabilities, use your application? Making forms accessible in Angular is crucial, not just because it's the right thing to do, but also because it can greatly enhance the user experience for everyone, regardless of their abilities. By doing so, we can ensure that our application is usable by as many people as possible, which can ultimately lead to a better user base and even more business opportunities. In this tutorial we’ll cover several things we can do to greatly improve the accessibility of a basic Angular form.

The Existing, Partially Accessible Form

For this example, we’ll be working with the form that we created in a previous tutorial about Reactive form controls in Angular. If you haven’t seen that tutorial, you’ll probably want to read it first because this tutorial might not make as much sense if you don’t.

If you’d rather watch a video, check it out below:

When it comes to Angular forms, there are several common accessibility challenges that we need to be aware of.

Including Proper Form Labels

One of the most critical issues is missing or incorrect label tags, which can make it impossible for screen readers to properly announce the form fields to users who rely on them.

Here, in our form component, we do actually already have this handled correctly. Mostly anyway…

form.component.html

<label>
    <strong>Enter Your Name</strong>
    <input type="text" [formControl]="name" />
</label>

Our input is placed within the label element which will properly associate the “Enter Your Name” text description with the input.

Keyboard Navigation and Focus Management

Another challenge is the lack of keyboard navigation which can prevent users, who can’t use a mouse, from interacting with our form.

Now, this type of thing normally happens when we create custom controls where we’re not using native web form controls like inputs and buttons.

This can be common practice in Angular apps, but this example is a simple form and we are using a standard text input field and a button. So this shouldn’t be an issue for us. Both of them will automatically receive focus when tabbing through the form.

So, this form is not horrible accessibility-wise, but there are several things we can do to make it more accessible. Let’s explore some practical tips and techniques for improving this form.

Enhancing Accessibility for the Angular Form

First and foremost, we need to ensure that our form fields have proper label tags, which should be programmatically associated with their respective fields.

Now, as we already learned, we’re already good here with the use of the label element. But we are actually missing some information.

The name field is required, yet a person using assistive technology to navigate our form would have no idea that they must enter this information before moving on.

Example of a basic sign-up form

We need to make them aware.

Adding the Appropriate Required Information

There are few things that we should do for this. First, we should add the HTML required attribute to the form field:

form.component.html

<input type="text" [formControl]="name" required />

Next, we should add some additional information to the label to help describe the fact that it’s required.

Let’s add a span, and let’s add the word “required” within it:

form.component.html

<label>
    <strong>
        Enter Your Name 
        <span>(Required)</span>
    </strong>
</label>

So now, a screen reader will announce the label, and it will include the word “required” which helps convey its required status to the user.

Example of additional required text added to a form label

Using CSS to Visually Hide Text Accessibly

Often times, the design for our forms may require us to hide this additional “required” label and mark this in some sort of visual manner instead.

To hide this text in an accessible way, we need to be careful in how we do it. Using CSS like display: none or opacity: 0 can actually prevent this text from being read by screen readers. Luckily, we do have a recommended set of styles to use for this “visibility hidden” concept.

Using the CSS clip property, along with position: absolute, and several other styles, we have a widely accepted pattern to hide things without harming their accessibility.

form.component.scss

.visually-hidden {
    border: 0;
    clip: rect(0 0 0 0);
    height: 1px;
    margin: -1px;
    overflow: hidden;
    padding: 0;
    position: absolute;
    width: 1px;
    white-space: nowrap;
    outline: 0;
    left: 0;
}

So, we can add this “visually-hidden” class to our span:

form.component.html

<label>
    <strong>
        Enter Your Name 
        <span class="visually-hidden">(Required)</span>
    </strong>
</label>

Now, we should see the required text hidden, but it will still be processed correctly by assistive technologies:

Example of additional required text in a form label accessibly visually hidden with CSS

Adding Visual Representation of Required Status with CSS

We still want to mark this field as required visually so that it’s clear for sighted users as well.

In the CSS, for our strong element, we’ll add styles to an ::after pseudo element to create a little red dot after the label text.

form.component.scss

strong::after {
    content: '';
    height: 0.5em;
    width: 0.5em;
    background-color: red;
    border-radius: 50%;
    display: inline-block;
    vertical-align: text-top;
}

There, now after we save, we should have a visual representation of the required status of this field:

Example of visual representation for a required field with a form label that has an accessibly visually hidden reguired label

This just makes it more accessible for everyone, even sighted users.

Enhancing the Visual Feedback for Focus States

Speaking of the visibility of things, when we tab to our textbox and then to our button, we do get an outline on these controls:

Example of the default outline styles for focus states of an input and a button

It’s not horrible, but it can easily be made a little more clear and made to work more consistently across different browsers.

So, let’s add some styles for our input and button focus states. Let’s add a consistent outline, let’s use “skyblue” for the color:

form.component.scss

input, 
button {

    &:focus {
        outline: solid 0.25em skyblue;
    }

}

This will be similar to what we just saw before the change, just a different color. But this way it will be more consistent in Safari, Firefox, you name it.

Now to make it stand out a little more, let’s offset it from the control a little bit:

form.component.scss

input, 
button {

    &:focus {
        outline: solid 0.25em skyblue;
        outline-offset: 0.25em;
    }

}

Ok, now when we tab between the field and the button, the outline should stand out a little more:

Example of custom outline styles for focus states of an input and a button

Not a huge difference but should be pretty apparent now.

This just helps those with vision impairments understand where they are focused easier. And realistically it helps the rest of us too.

Setting the Submit Button as the Default Button for the Form

Now, to make this form easier to use, we should make the submit button the default button for the form.

What I mean by this is, when we’re focused within the form, if we hit the enter key, the submit action should automatically fire.

The easiest way to do this is to wrap the form contents in an HTML form element:

form.component.html

<form>
    <label>
        ...
        <input type="text" [formControl]="name" required />
    </label>
    <button>
        Continue
    </button>
</form>

And that’s it. We shouldn’t need to do anything else which may seem odd because we’re using the “click” event on our button to submit our form:

form.component.html

<button (click)="submitted = name.valid;" ...>
    Continue
</button>

The “click” event actually fires on activation. So, with this being our default button now, when we click, or use the enter key, or even the spacebar, the button will be activated.

Ok, now we should be able to focus the field, add a value, then hit enter, and it should submit the form without needing to click the button:

Example of a button set as the default button using the HTML form element

Enhancing the Accessibility of the Inline Validation for the Form Control

Ok, at this point, we’ve added some great enhancements, and this form is sure to be easier to use. But one area that we haven’t yet focused on is the validation aspect.

How do we let the user know that they’ve made a mistake, or missed something?

Well, you may have seen the “Your name is required!” error message display as we’ve been working with this form.

Example of an inline error message

This is great for users who can see it, but for those that can’t, they won’t know it exists. But we can actually associate this message with the form field programmatically.

For this we’ll add an aria-describedBy attribute to our input element. This attribute requires a list of ids for the elements that describe it.

We can think of our error message as something that helps describe this field. So, we need to add an id to this element, let’s call it “nameError”:

form.component.html

<div id="nameError" ...>
    Your name is required!
</div>

Now we can pass this id to the aria-describedBy attribute:

form.component.html

<input aria-describedby="nameError" ... />

There, now this message will be announced by screen readers.

Enhancing the Overall Angular Form Validation

Next, we need to handle how we react when the submit button is activated while the form is invalid.

Here in this example we’re lucky because it’s a very simple form. If it were a larger form with multiple fields requiring validation, we’d have to handle the possibility for multiple invalid fields.

Here though, we’re only concerned with the single name form control. If it’s valid, we simply submit the form. Easy right? If not, we simply need to focus the field.

Now, it’s important to mention here, I’m using a class and CSS to make the button appear disabled while the form is invalid.

form.component.html

<button [class.disabled]="name.invalid" ...>
    Continue
</button>

This is important because I often get the question, “why not use the HTML disabled attribute?”. The reason I don’t use it is because making it disabled will take the button out of the tab order.

So, a user navigating via the keyboard, or some other assistive technology would tab right on by not knowing that’s the end of the form.

This is a problem because these users may want to navigate to the end of the form to discover what’s being requested of them before filling it all out. So, the disabled attribute should be avoided for this type of thing.

Ok, to handle the focusing when invalid, let’s add a new function to submit the form, let’s call it submitForm():

form.component.ts

protected submitForm() {
}

We’ll call this function when the button is activated so within it, we’ll set our “submitted” property just like we were in the template, based on the valid state of the form control:

form.component.ts

protected submitForm() {
    this.submitted = this.name.valid;
}

Now, we can add a condition to set focus. So, if “submitted” is false, we’ll want to programmatically focus the input:

form.component.ts

protected submitForm() {
    ...
    if (!this.submitted) {
    }
}

Now, to focus this field, we need to get a handle to the input element, so let’s add a “field” parameter to our function. It will be typed to an HTMLInputElement since we’ll be passing it our name input element:

form.component.ts

protected submitForm(field: HTMLInputElement) {
    ...
}

Ok, now we can programmatically focus this element when “submitted” is false:

form.component.ts

protected submitForm(field: HTMLInputElement) {
    ...
    if (!this.submitted) {
        field.focus();
    }
}

Now, the last thing we need to do is prevent the form from natively submitting in the browser since we handle the form submission ourselves. We will need to prevent the default action of the button.

To do this, we can pass the click event to the submitForm() function as a parameter. It’ll be typed to a MouseEvent:

form.component.ts

protected submitForm(field: HTMLInputElement,
                     event: MouseEvent
) {
    ...
}

Now, within our function, we want to prevent the default action of this event since we handle everything ourselves:

form.component.ts

protected submitForm(field: HTMLInputElement,
                     event: MouseEvent
) {
    event.preventDefault();
    ...
}

Ok, now we just need to wire this up in the template. Let’s add a template reference variable on the input element, we’ll call it “nameField”.

form.component.html

<input #nameField ... />

Ok, now in the click event on the button, let’s replace this with the submitForm() function, and we’ll pass it our input element reference. We’ll also need to pass along the click event too:

form.component.html

<button (click)="submitForm(nameField, $event)" ...>
    Continue
</button>

Ok, that should be all that we need. Let’s save it and try it out:

Example of a form automatically focusing the only invalid form when it's invalid but the form was submitted

Now, when we tab to the name field, and then to the button, when we hit enter, now we see that it no longer reloads and instead focuses the name field like we want.

And then, if we add a valid name, we can properly submit the form.

So now it’ll be much less confusing to the user.

In Conclusion

So, by following these tips, we can greatly enhance the accessibility of our Angular form, making it usable by a much wider range of users.

In this particular case, we had an application that, if used by people with disabilities, would be fairly inaccessible to them. By implementing the strategies we just covered, we were able to make the form much more accessible.

The importance of making Angular forms accessible cannot be overstated. By following these strategies, we can ensure that our application is usable by everyone, regardless of their abilities.

That’s why I want to leave you with a sense of responsibility to prioritize accessibility in your own projects.

Now, if you’ve made it this far, I’d love to hear from you in the comments below! Have you had any experiences with making forms accessible in Angular? Do you have any tips or tricks to share?

Be sure to check out my YouTube channel for more Angular tips and tutorials, and let’s keep the conversation going!

Additional Resources

Want to See It in Action?

Check out the demo code and examples of these techniques in the in the Stackblitz example below. If you have any questions or thoughts, don’t hesitate to leave a comment.

Want to Show Some Love?

If you found any this helpful and want to show some love, you can always buy me a coffee or buy some funny Angular merch from my shop!