angular aria hero image

Introduction

While Signal Forms have been highly anticipated by many developers (myself included), the Angular Aria package is by far my favorite feature in Angular 21.

The most difficult part of starting with accessibility is learning how to implement it. Angular Aria is removing that entry barrier by providing amazing directives. That does not mean that you don't have to learn anything about A11Y now or in the future ;) 

Cinema Application

As some of you may know, I consider myself a bookworm and comic addict, and because of that, I’ve always wanted to create a Comic book on my own, but was lacking drawing skills. With the evolution of AI - Nano Banana, I can finally do it! I’m also a big fan of learning by doing, and that is exactly what we will do today. We will create a simplified cinema application using every piece of the library. Now, let me introduce you to our Comic Book Heroes!

  1. PM - Elena  Fisher

  2. Software Engineer - Matteo Rossi

  3. UI/UX - Camille Dubois 

Start with Accessibility, Stay away from Tech Debt

Image 1

Comic Book #1 Transcript

  1. Elana - Let’s start with Accessibility.

  2. Camille - Matteo, you can do that, right?

  3. Matteo - Sure, we have Angular-Aria.

Starting with accessibility reduces technical debt, but its real value goes beyond that. An accessibility-first approach fundamentally changes how you think about the user journey. When accessibility is considered from the beginning, you’re forced to design for a wider range of users. This often leads to clearer information hierarchy, more intentional interaction patterns, and simpler flows overall. Instead of optimizing for a “default” user and adjusting later, accessibility pushes you to build inclusive experiences by design. There’s also a strong practical advantage. Applying accessibility after features are fully implemented is significantly harder. At that point, accessibility issues often expose deeper structural problems semantic markup, focus order, and component composition that require large refactors. Addressing these concerns early allows accessibility requirements to shape the structure itself, rather than fighting against it later. In that sense, accessibility-first isn’t just a best practice - it’s a way to build better foundations from the start, both for users and for the codebase.

Installation

  1. git clone https://github.com/AdrianRomanski/angular-aria-cinema

  2. cd angular-aria-cinema

  3. Check out the starting point: git checkout part-1-start

  4. yarn install

Day Selection

Image 2

Comic Book #2 Transcript

  1. PM - We have a repertoire scheduled a week in advance.

  2. Camille - Let’s limit the choice of days to this week.

  3. Matteo - Select will be fine.

So the requirements are clear: users need to select a day from the current week . The obvious choice might seem to be a Select but before jumping to that conclusion, let’s take a closer look. Our first interaction with Angular Aria will actually be through a Combobox . Why introduce a Combobox when the end goal looks like a simple Select? Understanding this distinction early will help clarify accessibility patterns and component behavior as we move forward.

Combobox Container

Code

“Note on color-coding.”

Before we start, I will introduce you to the color legend.

  1. Code related to the current example.

  2. Comment blocks used for grouping.

This approach will make the main parts stand out while still keeping the big picture clear.

Let’s start by adding our first import - Combobox from @angular/aria/combobox that will act as a wrapper for Select.

libs/reservation/feature-movie-selection/src/lib/ui-day-selection.ts First, we have to create a container that will help with coordinating input and popup, and orchestrating its behavior.

libs/reservation/feature-movie-selection/src/lib/ui-day-selection.html

Then apply the ngCombobox directive to the container and set readonly (we want to prevent users from typing).

libs/reservation/feature-movie-selection/src/lib/ui-day-selection.html

Documentation

ngCombobox

  • Turns this container into a Combobox root, coordinating input with popup content.

  • Manages open/close state, keyboard interaction, and ARIA roles. 

readonly

  • Prevents free text input - selection must come from options.

Visualization & A11Y Tree

this weekScreenshot 2026-01-11 at 11.24.35It-Aint-Much-But-Its-Honest-Work-meme-passes-away

ComboboxInput

With our container ready, let’s add an input block. 

Code 

Add another import from @angular/aria/combobox - ComboboxInput

We need to add more functionality:

  1. A display for the selected day or placeholder message using the day() method

  2. An input element for date selection 

  3. An arrow icon to indicate the open/closed state

