Build more accessible Angular apps

1. Before you begin

black Angular logo

Accessibility is a vital part of web development, ensuring that users can perceive, understand, navigate, and interact with apps. In fact, 1 in 4 US adults have a disability that impacts their major life activities. Worldwide, about 15 percent of the world's population—more than 1 billion people—have some form of disability, with about 2 to 4 percent experiencing significant difficulties.

Common conditions that affect a person's use of the web include blindness or low vision, deafness or impaired hearing, restricted motor skills, cognitive disabilities, and color blindness—and this is just a partial list.

In this course, a11y is shorthand for accessibility. Notice that the a is followed by 11 characters and a y.

For an in-depth introduction to issues and techniques for designing accessible apps, see Accessibility.

What you'll build

  • Use best practices and built-in techniques to address common web accessibility issues in a demo Dumpling Shop Angular app
  • Meet all accessibility guidelines, WCAG 2.0, and ARIA 1.2, and pass axe and Lighthouse accessibility audits.

Dumpling Time shop website in pink and red theme Dumpling Time shop website in purple and green theme

What you'll learn

You learn about eight common accessibility issues in Angular apps that affect users, how to identify them, and how to fix them. More specifically, you:

  • Use Google Chrome Developer Tools, Lighthouse, and axe to audit your app's accessibility
  • Solve single-page app (SPA) pitfalls with unique page titles
  • Fix low-color contrast issues for low-vision users
  • Use semantic HTML to ensure screen readers navigate the page correctly
  • Use Angular Material and unnest controls to ensure screen readers can access all controls
  • Add ARIA support for screen readers
  • Import and use the Angular CDK a11y package
  • Use FocusTrap for custom component screen-reader navigation
  • Announce notifications with the CDK LiveAnnouncer
  • Detect users with HighContrast mode and implement high-contrast theming

What you'll need

2. Get set up

Get the code

Everything you need for this project is in a GitHub repository. To get started, clone the code and open it in your favorite dev environment.

Clone the repository and serve the app

VSCode or a local IDE is the recommended method for working through this codelab.

  1. Open a new browser tab and go to https://github.com/googlecodelabs/angular-accessibility.
  2. Fork and clone the repository, and cd angular-accessibility/ into the repository.
  3. Check out the starter code branch git checkout get-started.
  4. Open the code in VSCode or your preferred IDE.
  5. Run npm install to install the dependencies required to run the server.
  6. Run ng serve to run the server.
  7. Open a browser tab to http://localhost:4200.

3. Establish a baseline

What's your starting point?

Your starting point is a basic restaurant app designed for this codelab. The code has been simplified to show the concepts in this codelab and it has little functionality.

Dumpling Time shop website in purple and green theme

Explore the demo

To get started, walk through the three functionalities of your app:

  1. Use the navigation bar to view Our Shop, Our Story, and Find Us routes, and see details about the dumpling company.
  2. Change themes to toggle light and dark mode.
  3. Customize your order's dumpling fillings, quantity, and color.
  4. Select Purchase to log your customized order in the console.

Use Angular to address common web accessibility issues

In this codelab, you focus on the accessibility of the existing features of this app. You will start by identifying a11y issues in your app, then turn the 🛑 into a ✅ by implementing a solution.

How do you know what to fix?

Start each example by recognizing the accessibility issue using a mixture of manual and automated testing.

In the current state of the web, manually testing accessibility is mandatory.

You have tools that can identify accessibility issues, but no tool can certify that an app is fully accessible. Manual testing ensures that you test for a breadth of a11y concepts that include logical content order and feature parity.

Manual testing

To manually test accessibility in this course, you turn on our computer's built-in screen reader and navigate through your app with keyboard navigation. For more information, see Semantics and screen readers.

Practice by turning on the screen reader and navigating the screen.

You can use the MacOS built-in VoiceOver. Click System Preferences > Accessibility > VoiceOver > Enable VoiceOver to turn it on. To toggle VoiceOver, quickly press TouchID three times while holding the Command key.

