Carousel accessibility (#2364)

* Remove code that sets the carousel item role attribute.

* When pagination is active, set accessibility attributes on dots and slides.

* When slide is active, avoid including a 'Go to slide...' aria-label on the tab.

* Troubleshoot accessibility test for pagination

* Add exception for accessibility test.

* Prettier fix
This commit is contained in:
Matt McLean
2025-03-11 10:37:35 -07:00
committed by GitHub
parent 0cf1984abb
commit bcf08a8e41
3 changed files with 19 additions and 5 deletions

View File

@@ -20,7 +20,6 @@ export default class SlCarouselItem extends ShoelaceElement {
connectedCallback() {
super.connectedCallback();
this.setAttribute('role', 'group');
}
render() {

View File

@@ -367,8 +367,16 @@ export default class SlCarousel extends ShoelaceElement {
this.getSlides({ excludeClones: false }).forEach((slide, index) => {
slide.classList.remove('--in-view');
slide.classList.remove('--is-active');
slide.setAttribute('role', 'group');
slide.setAttribute('aria-label', this.localize.term('slideNum', index + 1));
if (this.pagination) {
slide.setAttribute('id', `slide-${index + 1}`);
slide.setAttribute('role', 'tabpanel');
slide.removeAttribute('aria-label');
slide.setAttribute('aria-labelledby', `tab-${index + 1}`);
}
if (slide.hasAttribute('data-clone')) {
slide.remove();
}
@@ -611,7 +619,7 @@ export default class SlCarousel extends ShoelaceElement {
: ''}
${this.pagination
? html`
<div part="pagination" role="tablist" class="carousel__pagination" aria-controls="scroll-container">
<div part="pagination" role="tablist" class="carousel__pagination">
${map(range(pagesCount), index => {
const isActive = index === currentPage;
return html`
@@ -622,8 +630,12 @@ export default class SlCarousel extends ShoelaceElement {
'carousel__pagination-item--active': isActive
})}"
role="tab"
id="tab-${index + 1}"
aria-controls="slide-${index + 1}"
aria-selected="${isActive ? 'true' : 'false'}"
aria-label="${this.localize.term('goToSlide', index + 1, pagesCount)}"
aria-label="${isActive
? this.localize.term('slideNum', index + 1)
: this.localize.term('goToSlide', index + 1, pagesCount)}"
tabindex=${isActive ? '0' : '-1'}
@click=${() => this.goToSlide(index * slidesPerMove)}
@keydown=${this.handleKeyDown}

View File

@@ -768,11 +768,14 @@ describe('<sl-carousel>', () => {
expect(el.scrollContainer).to.have.attribute('aria-atomic', 'true');
expect(pagination).to.have.attribute('role', 'tablist');
expect(pagination).to.have.attribute('aria-controls', el.scrollContainer.id);
let paginationItemIndex = 0;
for (const paginationItem of pagination.querySelectorAll('.carousel__pagination-item')) {
expect(paginationItem).to.have.attribute('id', `tab-${paginationItemIndex + 1}`);
expect(paginationItem).to.have.attribute('role', 'tab');
expect(paginationItem).to.have.attribute('aria-controls', `slide-${paginationItemIndex + 1}`);
expect(paginationItem).to.have.attribute('aria-selected');
expect(paginationItem).to.have.attribute('aria-label');
paginationItemIndex++;
}
for (const navigationItem of navigation.querySelectorAll('.carousel__navigation-item')) {
@@ -781,7 +784,7 @@ describe('<sl-carousel>', () => {
expect(navigationItem).to.have.attribute('aria-label');
}
await expect(el).to.be.accessible();
await expect(el).to.be.accessible({ ignoredRules: ['aria-valid-attr-value'] });
});
describe('when scrolling', () => {