Add the ngComboboxInput directive to the input, and it will register it as ComboboxInput.  

Let’s check the accessibility tree.

Screenshot 2026-01-11 at 11.32.42

We need to fix two problems: 

  1. combobox  is not labelled.

    1. add id ‘week-label’  to the header.

    2. add id ‘day-value’ to span with day() signal.

    3. make an association with them by adding aria-labelledby=”week-label day-value” to the input.

  2. should not be visible, it's purely visual (open and close states are handled by aria-expanded).

    1. hide it by adding aria-hidden=”true”

Screenshot 2026-01-11 at 15.11.55

Cool! The icon is no longer visible in the tree, and combobox has meaningful labels. 

Documentation

ngComboboxInput

  • Registers this input as the Combobox input.

  • Handles focus, keyboard navigation, and ARIA attributes.

id="week-label" id="day-value"

  • Identifiers for ARIA association with aria-labelledby.

aria-labelledby

  • Associates the input with human-readable text from the heading and selected value.

aria-hidden

  • Hides the decorative icon (▼) from assistive technologies since it’s purely visual.

Visualization & A11Y Tree

Closed State

Screenshot 2026-01-11 at 11.11.07

Screenshot 2026-01-11 at 15.11.55 (1)

Tree : We got a proper label, it is focusable and expanded is set to false (closed)

Focused State

Screenshot 2026-01-11 at 11.11.17Screenshot 2026-01-11 at 15.11.55 (1)

Visual: A proper focus indicator is visible Tree: focused is set to true, expanded is set to false (closed)

Open State

Screenshot 2026-01-11 at 11.11.17Screenshot 2026-01-11 at 15.11.55 (1)

Visual: An arrow is rotated

Tree: expanded is set to true (open)

Connecting Template With Trigger

After the user interacts with the input, we want to show the list of days. To achieve this, we'll connect a template that holds the list to a trigger using a combination of Angular Aria and Angular CDK.

Code

First, add another import from @angular/aria/combobox - ComboboxPopupContainer , then add a new library '@angular/cdk/overlay , and import CdkConnectedOverlay from it.

We need a new template with the ngComboboxPopupContainer directive.

Now we have to connect the trigger with an overlay. Let’s start by declaring a template reference variable to Input#trigger . We have to pass this variable to cdkConnectedOverlay . For that, we need to add another template inside ngComboboxPopupContainer . Two directives are required to make it work.

  1. [cdkConnectedOverlayOpen] ="true"

  2. [cdkConnectedOverlay] = "{origin: daySelection, usePopower: ‘inline’, matchWidth: true}"

    1. origin - Overlay is positioned relative to this element - daySelection

    2. usePopower - Overlay stays inside the current component DOM tree

    3. matchWidth - Overlay’s width matches the origin element’s width

Documentation

ngComboboxPopupContainer

  • Marks where the combobox popup content is defined.

  • Ensures correct ARIA relationships between trigger and popup.

cdkConnectedOverlay

  • Positions the popup relative to the trigger element by passing dayOrigin .

Visualization & A11Y Tree

Screenshot 2026-01-11 at 11.11.07Screenshot 2026-01-11 at 19.09.19

Group has been added to the tree, it represents the overlay where the list will be displayed. It will be displayed after interaction with the element (clicking it or focusing it), as it’s lazy-loaded.

Week Template

The last piece of this component is actually the template we want to show. 

It will hold a List of day Options for the current week.

Code

Add two imports: Listbox and Option from '@angular/aria/listbox' .

Add an ordered list with the ngListbox directive and aria-label=”Available days” .

Why an ordered list? Because the order of days matters, we don’t want to start a new week on Friday… or do we? 😄

If we get an unordered list, we need list items. Start by adding <li> with the ngOption directive. Loop through the days() signal that has an available days list. Now let’s add inputs:

  1. [value]=”day.value” - value that will be emitted after selection

  2. [label]=”day.label” - value that will be displayed as a label of the item

There are 3 things we want to display: the selected state, the name of the day, and the date itself.

Documentation

ngListbox

  • Defines an ARIA listbox.

  • Manages option selection, focus, and keyboard navigation.