In this course, you primarily test issues manually, and use automated tools to assist in checking specific automatable features.

Automated testing

You also use a few development tools to automate and audit your app. These tools allow you to check things like the presence of alt text on an image or the contrast ratio of a text color. You can think of these tools as linters; they can recognize that alt text is present, but you must manually check that the content is logical and provides value.

Lighthouse and Chrome Developer Tools

  1. Open Chrome Developer Tools.
  2. Select the Lighthouse tab and select the Accessibility checkbox.
  3. Click Generate report to run an a11y Lighthouse audit.

Lighthouse example tab with button to Generate Report in a Chrome DevTools tab

Axe

  1. Install the axe DevTools extension. You may need to restart your browser to see the extension.
  2. Open Chrome Developer Tools.
  3. Select the axe DevTools tab and select Scan all of my page to run an axe DevTools scan.

Linting

You can use the Angular ESLint rules to lint your code for automatable a11y attributes.

In eslint.json, add the following, which apply to accessibility:

"@angular-eslint/template/accessibility-alt-text": 2,
"@angular-eslint/template/accessibility-elements-content": 2,
"@angular-eslint/template/accessibility-label-for": 2,
"@angular-eslint/template/no-positive-tabindex": 2,
"@angular-eslint/template/accessibility-table-scope": 2,
"@angular-eslint/template/accessibility-valid-aria": 2,
"@angular-eslint/template/click-events-have-key-events": 2,
"@angular-eslint/template/mouse-events-have-key-events": 2,
"@angular-eslint/template/no-autofocus": 2,
"@angular-eslint/template/no-distracting-elements": 2

For more information, see the latest ESLint rules on GitHub.

Your starting point

Using your new testing methods, you can identify the following issues in your app using Lighthouse and axe audits, and manual VoiceOver:

Chrome DevTools Lighthouse audit with score of 82

Accessibility audit:

  • 🛑 All pages have the same page title
  • 🛑 Elements must have sufficient color contrast
  • 🛑 HTML should have logical order, name, and role
  • 🛑 Nested checkboxes are not selectable for screen readers
  • 🛑 Screen reader does not read slider values
  • 🛑 Screen reader focus in the color picker exits the dialog
  • 🛑 Changes, errors, and notifications are not announced
  • 🛑 HighContrast mode is not enabled

4. Define unique page titles

Providing unique, concise page titles helps users using a11y services quickly understand a web page's content and purpose. Page titles are critical to users with visual disabilities because they are the first page element announced by screen-reading software.

Angular is a single-page app and, as a result, a majority of the transitions, such as moving to a new page, do not involve a page reload. Until recently, this meant that each page had an identical page title, and provided no value for understanding the page's content or purpose.

In Angular v14, the Router added a built-in method to define unique page titles out of the box. This provides a streamlined approach to ensure developers follow page title best practices.

By the end of this section, your app will pass the following audit:

  • 🛑 All pages have the same page title

You can find each of these steps under the comment: TODO: #4. Define unique page titles.

Identify the issue

To identify this issue, turn on your screen reader and navigate between the Our Shop, Our Story, and Find Us tabs to see the page titles:

  1. Turn on VoiceOver.
  2. Use tab navigation to navigate between pages.
  3. Verify the page title is always a11y in Angular.

This is an issue because your page title must be unique so that a user can quickly understand what the page is about without having to navigate through it.

Chrome browser with three tabs open with identical page title: 'a11y in Angular'

Add meaningful page titles

If a page or view changes, you want to manage the page title properly. To fix this, you use Angular's built-in Router.title property to define unique titles for each of your pages.

  1. Add a unique title to each of the three defined routes:

src/app/app-routing.module.ts

