An accordion organizes related content into expandable and collapsible sections, reducing page scrolling and helping users focus on relevant information. Each section has a trigger button and a content panel. Clicking a trigger toggles the visibility of its associated panel.
<divngAccordionGroup class="basic-accordion" [multiExpandable]="false"> <h3> <spanngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" ></span> </span> </h3> <div ngAccordionPanel #panel1="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger"> How do you link the button to the content it controls? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" ></span> </span> </h3> <div ngAccordionPanel #panel2="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" ></span> </span> </h3> <div ngAccordionPanel #panel3="ngAccordionPanel"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
<divngAccordionGroup class="basic-accordion" [multiExpandable]="false"> <h3> <spanngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" ></span> </span> </h3> <div ngAccordionPanel #panel1="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger"> How do you link the button to the content it controls? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" ></span> </span> </h3> <div ngAccordionPanel #panel2="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" ></span> </span> </h3> <div ngAccordionPanel #panel3="ngAccordionPanel"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
<divngAccordionGroup class="material-accordion" [multiExpandable]="false"> <h3> <spanngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" translate="no" >keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel #panel1="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger" [expanded]="true"> How do you link the button to the content it controls? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" translate="no" >keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel #panel2="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" translate="no" >keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel #panel3="ngAccordionPanel"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
<divngAccordionGroup class="basic-accordion"> <h3> <spanngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" ></span> </span> </h3> <div ngAccordionPanel #panel1="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger"> How do you link the button to the content it controls? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" ></span> </span> </h3> <div ngAccordionPanel #panel2="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger" [expanded]="true"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" ></span> </span> </h3> <div ngAccordionPanel #panel3="ngAccordionPanel"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
<divngAccordionGroup class="material-accordion"> <h3> <spanngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" translate="no" >keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel #panel1="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger"> How do you link the button to the content it controls? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" translate="no" >keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel #panel2="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger" [expanded]="true"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" translate="no" >keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel #panel3="ngAccordionPanel"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
Disable specific triggers using the disabled input. Control how disabled items behave during keyboard navigation using the softDisabled input on the accordion group.
<divngAccordionGroup class="basic-accordion"> <h3> <spanngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" ></span> </span> </h3> <div ngAccordionPanel #panel1="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger" disabled> How do you link the button to the content it controls? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" ></span> </span> </h3> <div ngAccordionPanel #panel2="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q3" #panel3="ngAccordionPanel"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
<divngAccordionGroup class="material-accordion"> <h3> <spanngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" translate="no" >keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel #panel1="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger" disabled> How do you link the button to the content it controls? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" translate="no" >keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel #panel2="ngAccordionPanel"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <spanngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" translate="no" >keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel #panel3="ngAccordionPanel"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
When [softDisabled]="true" (the default), disabled items can receive focus but cannot be activated. When [softDisabled]="false", disabled items are skipped entirely during keyboard navigation.
Use the ngAccordionContent directive on an ng-template to defer rendering content until the panel first expands. This improves performance for accordions with heavy content like images, charts, or complex components.
<divngAccordionGroup> <div> <buttonngAccordionTrigger [panel]="panel1">Trigger Text</button> <div ngAccordionPanel #panel1="ngAccordionPanel"> <ng-template ngAccordionContent> <!-- This content only renders when the panel first opens --> <img src="large-image.jpg" alt="Description" /> <app-expensive-component /> </ng-template> </div> </div></div>
By default, content remains in the DOM after the panel collapses. Set [preserveContent]="false" to remove the content from the DOM when the panel closes.
Angular Aria provides component harnesses for testing accordion components.
Here is an example of how to use the harnesses in a component test:
import {ComponentFixture, TestBed} from '@angular/core/testing';import {HarnessLoader} from '@angular/cdk/testing';import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';import {AccordionGroupHarness} from '@angular/aria/accordion/testing';import {MyAccordionComponent} from './my-accordion'; // Your componentdescribe('MyAccordionComponent', () => { let fixture:ComponentFixture<MyAccordionComponent>; let loader:HarnessLoader; beforeEach(async () => {TestBed.configureTestingModule({ imports: [MyAccordionComponent], }); fixture =TestBed.createComponent(MyAccordionComponent); await fixture.whenStable(); loader =TestbedHarnessEnvironment.loader(fixture); }); it('should allow expanding panels', async () => { // Load the accordion group harness const group = await loader.getHarness(AccordionGroupHarness); // Get all individual accordions (items) in the group const accordions = await group.getAccordions(); expect(accordions.length).toBe(3); // Verify initial state (first expanded, others collapsed) expect(await accordions[0].isExpanded()).toBe(true); expect(await accordions[1].isExpanded()).toBe(false); // Expand the second panel await accordions[1].expand(); // Verify updated state expect(await accordions[1].isExpanded()).toBe(true); // If multiExpandable is false, the first one should now be collapsed expect(await accordions[0].isExpanded()).toBe(false); });});