[(values)]="selectedDay"

  • Enables two-way binding for the selected option.

ngOption

  • Registers this element as a selectable option inside the listbox and applies ARIA role="option".

[value]="day.value"

  • The value emitted when this option is selected.

[label]="day.label"

  • An accessible label used by screen readers

Visualization & A11Y Tree

Open List of Days

Screenshot 2026-01-12 at 10.08.01Screenshot 2026-01-12 at 10.09.40

After opening the listbox, focus stays on it, and expanded is set to true.

Select a Day

Screenshot 2026-01-12 at 10.14.29Screenshot 2026-01-12 at 10.14.17

After selecting the day, selected is set to true.

Feel the User Journey

Code

Screenshot 2026-01-14 at 17.49.56

Comic Book #3 Transcript

  1. PM - Good, so we can see this is the current week.

  2. Camille - Does it show the number of days?

  3. Matteo - It does, and navigation is also working. Same with selecting items.

If we really want to be sure that our solution is working, we need to get our hands dirty 😀 Screen Reader testing is not that hard if you do it step by step. First, we need to turn on the software. But before we jump in, here’s another color legend 😂

  • Pressed Key

  • Assistive Technology

  • Code From Example

Voiceover MacBook

  1. Turn on the Voiceover

  2. TAB

  3. “Current Week Select a day, clickable list box popup collapsed combobox”

    1. “Current Week Select a day” - these values come from aria-labelledby="week-label day-value"

    2. “clickable” indicates that the element is interactive and can be activated

    3. “listbox” - comes from role="listbox" that was applied by ngListbox

    4. “popup” - comes from aria-haspopup="listbox" that was applied by ngComboboxPopupContainer

    5. “collapsed” - comes from aria-expanded="false" and means that the listbox is currently closed

    6. “combobox” - comes from role="combobox" that was applied by ngCombobox

It does not look readable at first sight, but after some practice it becomes more logical. We should interpret it this way: “This is an interactive control. It’s a combobox that opens a listbox popup, and that popup is currently closed.” Let’s activate it 🙂

  1. Enter

  2. “Expanded, list box 7 items”

    1. expanded - comes from aria-expanded="true", which means that the listbox is currently open

    2. 7 items - the number of elements with role=”option” inside the element with role=”listbox”. The option role was applied by ngOption

  3. Escape

  4. “Collapsed”

  5. Space

  6. ArrowDown

  7. “Monday January 12 not selected, menu item, (1 of 7)”

  8. ArrowDown

  9. “Tuesday January 13 not selected, menu item, (2 of 7)”

  10. Enter

  11. “collapsed,  13 January” means the listbox is closed and the selected item is 13 January

Homework

Screenshot 2026-01-14 at 17.50.03

Comic Book #4 Transcript

  1. Matteo - I have a feeling we might be missing something.

  2. Camille - Good point! What about holidays?

  3. PM - How about we delegate this to our trainee?

Our trainee is joining the room:

  1. Software Engineer – Lucas Meyer

  2. PM - Elena  Fisher

  3. Software Engineer - Matteo Rossi

  4. UI/UX - Camille Dubois 

Screenshot 2026-01-14 at 17.50.10

Comic Book #5 Transcript

  1. PM - Lucas did an amazing job at the tech interview

  2. Matteo - Perfect, we are doing Day Selector, but the disabled state is missing.

  3. Camille - We need it for holidays in case of emergency

  4. Lucas Meyer - Sure! Will do that.

Tasks

  1. Add a disabled state using the option-directive

  2. Add a story that features one or more holidays

Screenshot 2026-01-14 at 17.50.37

Comic Book #6 Transcript

  1. Lucas Meyer - It’s working!

  2. PM - So, did we just finish the sprint?

  3. Matteo - Let’s hand it off to QA and catch up next Monday.

  4. Camille - We can select a day. What’s next?

Solution

git checkout part-1-finish

If you enjoyed this article, I’m happy to share that we will be creating a new component every second Monday. If you’re reading this in the future, just jump into the next lesson! 😀

https://github.com/AdrianRomanski/angular-aria-cinema