const routes: Routes = [
  { path: 'shop', component: ShopComponent, title: 'Our Shop – a11y in Angular' },
  { path: 'about', component: AboutComponent, title: 'Our Story - a11y in Angular' },
  { path: 'locate', component: LocationComponent, title: 'Find Us - a11y in Angular' },
  { path: '',   redirectTo: '/shop', pathMatch: 'full' },
  { path: '**', component: ShopComponent },
];

This will automatically import and use the Router's Title Service under the hood to manage changing the page title on navigation to match the title property defined in our routes. You can also manage more complicated page titles using a custom TitleStrategy.

Verify changes

Turn on your screen reader again and verify your changes. The pages should now have unique titles!

Chrome browser with three tabs open with unique page title: 'Our Shop - a11y in Angular', 'Our Story - a11y in Angular', 'Find Us - a11y in Angular'

Accessibility audit:

  • All pages have unique page titles
  • 🛑 Elements must have sufficient color contrast
  • 🛑 HTML should have logical order, name, and role
  • 🛑 Nested checkboxes are not selectable for screen readers
  • 🛑 Screen reader does not read slider values
  • 🛑 Screen reader focus in the color picker exits the dialog
  • 🛑 Changes, errors, and notifications are not announced
  • 🛑 HighContrast mode is not enabled

5. Ensure adequate color contrast

Your design might seem cool, but it's not if people with visual impairments like color blindness can't read your content. The Web Content Accessibility Guidelines (WCAG 2.0) define a series of color contrast ratios, which ensure that content is accessible. In Angular and on the web, you can define color palettes that ensure your components meet these standards and are visible for users with low-vision and color blindness.

By the end of this section, your app will pass the following audit:

  • 🛑 Elements must have sufficient color contrast

You can find each of these steps under the comments: TODO: #5. Ensure adequate color contrast.

Use Chrome Developer Tools to identify low-contrast issues

To identify this issue, use Chrome Developer Tools to inspect the elements in your app.

  1. Use the inspect tool to view the menu icon buttons. You can see that the contrast is 1.85, far below the WCAG requirements.

Chrome DevTools inspect element of a Home button with low contrast

  1. Run the Accessibility audit in Lighthouse or axe's scan to see these contrast-ratio issues.

Chrome DevTools Lighthouse audit results with error: 'Background and foreground colors do not have a sufficient contrast ratio'

Change Material theme color

Your component color scheme is defined in your custom Material theme. You update your theme value to meet the color-contrast ratio guidelines.

Update your Material theme to use a darker text color, increasing the contrast ratio of your icons:

src/styles.scss

$light-primary: mat.define-palette(mat.$pink-palette, $default: A100, $lighter: 100, $text: 900);

You can also use Chrome Developer Tools built-in accessibility tooling to find a color that meets standards, or update individual color values in Sass.

Verify changes

Inspect your elements again and verify your changes, our theme should now have sufficient color contrast ratios!

Chrome DevTools inspect element of a Home button with sufficient contrast

Accessibility Audit

  • All pages have unique page titles
  • Colors have a sufficient contrast ratio
  • 🛑 HTML should have logical order, name, and role
  • 🛑 Nested checkboxes are not selectable for screen readers
  • 🛑 Screen reader does not read slider values
  • 🛑 Screen reader focus in the color picker exits the dialog
  • 🛑 Changes, errors, and notifications are not announced
  • 🛑 HighContrast mode is not enabled

6. Use Semantic HTML

Native HTML elements capture a number of standard interaction patterns that are important to accessibility. While a paragraph can be styled as a span and a div can be styled as a button, semantic HTML element ensures that screen readers and keyboard navigation understand the interactions and controls of your HTML.

When authoring Angular components, you should reuse these native elements directly when possible, rather than reimplement well-supported behaviors. This ensures that the page has good content structure and natural content flow, and that the tab is in a logical order to assist users navigating the website with effective use of the keyboard.

By the end of this section, your app will pass the following audit:

  • 🛑 HTML should have logical order, name, and role

You can find each of these steps under the comments: TODO: #6. Use Semantic HTML.

