diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index d73622aab0c..c216a9556b2 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -84,9 +84,18 @@ export class CdkStep implements OnChanges { /** Whether user has seen the expanded step content or not. */ interacted = false; - /** Label of the step. */ + /** Plain text label of the step. */ @Input() label: string; + /** Aria label for the tab. */ + @Input('aria-label') ariaLabel: string; + + /** + * Reference to the element that the tab is labelled by. + * Will be cleared if `aria-label` is set at the same time. + */ + @Input('aria-labelledby') ariaLabelledby: string; + /** Whether the user can return to this step once it has been marked as complted. */ @Input() get editable(): boolean { return this._editable; } diff --git a/src/lib/stepper/stepper-horizontal.html b/src/lib/stepper/stepper-horizontal.html index e3e5b27458c..82947eebff7 100644 --- a/src/lib/stepper/stepper-horizontal.html +++ b/src/lib/stepper/stepper-horizontal.html @@ -9,6 +9,8 @@ [attr.aria-setsize]="_steps.length" [attr.aria-controls]="_getStepContentId(i)" [attr.aria-selected]="selectedIndex == i" + [attr.aria-label]="step.ariaLabel || null" + [attr.aria-labelledby]="(!step.ariaLabel && step.ariaLabelledby) ? step.ariaLabelledby : null" [index]="i" [state]="_getIndicatorType(i)" [label]="step.stepLabel || step.label" diff --git a/src/lib/stepper/stepper-vertical.html b/src/lib/stepper/stepper-vertical.html index 95c1941d424..5ff402fa1e0 100644 --- a/src/lib/stepper/stepper-vertical.html +++ b/src/lib/stepper/stepper-vertical.html @@ -8,6 +8,8 @@ [attr.aria-setsize]="_steps.length" [attr.aria-controls]="_getStepContentId(i)" [attr.aria-selected]="selectedIndex === i" + [attr.aria-label]="step.ariaLabel || null" + [attr.aria-labelledby]="(!step.ariaLabel && step.ariaLabelledby) ? step.ariaLabelledby : null" [index]="i" [state]="_getIndicatorType(i)" [label]="step.stepLabel || step.label" diff --git a/src/lib/stepper/stepper.spec.ts b/src/lib/stepper/stepper.spec.ts index 961eff34282..3cb330ed073 100644 --- a/src/lib/stepper/stepper.spec.ts +++ b/src/lib/stepper/stepper.spec.ts @@ -54,6 +54,7 @@ describe('MatStepper', () => { SimpleStepperWithStepControlAndCompletedBinding, SimpleMatHorizontalStepperApp, LinearStepperWithValidOptionalStep, + StepperWithAriaInputs, ], providers: [ {provide: Directionality, useFactory: () => dir} @@ -836,6 +837,46 @@ describe('MatStepper', () => { expect(stepper.selectedIndex).toBe(2); }); }); + + describe('aria labelling', () => { + let fixture: ComponentFixture; + let stepHeader: HTMLElement; + + beforeEach(() => { + fixture = TestBed.createComponent(StepperWithAriaInputs); + fixture.detectChanges(); + stepHeader = fixture.nativeElement.querySelector('.mat-step-header'); + }); + + it('should not set aria-label or aria-labelledby attributes if they are not passed in', () => { + expect(stepHeader.hasAttribute('aria-label')).toBe(false); + expect(stepHeader.hasAttribute('aria-labelledby')).toBe(false); + }); + + it('should set the aria-label attribute', () => { + fixture.componentInstance.ariaLabel = 'First step'; + fixture.detectChanges(); + + expect(stepHeader.getAttribute('aria-label')).toBe('First step'); + }); + + it('should set the aria-labelledby attribute', () => { + fixture.componentInstance.ariaLabelledby = 'first-step-label'; + fixture.detectChanges(); + + expect(stepHeader.getAttribute('aria-labelledby')).toBe('first-step-label'); + }); + + it('should not be able to set both an aria-label and aria-labelledby', () => { + fixture.componentInstance.ariaLabel = 'First step'; + fixture.componentInstance.ariaLabelledby = 'first-step-label'; + fixture.detectChanges(); + + expect(stepHeader.getAttribute('aria-label')).toBe('First step'); + expect(stepHeader.hasAttribute('aria-labelledby')).toBe(false); + }); + + }); }); /** Asserts that keyboard interaction works correctly. */ @@ -1158,3 +1199,16 @@ class LinearStepperWithValidOptionalStep { controls = [0, 0, 0].map(() => new FormControl()); step2Optional = false; } + + +@Component({ + template: ` + + + + ` +}) +class StepperWithAriaInputs { + ariaLabel: string; + ariaLabelledby: string; +}