From 08a1cb16889fd8bada9682f78d8a745aacb25bff Mon Sep 17 00:00:00 2001 From: gerjanvangeest Date: Wed, 22 May 2024 09:24:20 +0200 Subject: [PATCH] fix(tabs): make tab panels focusable (#2281) --- .changeset/ninety-needles-drop.md | 5 ++ packages/ui/components/tabs/src/LionTabs.js | 10 +++ .../ui/components/tabs/test/lion-tabs.test.js | 87 +++++++++++-------- 3 files changed, 64 insertions(+), 38 deletions(-) create mode 100644 .changeset/ninety-needles-drop.md diff --git a/.changeset/ninety-needles-drop.md b/.changeset/ninety-needles-drop.md new file mode 100644 index 000000000..e65095b40 --- /dev/null +++ b/.changeset/ninety-needles-drop.md @@ -0,0 +1,5 @@ +--- +'@lion/ui': patch +--- + +[tabs] make tab panels focusable diff --git a/packages/ui/components/tabs/src/LionTabs.js b/packages/ui/components/tabs/src/LionTabs.js index bb9849acc..821c072b9 100644 --- a/packages/ui/components/tabs/src/LionTabs.js +++ b/packages/ui/components/tabs/src/LionTabs.js @@ -19,6 +19,16 @@ function setupPanel({ el, uid }) { el.setAttribute('id', `panel-${uid}`); el.setAttribute('role', 'tabpanel'); el.setAttribute('aria-labelledby', `button-${uid}`); + /** + * Facilitates navigation to panel content for assistive technology users. + * + * Focusable tab panel elements are recommended if any panels in a set contain + * content where the first element in the panel is not focusable. + * https://www.w3.org/WAI/ARIA/apg/patterns/tabs/examples/tabs-automatic/ + */ + if (!el.hasAttribute('tabindex')) { + el.setAttribute('tabindex', '0'); + } } /** diff --git a/packages/ui/components/tabs/test/lion-tabs.test.js b/packages/ui/components/tabs/test/lion-tabs.test.js index 6511a0566..d7a4a929e 100644 --- a/packages/ui/components/tabs/test/lion-tabs.test.js +++ b/packages/ui/components/tabs/test/lion-tabs.test.js @@ -452,45 +452,26 @@ describe('', () => { }); describe('Accessibility', () => { - it('does not make panels focusable', async () => { - const el = /** @type {LionTabs} */ ( - await fixture(html` - - -
panel 1
- -
panel 2
-
- `) - ); - expect(Array.from(el.children).find(child => child.slot === 'panel')).to.not.have.attribute( - 'tabindex', - ); - expect(Array.from(el.children).find(child => child.slot === 'panel')).to.not.have.attribute( - 'tabindex', - ); - }); - - it('makes selected tab focusable (other tabs are unfocusable)', async () => { - const el = /** @type {LionTabs} */ ( - await fixture(html` - - -
panel 1
- -
panel 2
- -
panel 3
-
- `) - ); - const tabs = el.querySelectorAll('[slot=tab]'); - expect(tabs[0]).to.have.attribute('tabindex', '0'); - expect(tabs[1]).to.have.attribute('tabindex', '-1'); - expect(tabs[2]).to.have.attribute('tabindex', '-1'); - }); - describe('Tabs', () => { + it('makes selected tab focusable (other tabs are unfocusable)', async () => { + const el = /** @type {LionTabs} */ ( + await fixture(html` + + +
panel 1
+ +
panel 2
+ +
panel 3
+
+ `) + ); + const tabs = el.querySelectorAll('[slot=tab]'); + expect(tabs[0]).to.have.attribute('tabindex', '0'); + expect(tabs[1]).to.have.attribute('tabindex', '-1'); + expect(tabs[2]).to.have.attribute('tabindex', '-1'); + }); + it('links ids of content items to tab via [aria-controls]', async () => { const el = /** @type {LionTabs} */ ( await fixture(html` @@ -562,6 +543,36 @@ describe('', () => { expect(panels[0]).to.have.attribute('aria-labelledby', tabs[0].id); expect(panels[1]).to.have.attribute('aria-labelledby', tabs[1].id); }); + + it('makes panel focusable', async () => { + const el = /** @type {LionTabs} */ ( + await fixture(html` + + +
panel 1
+ +
panel 2
+
+ `) + ); + const panels = el.querySelectorAll('[slot=panel]'); + expect(panels[0]).to.have.attribute('tabindex', '0'); + }); + + it('does not override the tabindex already set on the panel', async () => { + const el = /** @type {LionTabs} */ ( + await fixture(html` + + +
panel 1
+ +
panel 2
+
+ `) + ); + const panels = el.querySelectorAll('[slot=panel]'); + expect(panels[0]).to.have.attribute('tabindex', '-1'); + }); }); });