Identify the issue

  1. Turn on VoiceOver.
  2. Use tab navigation to click through to the Our Story tab.
  3. Notice the tab order is not sequential.
  4. Click Purchase.
  5. Notice the button isn't recognized as a button.

Chrome DevTools Lighthouse audit results with error: Heading elements are not in a sequentially-descending order Properly ordered headings that do not skip levels convey the semantic structure of the page, making it easier to navigate and understand when using assistive technologies. Learn more.

Changing a <div> to a <button>

Replace the custom <div> with a Material button:

src/app/shop/shop.component.html

<button mat-flat-button 
  color="primary" 
  class="purchase-button"
  (click)="fauxPurchase()">
  Purchase
</button>

Use heading elements sequentially

Reorder the text to use semantic HTML and apply styling using Angular Material typography:

src/app/about/about.component.html

<h2>Who are we?</h2>
<p class="mat-subheading-2">Have you ever thought, "wow, I love dumplings"?</p>
<p class="right mat-subheading-1">Who hasn't.</p>
<p class="center mat-subheading-1">We took it one step further and created Dumpling Dumpling,</p> 
<p class="center mat-subheading-1">double the dumpling, double the fun.</p>
<div class="spacer"></div>
<h2>How are we different?</h2>
<p class="mat-subheading-2">Handmade in San Francisco, California, we craft fully customizable dumplings. Glitter? Rainbows? Vegan? We do it all.</p>
<p class="right mat-subheading-2">This shop is concept only.</p>

Verify changes

Turn on your screen reader again and verify your changes. VoiceOver now recognizes the button and text is read in a logical order!

Accessibility audit:

  • All pages have unique page titles
  • Colors have a sufficient contrast ratio
  • Semantic HTML ensures logical interaction
  • 🛑 Nested checkboxes are not selectable for screen readers
  • 🛑 Screen reader does not read slider values
  • 🛑 Screen reader focus in the color picker exits the dialog
  • 🛑 Changes, errors, and notifications are not announced
  • 🛑 HighContrast mode is not enabled

7. Create selectable controls with Angular Material

One complicated interaction pattern for accessibility services is nested controls. Think about menu subitems or nested checkboxes. How do you indicate to a user that you can select a subgroup of options or navigate to a parent menu item?

In Angular, simplify menus and controls to create navigable components by simplifying controls as much as possible. In this example, you use Angular Material's listbox to build out one example of this interaction pattern.

By the end of this section, your app will pass the following audit:

  • 🛑 Nested checkboxes are not selectable for screen readers

You can find each of these steps under the comments: TODO: #7. Create selectable controls with Angular Material.

Identify the issue

To identify this issue we will turn on our screen reader and attempt to select a nested checkbox.

  1. Turn on VoiceOver.
  2. Select different filling flavors.
  3. Notice that the parent checkboxes don't specify children when read by VoiceOver. How do you know that the Vegan checkbox is unselected now that you unselected the Bok Choy checkbox?

Fillings checkbox menu with options: Fillings Vegan Bok Choy Tofu & Shitake Meat Chicken Impossible Meat

A11y in Angular Material

You replace the semantic checkbox with the Angular Material checkbox, which contains built-in knowledge of this interaction pattern. It's important to note that replacing components with Material does not guarantee accessibility. Like any other component, you need to manually test because there are plenty of ways to implement Material inaccessibly.

Replace checkboxes with Material checkboxes

  1. First, add your new list of fillings and a variable to store your selected filling flavors:

src/app/shop/shop.component.ts

@Component(...)
export class ShopComponent implements OnInit {
  fillings: string[] = ['Bok Choy & Chili Crunch', 'Tofu & Mushroom', 'Chicken & Ginger', 'Impossible Meat & Spinach'];
  selectedFillings: string[] = [];

  fauxPurchase(): void {
    let flavor = '';
    this.selectedFillings.forEach(filling => {
      flavor = flavor + " " + filling
    })
  }
}
  1. Add a <mat-selection-list> to replace this messy grouping of HTML checkboxes:

src/app/shop/shop.component.html

<mat-selection-list [(ngModel)]="selectedFillings" 
  aria-label="Dumpling fillings">
  <mat-list-option *ngFor="let flavor of fillings" 
    [value]="flavor" 
    color="primary">
    {{ flavor }}
  </mat-list-option>
</mat-selection-list>

Your TODO comments also show where you can remove some unused Sass in src/app/shop/shop.component.scss to clean up your styling.

Verify changes

Turn on your screen reader again and verify your changes. Your checkboxes are now selectable and more intuitively navigated with a screen reader!

Fillings checkbox menu with items: Fillings Bok Choy & Chili Crunch Tofu & Mushroom Chicken & Ginger Impossible Meat & Spinach Quantity

Accessibility audit:

  • All pages have unique page titles
  • Colors have a sufficient contrast ratio
  • Semantic HTML ensures logical interaction
  • All controls are reachable by screen readers
  • 🛑 Screen reader does not read slider values
  • 🛑 Screen reader focus in the color picker exits the dialog
  • 🛑 Changes, errors, and notifications are not announced
  • 🛑 HighContrast mode is not enabled

8. Provide control labels with ARIA

You modified your Angular app's semantic HTML and Material components, but some components require specific attributes to be navigated fully by screen readers.

The Web Accessibility Initiative's Accessible Rich Internet Applications specification (WAI-ARIA or ARIA) helps bridge issues that can't be managed with native HTML. It lets you specify attributes that modify the way an element is translated into the accessibility tree.

By the end of this section, your app will pass the following audit:

  • 🛑 Screen reader does not read slider values

You can find each of these steps under the comments: TODO: #8. Provide control labels with ARIA.

Identify the issue

To identify this issue, turn on your screen reader and move your slider:

  1. Turn on VoiceOver.
  2. Navigate to the quantity slider and change the value.
  3. Notice that the value label is missing.

Chrome DevTools Lighthouse audit results with error:  ARIA input fields do not have accessible names When an input field doesn't have an accessible name, screen readers announce it with a generic name, making it unusable for users who rely on screen readers. Learn more.

Use ARIA attributes

Label control using aria-label to <mat-slider>:

src/app/shop/shop.component.html

<mat-slider
  aria-label="Dumpling order quantity slider"
  id="quantity"
  name="quantity"
  color="primary"
  class="quantity-slider"
  [max]="13"
  [min]="1"
  [step]="1"
  [tickInterval]="1"
  thumbLabel
  [(ngModel)]="quantity">
</mat-slider>

Verify changes

Turn on your screen reader again and verify your changes. You can now move the slider!

Chrome DevTools Lighthouse audit with passing audit for screen reader ARIA controls.

Accessibility audit:

  • All pages have unique page titles
  • Colors have a sufficient contrast ratio
  • Semantic HTML ensures logical interaction
  • All controls are reachable by screen readers
  • Slider uses ARIA attributes to provide a label
  • 🛑 Screen reader focus in the color picker exits the dialog
  • 🛑 Changes, errors, and notifications are not announced
  • 🛑 HighContrast mode is not enabled

9. Add the power of @angular/cdk/a11y

Up until now, you have relied on built-in Angular tooling to fix common a11y issues. Now, let's look at the CDK's a11y module and how it can help us solve more complicated and Angular-specific issues.

By the end of this section, you will continue this course with Angular a11y module tooling.

You can find these steps under the comment: TODO: #9. Add the power of @angular/cdk/a11y.

Import the module

Add the module to your app:

src/app/app.module.ts

import { A11yModule } from '@angular/cdk/a11y';

@NgModule({
  declarations: [...],
  imports: [
    A11yModule
  ],
  providers: [...],
  bootstrap: [...]
})

What does '@angular/cdk/a11y' do?

The a11y module provides a number of tools to improve accessibility and is specifically useful for component authors.

In the following sections, you add three common services: FocusTrap, LiveAnnouncer, and HighContrast.

For more information about all of the other services that @angular/cdk/a11y provides, see Accessibility.

10. Control focus with FocusTrap

When a dialog or modal is open, a user is interacting only inside it. Allowing the focus to escape outside the dialog mixes contexts and creates a state where the user doesn't know where on the page they are.

In Angular, the cdkTrapFocus directive traps tab-key focus within an element. This is intended to be used to create accessible experience for components like modal dialogs, where focus must be constrained.

By the end of this section, your app will pass the following audit:

  • 🛑 Screen reader focus in the color picker exits the dialog

You can find these steps under the comments: TODO: #10. Control focus with FocusTrap.

Identify the issue

To identify this issue, turn on your screen reader and open the color-picker dialog.

  1. Turn on VoiceOver.
  2. Use tab navigation to change the color.
  3. Check to see intuitive focus order and focus trapping in the color picker.

Dumpling Time shop website in purple and green theme with dialog open to select the dumpling wrapping color

Add FocusTrap

cdkFocusTrap can be used to trap and control focus order in custom components. Using mat-dialog-content is enough to resolve most issues by trapping focus in a dialog. Add the attribute cdkFocusInitial to define the initial focus region on the dumpling wrapper color <mat-selection-list> within the color picker dialog.

src/app/shop/color-picker/color-picker-dialog/color-picker-dialog.component.html

<mat-selection-list #colors aria-label="Dumpling wrapper color" multiple="false" cdkFocusInitial>
  ...
</mat-selection-list>

Verify changes

Turn on your screen reader again and verify your changes. Focus is now initially set on Change Color in the dialog!

Accessibility audit:

  • All pages have unique page titles
  • Colors have a sufficient contrast ratio
  • Semantic HTML ensures logical interaction
  • All controls are reachable by screen readers
  • Slider uses ARIA attributes to provide a label
  • Color picker has correct focus trapping
  • 🛑 Changes, errors, and notifications are not announced
  • 🛑 HighContrast mode is not enabled

11. Announce changes with LiveAnnouncer

Screen readers need to be notified when something on the page changes. Imagine attempting to submit a form or complete a purchase, and not knowing an error has popped up preventing the form submission. That's frustrating!

LiveAnnouncer is used to announce messages for screen-reader users using an aria-live region to ensure screen readers are notified about notifications and live page changes. For more information on aria-live regions, see the W3C's WAI-ARIA. In Angular, calling LiveAnnouncer as a service is a more testable solution than aria-live attributes.

By the end of this section, your app will pass the following audit:

  • 🛑 Changes, errors, and notifications are not announced

You can find these steps under the comments: TODO: #11. Announce changes with LiveAnnouncer.

Identify the issue

To identify this issue, turn on your screen reader and select Purchase without completing the form fields:

  1. Turn on VoiceOver.
  2. Use tab navigation to change the color and make a fake purchase.
  3. Notice that there's no indication what color was selected when exiting the dialog and the purchase is not read.

Dumpling Time shop website in pink and red theme with dialog open to select the dumpling wrapping color

Add the LiveAnnouncer to your code

Add LiveAnnouncer, and announce both color selection and the fake purchase as a string. In a real implementation, this may be read when you navigate to a third-party payment system or for form errors.

  1. Add an announcement when a color is selected:

src/app/shop/color-picker/color-picker-dialog/color-picker-dialog.component.ts

import { LiveAnnouncer } from '@angular/cdk/a11y';

@Component(...)
export class ColorPickerDialogComponent implements OnInit {
  constructor(
    public dialogRef: MatDialogRef<ColorPickerDialogComponent>,
    @Inject(MAT_DIALOG_DATA) public data: ColorDialogData,
    private liveAnnouncer: LiveAnnouncer) { }

  public changeColor(color: string): void {
    this.liveAnnouncer.announce(`Select color: ${color}`);
    this.dialogRef.close();
  }
}
  1. Add an announcement when a fake purchase is made:

src/app/shop/shop.component.ts

import { LiveAnnouncer } from '@angular/cdk/a11y';

@Component(...)
export class ShopComponent implements OnInit {

  constructor(private liveAnnouncer: LiveAnnouncer) { }

  fauxPurchase(): void {
    let flavor = '...';
    const fakePurchase = `Purchase ${this.quantity} ${flavor}dumplings in the color ${this.color}!`;

    this.liveAnnouncer.announce(fakePurchase);
  }
}

Verify changes

Turn on your screen reader again and verify your changes. You're now notified of your errors!

Accessibility audit:

  • All pages have unique page titles
  • Colors have a sufficient contrast ratio
  • Semantic HTML ensures logical interaction
  • All controls are reachable by screen readers
  • Slider uses ARIA attributes to provide a label
  • Color picker has correct focus trapping
  • Changes, errors, and notifications are announced
  • 🛑 HighContrast mode is not enabled

12. Enable HighContrast mode

Microsoft Windows supports an accessibility feature called High Contrast Mode. This mode changes the appearance of all apps, including web apps, to dramatically increase contrast. In Angular, you want to respect a user's preferences in your app.

The HighContrastModeDetector lets you determine whether the browser is currently in a high-contrast-mode environment.

Internet Explorer, Microsoft Edge, and Firefox support this mode. Google Chrome does not support Windows High Contrast Mode. This service does not detect high-contrast mode as added by the Chrome High Contrast browser extension.

By the end of this section, your app will pass the following audit:

  • 🛑 HighContrast mode is not enabled

You can find these steps under the comments: TODO: #12. Enable HighContrast mode.

Identify the issue

To identify this issue, open your app in Internet Explorer, Microsoft Edge, or Firefox, turn on High Contrast Mode, and observe the lack of change:

  1. Open your app in Internet Explorer, Microsoft Edge, or Firefox.
  2. Turn on High Contrast Mode.
  3. Notice that the application is unchanged.

Add support for high contrast mode

In styles.scss, use the cdk-high-contrast mixin provided in @angular/cdk/a11y to add an outline to your buttons in High Contrast mode:

src/app/shop/shop.component.scss

@use '@angular/cdk';

.purchase-button {
    border-radius: 5px;
    background-color: mat.get-color-from-palette(mat.$pink-palette, A100);

    @include cdk-high-contrast {
      outline: solid 1px;
      background-color: mat.get-color-from-palette(mat.$pink-palette, 50);
    }
}

:host-context(.dark-theme) {
  .purchase-button {
    background-color: mat.get-color-from-palette(mat.$light-green-palette, A100);

    @include cdk-high-contrast {
      outline: solid 1px;
      background-color: mat.get-color-from-palette(mat.$light-green-palette, 50);
    }
  }
}

Verify changes

Refresh your app and verify your changes. You added an outline to the button in High Contrast mode!

Dumpling Time shop website in red and pink theme with High Contrast Mode on and the purchase button is now strongly focused with a thick red outline Dumpling Time shop website in blue and green theme with High Contrast Mode on and the purchase button is now strongly focused with a thick blue outline

Accessibility audit:

  • All pages have unique page titles
  • Colors have a sufficient contrast ratio
  • Semantic HTML ensures logical interaction
  • All controls are reachable by screen readers
  • Slider uses ARIA attributes to provide a label
  • Color picker has correct focus trapping
  • Changes, errors, and notifications are announced
  • High contrast mode is enabled

13. Congratulations!

Congratulations, you addressed common web accessibility issues in your Angular app! 🎉

To see all of the solutions, check out the main branch.

Dumpling Time shop website in red and pink theme shows all changes made in this codelab Dumpling Time shop website in blue and green theme shows all changes made in this codelab Chrome DevTools Lighthouse audit with score of 100/100

You now know the key steps required to resolving eight common a11y pitfalls in your Angular application.

Learn more

Check out these codelabs:

Read these materials: