diff --git a/aria-practices.html b/aria-practices.html index cb4235a08f..6ed25bdd30 100644 --- a/aria-practices.html +++ b/aria-practices.html @@ -2772,8 +2772,8 @@

Tabs

Examples

@@ -2835,6 +2835,7 @@

Keyboard Interaction

  • If the tab list is horizontal, it does not listen for Down Arrow or Up Arrow so those keys can provide their normal browser scrolling functions even when focus is inside the tab list.
  • +
  • When the tabpanel does not contain any focusable elements or the first element with content is not focusable, the tabpanel should set tabindex="0" to include it in the tab sequence of the page.
  • @@ -2849,7 +2850,7 @@

    WAI-ARIA Roles, States, and Properties

  • Each element with role tab has the property aria-controls referring to its associated tabpanel element.
  • The active tab element has the state aria-selected set to true and all other tab elements have it set to false.
  • -
  • Each element with role tabpanel has the property aria-labelledby referring to its associated tab element.
  • +
  • Each element with role tabpanel has the property aria-labelledby referring to its associated tab element.
  • If a tab element has a popup menu, it has the property aria-haspopup set to either menu or true.
  • If the tablist element is vertically oriented, it has the property aria-orientation set to vertical. diff --git a/cspell.json b/cspell.json index cbfcc3480f..d3c7609f7d 100644 --- a/cspell.json +++ b/cspell.json @@ -11,6 +11,7 @@ "Accesskey", "activedescendants", "affordance", + "Ahlefeldt", "alertdialog", "Anjou", "Aristov", @@ -74,6 +75,7 @@ "Focusability", "focusable", "Foltz", + "Fonseca", "Foyle", "Frahm", "funders", @@ -87,6 +89,7 @@ "Gunderson", "Hassium", "Hausler", + "Henriette", "highlighttext", "Higley", "Hillen", @@ -104,8 +107,10 @@ "jumpto", "Kasper", "kbdshortcuts", + "Kiærskou", "Kowno", "Labelable", + "Lange", "Lauke", "Leventhal", "Lewandowski", @@ -129,6 +134,7 @@ "moreaccessible", "Moscovium", "MSAA", + "Müller", "multithumb", "navs", "Nemeth", @@ -179,8 +185,10 @@ "Ségur", "Shirisha", "shizzle", + "Siboni", "Shopify", "Smorgeni", + "Sofie", "sortability", "sourcecode", "Spinbuttons", @@ -202,9 +210,11 @@ "textarea's", "textfield", "Thaarup", + "Theresia", "Thiel", "Thiers", "thomascorthals", + "Tonekunstnerinde", "togglable", "transactinide", "transuranic", @@ -220,6 +230,7 @@ "unmanaged", "Vasily", "Vinkle", + "virkelig", "vnurc", "Vyacheslav", "walkability", diff --git a/examples/index.html b/examples/index.html index 5b35cedb86..c4c1002db2 100644 --- a/examples/index.html +++ b/examples/index.html @@ -379,8 +379,8 @@

    Examples by Role

    @@ -393,8 +393,8 @@

    Examples by Role

    @@ -403,8 +403,8 @@

    Examples by Role

    @@ -532,8 +532,8 @@

    Examples By Properties and States

  • Actions Menu Button Using aria-activedescendant (HC)
  • Actions Menu Button Using element.focus() (HC)
  • Navigation Menu Button (HC)
  • -
  • Tabs with Automatic Activation
  • -
  • Tabs with Manual Activation
  • +
  • Tabs with Automatic Activation (HC)
  • +
  • Tabs with Manual Activation (HC)
  • Toolbar
  • @@ -655,8 +655,6 @@

    Examples By Properties and States

  • Horizontal Multi-Thumb Slider (HC)
  • Date Picker Spin Button
  • Table
  • -
  • Tabs with Automatic Activation
  • -
  • Tabs with Manual Activation
  • Toolbar
  • Treegrid Email Inbox
  • Navigation Treeview (HC)
  • @@ -695,8 +693,8 @@

    Examples By Properties and States

  • Vertical Temperature Slider (HC)
  • Date Picker Spin Button
  • Switch Using HTML Button (HC)
  • -
  • Tabs with Automatic Activation
  • -
  • Tabs with Manual Activation
  • +
  • Tabs with Automatic Activation (HC)
  • +
  • Tabs with Manual Activation (HC)
  • File Directory Treeview Using Computed Properties
  • File Directory Treeview Using Declared Properties
  • Navigation Treeview (HC)
  • @@ -816,8 +814,8 @@

    Examples By Properties and States

  • Listbox with Grouped Options
  • Listboxes with Rearrangeable Options
  • Scrollable Listbox
  • -
  • Tabs with Automatic Activation
  • -
  • Tabs with Manual Activation
  • +
  • Tabs with Automatic Activation (HC)
  • +
  • Tabs with Manual Activation (HC)
  • File Directory Treeview Using Computed Properties
  • File Directory Treeview Using Declared Properties
  • diff --git a/examples/tabs/css/tabs.css b/examples/tabs/css/tabs.css index dfbfb8fe2d..bbb1fc794b 100644 --- a/examples/tabs/css/tabs.css +++ b/examples/tabs/css/tabs.css @@ -1,107 +1,71 @@ .tabs { - width: 20em; font-family: "lucida grande", sans-serif; } [role="tablist"] { - margin: 0 0 -0.1em; - overflow: visible; + min-width: 550px; } -[role="tab"] { +[role="tab"], +[role="tab"]:focus, +[role="tab"]:hover { position: relative; + z-index: 2; + top: 2px; margin: 0; - padding: 0.3em 0.5em 0.4em; + margin-top: 4px; + padding: 3px 3px 4px; border: 1px solid hsl(219deg 1% 72%); - border-radius: 0.2em 0.2em 0 0; - box-shadow: 0 0 0.2em hsl(219deg 1% 72%); + border-bottom: 2px solid hsl(219deg 1% 72%); + border-radius: 5px 5px 0 0; overflow: visible; - font-family: inherit; - font-size: inherit; background: hsl(220deg 20% 94%); -} - -[role="tab"]:hover::before, -[role="tab"]:focus::before, -[role="tab"][aria-selected="true"]::before { - position: absolute; - bottom: 100%; - right: -1px; - left: -1px; - border-radius: 0.2em 0.2em 0 0; - border-top: 3px solid hsl(20deg 96% 48%); - content: ""; + outline: none; + font-weight: bold; } [role="tab"][aria-selected="true"] { - border-radius: 0; + padding: 2px 2px 4px; + margin-top: 0; + border-width: 2px; + border-top-width: 6px; + border-top-color: rgb(36 116 214); + border-bottom-color: hsl(220deg 43% 99%); background: hsl(220deg 43% 99%); - outline: 0; } -[role="tab"][aria-selected="true"]:not(:focus):not(:hover)::before { - border-top: 5px solid hsl(218deg 96% 48%); +[role="tab"][aria-selected="false"] { + border-bottom: 1px solid hsl(219deg 1% 72%); } -[role="tab"][aria-selected="true"]::after { - position: absolute; - z-index: 3; - bottom: -1px; - right: 0; - left: 0; - height: 0.3em; - background: hsl(220deg 43% 99%); - box-shadow: none; - content: ""; -} - -[role="tab"]:hover, -[role="tab"]:focus, -[role="tab"]:active { - outline: 0; - border-radius: 0; - color: inherit; +[role="tab"] span.focus { + display: inline-block; + margin: 2px; + padding: 4px 6px; } -[role="tab"]:hover::before, -[role="tab"]:focus::before { - border-color: hsl(20deg 96% 48%); +[role="tab"]:hover span.focus, +[role="tab"]:focus span.focus, +[role="tab"]:active span.focus { + padding: 2px 4px; + border: 2px solid rgb(36 116 214); + border-radius: 3px; } [role="tabpanel"] { - position: relative; - z-index: 2; - padding: 0.5em 0.5em 0.7em; - border: 1px solid hsl(219deg 1% 72%); - border-radius: 0 0.2em 0.2em; - box-shadow: 0 0 0.2em hsl(219deg 1% 72%); + padding: 5px; + border: 2px solid hsl(219deg 1% 72%); + border-radius: 0 5px 5px; background: hsl(220deg 43% 99%); + min-height: 10em; + min-width: 550px; + overflow: auto; } [role="tabpanel"].is-hidden { display: none; } -[role="tabpanel"]:focus { - border-color: hsl(20deg 96% 48%); - box-shadow: 0 0 0.2em hsl(20deg 96% 48%); - outline: 0; -} - -[role="tabpanel"]:focus::after { - position: absolute; - bottom: 0; - right: -1px; - left: -1px; - border-bottom: 3px solid hsl(20deg 96% 48%); - border-radius: 0 0 0.2em 0.2em; - content: ""; -} - [role="tabpanel"] p { margin: 0; } - -[role="tabpanel"] * + p { - margin-top: 1em; -} diff --git a/examples/tabs/js/tabs-automatic.js b/examples/tabs/js/tabs-automatic.js new file mode 100644 index 0000000000..0c8286690d --- /dev/null +++ b/examples/tabs/js/tabs-automatic.js @@ -0,0 +1,136 @@ +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: tabs-automatic.js + * + * Desc: Tablist widget that implements ARIA Authoring Practices + */ + +'use strict'; + +class TabsAutomatic { + constructor(groupNode) { + this.tablistNode = groupNode; + + this.tabs = []; + + this.firstTab = null; + this.lastTab = null; + + this.tabs = Array.from(this.tablistNode.querySelectorAll('[role=tab]')); + this.tabpanels = []; + + for (var i = 0; i < this.tabs.length; i += 1) { + var tab = this.tabs[i]; + var tabpanel = document.getElementById(tab.getAttribute('aria-controls')); + + tab.tabIndex = -1; + tab.setAttribute('aria-selected', 'false'); + this.tabpanels.push(tabpanel); + + tab.addEventListener('keydown', this.onKeydown.bind(this)); + tab.addEventListener('click', this.onClick.bind(this)); + + if (!this.firstTab) { + this.firstTab = tab; + } + this.lastTab = tab; + } + + this.setSelectedTab(this.firstTab, false); + } + + setSelectedTab(currentTab, setFocus) { + if (typeof setFocus !== 'boolean') { + setFocus = true; + } + for (var i = 0; i < this.tabs.length; i += 1) { + var tab = this.tabs[i]; + if (currentTab === tab) { + tab.setAttribute('aria-selected', 'true'); + tab.removeAttribute('tabindex'); + this.tabpanels[i].classList.remove('is-hidden'); + if (setFocus) { + tab.focus(); + } + } else { + tab.setAttribute('aria-selected', 'false'); + tab.tabIndex = -1; + this.tabpanels[i].classList.add('is-hidden'); + } + } + } + + setSelectedToPreviousTab(currentTab) { + var index; + + if (currentTab === this.firstTab) { + this.setSelectedTab(this.lastTab); + } else { + index = this.tabs.indexOf(currentTab); + this.setSelectedTab(this.tabs[index - 1]); + } + } + + setSelectedToNextTab(currentTab) { + var index; + + if (currentTab === this.lastTab) { + this.setSelectedTab(this.firstTab); + } else { + index = this.tabs.indexOf(currentTab); + this.setSelectedTab(this.tabs[index + 1]); + } + } + + /* EVENT HANDLERS */ + + onKeydown(event) { + var tgt = event.currentTarget, + flag = false; + + switch (event.key) { + case 'ArrowLeft': + this.setSelectedToPreviousTab(tgt); + flag = true; + break; + + case 'ArrowRight': + this.setSelectedToNextTab(tgt); + flag = true; + break; + + case 'Home': + this.setSelectedTab(this.firstTab); + flag = true; + break; + + case 'End': + this.setSelectedTab(this.lastTab); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } + } + + onClick(event) { + this.setSelectedTab(event.currentTarget); + } +} + +// Initialize tablist + +window.addEventListener('load', function () { + var tablists = document.querySelectorAll('[role=tablist].automatic'); + for (var i = 0; i < tablists.length; i++) { + new TabsAutomatic(tablists[i]); + } +}); diff --git a/examples/tabs/js/tabs-manual.js b/examples/tabs/js/tabs-manual.js new file mode 100644 index 0000000000..0b8102f0b2 --- /dev/null +++ b/examples/tabs/js/tabs-manual.js @@ -0,0 +1,136 @@ +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: tabs-manual.js + * + * Desc: Tablist widget that implements ARIA Authoring Practices + */ + +'use strict'; + +class TabsManual { + constructor(groupNode) { + this.tablistNode = groupNode; + + this.tabs = []; + + this.firstTab = null; + this.lastTab = null; + + this.tabs = Array.from(this.tablistNode.querySelectorAll('[role=tab]')); + this.tabpanels = []; + + for (var i = 0; i < this.tabs.length; i += 1) { + var tab = this.tabs[i]; + var tabpanel = document.getElementById(tab.getAttribute('aria-controls')); + + tab.tabIndex = -1; + tab.setAttribute('aria-selected', 'false'); + this.tabpanels.push(tabpanel); + + tab.addEventListener('keydown', this.onKeydown.bind(this)); + tab.addEventListener('click', this.onClick.bind(this)); + + if (!this.firstTab) { + this.firstTab = tab; + } + this.lastTab = tab; + } + + this.setSelectedTab(this.firstTab); + } + + setSelectedTab(currentTab) { + for (var i = 0; i < this.tabs.length; i += 1) { + var tab = this.tabs[i]; + if (currentTab === tab) { + tab.setAttribute('aria-selected', 'true'); + tab.removeAttribute('tabindex'); + this.tabpanels[i].classList.remove('is-hidden'); + } else { + tab.setAttribute('aria-selected', 'false'); + tab.tabIndex = -1; + this.tabpanels[i].classList.add('is-hidden'); + } + } + } + + moveFocusToTab(currentTab) { + currentTab.focus(); + } + + moveFocusToPreviousTab(currentTab) { + var index; + + if (currentTab === this.firstTab) { + this.moveFocusToTab(this.lastTab); + } else { + index = this.tabs.indexOf(currentTab); + this.moveFocusToTab(this.tabs[index - 1]); + } + } + + moveFocusToNextTab(currentTab) { + var index; + + if (currentTab === this.lastTab) { + this.moveFocusToTab(this.firstTab); + } else { + index = this.tabs.indexOf(currentTab); + this.moveFocusToTab(this.tabs[index + 1]); + } + } + + /* EVENT HANDLERS */ + + onKeydown(event) { + var tgt = event.currentTarget, + flag = false; + + switch (event.key) { + case 'ArrowLeft': + this.moveFocusToPreviousTab(tgt); + flag = true; + break; + + case 'ArrowRight': + this.moveFocusToNextTab(tgt); + flag = true; + break; + + case 'Home': + this.moveFocusToTab(this.firstTab); + flag = true; + break; + + case 'End': + this.moveFocusToTab(this.lastTab); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } + } + + // Since this example uses buttons for the tabs, the click onr also is activated + // with the space and enter keys + onClick(event) { + this.setSelectedTab(event.currentTarget); + } +} + +// Initialize tablist + +window.addEventListener('load', function () { + var tablists = document.querySelectorAll('[role=tablist].manual'); + for (var i = 0; i < tablists.length; i++) { + new TabsManual(tablists[i]); + } +}); diff --git a/examples/tabs/tabs-1/js/tabs.js b/examples/tabs/tabs-1/js/tabs.js deleted file mode 100644 index 9c17ba34ff..0000000000 --- a/examples/tabs/tabs-1/js/tabs.js +++ /dev/null @@ -1,257 +0,0 @@ -/* - * This content is licensed according to the W3C Software License at - * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document - */ - -'use strict'; - -(function () { - var tablist = document.querySelectorAll('[role="tablist"]')[0]; - var tabs; - var panels; - var delay = determineDelay(); - - generateArrays(); - - function generateArrays() { - tabs = document.querySelectorAll('[role="tab"]'); - panels = document.querySelectorAll('[role="tabpanel"]'); - } - - // For easy reference - var keys = { - end: 35, - home: 36, - left: 37, - up: 38, - right: 39, - down: 40, - delete: 46, - }; - - // Add or subtract depending on key pressed - var direction = { - 37: -1, - 38: -1, - 39: 1, - 40: 1, - }; - - // Bind listeners - for (var i = 0; i < tabs.length; ++i) { - addListeners(i); - } - - function addListeners(index) { - tabs[index].addEventListener('click', clickEventListener); - tabs[index].addEventListener('keydown', keydownEventListener); - tabs[index].addEventListener('keyup', keyupEventListener); - - // Build an array with all tabs ( - - +

    Danish Composers

    +
    + + + +
    -
    -

    Nils Frahm is a German musician, composer and record producer based in Berlin. He is known for combining classical and electronic music and for an unconventional approach to the piano in which he mixes a grand piano, upright piano, Roland Juno-60, Rhodes piano, drum machine, and Moog Taurus.

    +
    +

    Maria Theresia Ahlefeldt (16 January 1755 – 20 December 1810) was a Danish, (originally German), composer. She is known as the first female composer in Denmark. Maria Theresia composed music for several ballets, operas, and plays of the royal theatre. She was given good critic as a composer and described as a “virkelig Tonekunstnerinde” ('a True Artist of Music').

    - - @@ -68,10 +125,29 @@

    Example

    Accessibility Features

    -

    - To demonstrate the effects of deleting a tab, the third tab, labeled Joke, - can be deleted when it has focus by pressing Delete. -

    +
      +
    • + To make it easy for screen reader users to navigate from a tab to the beginning of content in the active tabpanel, the tabpanel element has tabindex="0" to include the panel in the page Tab sequence. + It is recommended that all tabpanel elements in a tab set are focusable if there are any panels in the set that contain content where the first element in the panel is not focusable. +
    • +
    • To ensure people who rely on browser or operating system high contrast settings can both distinguish the active (selected) tab from other tabs and perceive keyboard focus: +
        +
      • + The active tab has a 2 pixel border on its left and right sides and a 4 pixel border on top, while the names of inactive tabs have 1 pixel borders. + The active tab is also 4 pixels higher than the inactive tabs. +
      • +
      • + The focus ring is drawn with a CSS border on a child span element of the tab element. + This focus span is separated from the tab border by 2 pixels of space to ensure focus and selection are separately perceivable. + Note that when a tab element is focused, the outline of the tab element itself is set to 0 so that only one focus ring is displayed. +
      • +
      • + Because transparent borders are visible on some systems when high contrast settings are enabled, only the focused span element has a visible border. + When span elements are not indicating focus, they have a 0-width border and additional padding equal in width to the border that is used to indicate focus. +
      • +
      +
    • +
    @@ -121,10 +197,6 @@

    Keyboard Support

    End Moves focus to the last tab and activates it. - - Delete - When focus is on the Joke tab, removes the tab from the tab list and places focus on the previous tab. -
    @@ -151,10 +223,10 @@

    Role, Property, State, and Tabindex Attributes

    Indicates that the element serves as a container for a set of tabs. - + - aria-label=Entertainment + aria-labelledby="ID_REFERENCE" div @@ -172,27 +244,22 @@

    Role, Property, State, and Tabindex Attributes

    - aria-selected=true + aria-selected="true" button @@ -200,7 +267,7 @@

    Role, Property, State, and Tabindex Attributes

    - aria-selected=false + aria-selected="false" button @@ -208,17 +275,17 @@

    Role, Property, State, and Tabindex Attributes

    - tabindex=-1 + tabindex="-1" button @@ -227,21 +294,21 @@

    Role, Property, State, and Tabindex Attributes

    - aria-controls=IDREF + aria-controls="ID_REFERENCE" button - Refers to the tabpanel element associated with the tab. + Refers to the element with role=tabpanel associated with the tab. @@ -264,16 +331,14 @@

    Role, Property, State, and Tabindex Attributes

    - aria-labelledby=IDREF + aria-labelledby="ID_REFERENCE" div @@ -281,7 +346,7 @@

    Role, Property, State, and Tabindex Attributes

    - tabindex=0 + tabindex="0" div @@ -289,8 +354,8 @@

    Role, Property, State, and Tabindex Attributes

    @@ -301,8 +366,8 @@

    Role, Property, State, and Tabindex Attributes

    Javascript and CSS Source Code

    @@ -321,9 +386,7 @@

    HTML Source Code

    - - diff --git a/examples/tabs/tabs-2/tabs.html b/examples/tabs/tabs-manual.html similarity index 54% rename from examples/tabs/tabs-2/tabs.html rename to examples/tabs/tabs-manual.html index 27eb2d07e0..7f24f3b185 100644 --- a/examples/tabs/tabs-2/tabs.html +++ b/examples/tabs/tabs-manual.html @@ -6,34 +6,39 @@ - - - - + + + + - + + + +

    Example of Tabs with Manual Activation

    - The below example section demonstrates a tabs widget that implements the tabs design pattern. - In this example, a new panel is displayed only after the user activates a tab with either Space, Enter, or a mouse click. - Typically, manual activation of tabs is only necessary when panels cannot be displayed instantly, i.e., not all the panel content is present in the DOM. - For additional guidance, see Deciding When to Make Selection Automatically Follow Focus. + The below example section demonstrates a tabs widget that implements the tabs design pattern. + In this example, a panel is displayed when users activate its tab with either Space, Enter, or a mouse click. + So, for keyboard users, activating a tab requires two steps: 1) focus the tab, and 2) activate the tab. + This two-step process is referred to as manual activation. + Manual activation of tabs is recommended unless panels can be displayed instantly, i.e., all the panel content is present in the DOM. + For additional guidance, see Deciding When to Make Selection Automatically Follow Focus.

    Similar examples include:

    @@ -43,23 +48,74 @@

    Example

    -
    - - - +

    Danish Composers

    +
    + + + +
    -
    -

    Nils Frahm is a German musician, composer and record producer based in Berlin. He is known for combining classical and electronic music and for an unconventional approach to the piano in which he mixes a grand piano, upright piano, Roland Juno-60, Rhodes piano, drum machine, and Moog Taurus.

    +
    +

    Maria Theresia Ahlefeldt (16 January 1755 – 20 December 1810) was a Danish, (originally German), composer. She is known as the first female composer in Denmark. Maria Theresia composed music for several ballets, operas, and plays of the royal theatre. She was given good critic as a composer and described as a “virkelig Tonekunstnerinde” ('a True Artist of Music').

    - - @@ -68,10 +124,29 @@

    Example

    Accessibility Features

    -

    - To demonstrate the effects of deleting a tab, the third tab, labeled Joke, - can be deleted when it has focus by pressing Delete. -

    +
      +
    • To ensure people who rely on browser or operating system high contrast settings can both distinguish the active (selected) tab from other tabs and perceive keyboard focus: +
        +
      • + The active tab has a 2 pixel border on its left and right sides and a 4 pixel border on top, while the names of inactive tabs have 1 pixel borders. + The active tab is also 4 pixels higher than the inactive tabs. +
      • +
      • + The focus ring is drawn with a CSS border on a child span element of the tab element. + This focus span is separated from the tab border by 2 pixels of space to ensure focus and selection are separately perceivable. + Note that when a tab element is focused, the outline of the tab element itself is set to 0 so that only one focus ring is displayed. +
      • +
      • + Because transparent borders are visible on some systems when high contrast settings are enabled, only the focused span element has a visible border. + When span elements are not indicating focus, they have a 0-width border and additional padding equal in width to the border that is used to indicate focus. +
      • +
      +
    • +
    • + Note that since the first element in every tabpanel is a focusable element (i.e., a link), the tabpanel is not included in the page Tab sequence. + To make it easy for screen reader users to navigate from a tab to the beginning of content in the active tabpanel, it is recommended that all tabpanel elements in a tab set are focusable if there are any panels in the set that contain content where the first element in the panel is not focusable. +
    • +
    @@ -89,7 +164,7 @@

    Keyboard Support

    • When focus moves into the tab list, places focus on the active tab element .
    • -
    • When the tab list contains the focus, moves focus to the next element in the tab sequence, which is the tabpanel element.
    • +
    • When the tab list contains the focus, moves focus to the next element in the tab sequence, which is the a element in tabpanel.
    @@ -127,10 +202,6 @@

    Keyboard Support

    End When a tab has focus, moves focus to the last tab. - - Delete - When focus is on the Joke tab, removes the tab from the tab list and places focus on the previous tab. -
    @@ -157,10 +228,10 @@

    Role, Property, State, and Tabindex Attributes

    Indicates that the element serves as a container for a set of tabs. - + - aria-label=Entertainment + aria-labelledby="ID_REFERENCE" div @@ -187,7 +258,7 @@

    Role, Property, State, and Tabindex Attributes

    - aria-selected=true + aria-selected="true" button @@ -203,7 +274,7 @@

    Role, Property, State, and Tabindex Attributes

    - aria-selected=false + aria-selected="false" button @@ -221,7 +292,7 @@

    Role, Property, State, and Tabindex Attributes

    - tabindex=-1 + tabindex="-1" button @@ -230,15 +301,15 @@

    Role, Property, State, and Tabindex Attributes

    • Removes the element from the page Tab sequence.
    • Set when a tab is not selected so that only the selected (active) tab is in the page Tab sequence.
    • -
    • Since an HTML button element is used for the tab, it is not necessary to set tabindex=0 on the selected (active) tab element.
    • -
    • This approach to managing focus is described in the section on roving tabindex.
    • +
    • Since an HTML button element is used for the tab, it is not necessary to set tabindex="0" on the selected (active) tab element.
    • +
    • This approach to managing focus is described in the section on roving tabindex.
    - aria-controls=IDREF + aria-controls="ID_REFERENCE" button @@ -267,7 +338,7 @@

    Role, Property, State, and Tabindex Attributes

    - aria-labelledby=IDREF + aria-labelledby="ID_REFERENCE" div @@ -281,22 +352,6 @@

    Role, Property, State, and Tabindex Attributes

    - - - - tabindex=0 - - - div - - -
      -
    • Puts the tabpanel in the page Tab sequence.
    • -
    • Facilitates movement to panel content for assistive technology users.
    • -
    • Especially helpful if there are panels that do not contain a focusable element.
    • -
    - -
    @@ -304,8 +359,8 @@

    Role, Property, State, and Tabindex Attributes

    Javascript and CSS Source Code

    @@ -323,10 +378,7 @@

    HTML Source Code

    - - - diff --git a/test/tests/tabs_tabs-1.js b/test/tests/tabs_tabs-automatic.js similarity index 84% rename from test/tests/tabs_tabs-1.js rename to test/tests/tabs_tabs-automatic.js index c511f2d1f3..5ee032c298 100644 --- a/test/tests/tabs_tabs-1.js +++ b/test/tests/tabs_tabs-automatic.js @@ -3,24 +3,22 @@ const { Key } = require('selenium-webdriver'); const assertAttributeValues = require('../util/assertAttributeValues'); const assertAriaControls = require('../util/assertAriaControls'); const assertAriaLabelledby = require('../util/assertAriaLabelledby'); -const assertAriaLabelExists = require('../util/assertAriaLabelExists'); const assertAriaRoles = require('../util/assertAriaRoles'); -const assertNoElements = require('../util/assertNoElements'); const assertTabOrder = require('../util/assertTabOrder'); -const exampleFile = 'tabs/tabs-1/tabs.html'; +const exampleFile = 'tabs/tabs-automatic.html'; const ex = { tablistSelector: '#ex1 [role="tablist"]', tabSelector: '#ex1 [role="tab"]', tabpanelSelector: '#ex1 [role="tabpanel"]', - tabCount: 3, - deletableId: 'complex', + tabCount: 4, tabTabOrder: [ - // button id, tab id - ['#nils', '#nils-tab'], - ['#agnes', '#agnes-tab'], - ['#complex', '#complex-complex'], + // button id, tabpanel id + ['#tab-1', '#tabpanel-1'], + ['#tab-2', '#tabpanel-2'], + ['#tab-3', '#tabpanel-3'], + ['#tab-4', '#tabpanel-4'], ], }; @@ -73,11 +71,11 @@ ariaTest( ); ariaTest( - '"ariaLabel" attribute on role="tablist"', + '"ariaLabelledby" attribute on role="tablist"', exampleFile, - 'tablist-aria-label', + 'tablist-aria-labelledby', async (t) => { - await assertAriaLabelExists(t, ex.tablistSelector); + await assertAriaLabelledby(t, ex.tablistSelector); } ); @@ -86,7 +84,7 @@ ariaTest( exampleFile, 'tab-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'tab', '3', 'button'); + await assertAriaRoles(t, 'ex1', 'tab', ex.tabCount.toString(), 'button'); } ); @@ -192,7 +190,7 @@ ariaTest( exampleFile, 'tabpanel-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'tabpanel', '3', 'div'); + await assertAriaRoles(t, 'ex1', 'tabpanel', ex.tabCount.toString(), 'div'); } ); @@ -206,7 +204,7 @@ ariaTest( ); ariaTest( - 'tabindex="0" on role="tabpanel" elements', + 'tabindex="0" on tabpanel elements', exampleFile, 'tabpanel-tabindex', async (t) => { @@ -217,7 +215,7 @@ ariaTest( // Keys ariaTest( - 'TAB key moves focus to open tab and panel', + 'TAB key moves focus to open tab and first link in the tabpanel', exampleFile, 'key-tab', async (t) => { @@ -407,54 +405,3 @@ ariaTest( } } ); - -ariaTest( - 'DELETE key removes third tab', - exampleFile, - 'key-delete', - async (t) => { - let tabs = await t.context.queryElements(t, ex.tabSelector); - - // Put focus on the first tab - await openTabAtIndex(t, 0); - - // Send the delete key to the tab - await tabs[0].sendKeys(Key.DELETE); - - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 3, - 'Sending DELETE to first tab should not change number of tabs' - ); - - // Put focus on the second tab - await openTabAtIndex(t, 1); - - // Send the delete key to the tab - await tabs[1].sendKeys(Key.DELETE); - - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 3, - 'Sending DELETE to second tab should not change number of tabs' - ); - - // Put focus on the last tab - await openTabAtIndex(t, 2); - - // Send the delete key to the tab - await tabs[2].sendKeys(Key.DELETE); - - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 2, - 'Sending DELETE to third tab should change number of tabs' - ); - - assertNoElements( - t, - `#${ex.deletableId}`, - `Sending DELETE to third tab should have delete tab with id: ${ex.deletableId}` - ); - } -); diff --git a/test/tests/tabs_tabs-2.js b/test/tests/tabs_tabs-manual.js similarity index 82% rename from test/tests/tabs_tabs-2.js rename to test/tests/tabs_tabs-manual.js index 4aa115088d..f489d4b75e 100644 --- a/test/tests/tabs_tabs-2.js +++ b/test/tests/tabs_tabs-manual.js @@ -1,26 +1,23 @@ const { ariaTest } = require('..'); const { By, Key } = require('selenium-webdriver'); -const assertAttributeValues = require('../util/assertAttributeValues'); const assertAriaControls = require('../util/assertAriaControls'); const assertAriaLabelledby = require('../util/assertAriaLabelledby'); -const assertAriaLabelExists = require('../util/assertAriaLabelExists'); const assertAriaRoles = require('../util/assertAriaRoles'); -const assertNoElements = require('../util/assertNoElements'); const assertTabOrder = require('../util/assertTabOrder'); -const exampleFile = 'tabs/tabs-2/tabs.html'; +const exampleFile = 'tabs/tabs-manual.html'; const ex = { tablistSelector: '#ex1 [role="tablist"]', tabSelector: '#ex1 [role="tab"]', tabpanelSelector: '#ex1 [role="tabpanel"]', - tabCount: 3, - deletableId: 'complex', + tabCount: 4, tabTabOrder: [ - // button id, tab id - ['#nils', '#nils-tab'], - ['#agnes', '#agnes-tab'], - ['#complex', '#complex-complex'], + // button id, tabpanel id + ['#tab-1', '#tabpanel-1 a'], + ['#tab-2', '#tabpanel-2 a'], + ['#tab-3', '#tabpanel-3 a'], + ['#tab-4', '#tabpanel-4 a'], ], }; @@ -74,11 +71,11 @@ ariaTest( ); ariaTest( - '"ariaLabel" attribute on role="tablist"', + '"ariaLabelledby" attribute on role="tablist"', exampleFile, - 'tablist-aria-label', + 'tablist-aria-labelledby', async (t) => { - await assertAriaLabelExists(t, ex.tablistSelector); + await assertAriaLabelledby(t, ex.tablistSelector); } ); @@ -87,7 +84,7 @@ ariaTest( exampleFile, 'tab-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'tab', '3', 'button'); + await assertAriaRoles(t, 'ex1', 'tab', ex.tabCount.toString(), 'button'); } ); @@ -193,7 +190,7 @@ ariaTest( exampleFile, 'tabpanel-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'tabpanel', '3', 'div'); + await assertAriaRoles(t, 'ex1', 'tabpanel', ex.tabCount.toString(), 'div'); } ); @@ -206,19 +203,10 @@ ariaTest( } ); -ariaTest( - 'tabindex="0" on role="tabpanel" elements', - exampleFile, - 'tabpanel-tabindex', - async (t) => { - await assertAttributeValues(t, ex.tabpanelSelector, 'tabindex', '0'); - } -); - // Keys ariaTest( - 'TAB key moves focus to open tab and panel', + 'TAB key moves focus to open tab and first link in the tabpanel', exampleFile, 'key-tab', async (t) => { @@ -396,54 +384,3 @@ ariaTest('END key moves focus', exampleFile, 'key-end', async (t) => { ); } }); - -ariaTest( - 'DELETE key removes third tab', - exampleFile, - 'key-delete', - async (t) => { - const tabs = await t.context.queryElements(t, ex.tabSelector); - - // Put focus on the first tab - await openTabAtIndex(t, 0); - - // Send the delete key to the tab - await tabs[0].sendKeys(Key.DELETE); - - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 3, - 'Sending DELETE to first tab should not change number of tabs' - ); - - // Put focus on the second tab - await openTabAtIndex(t, 1); - - // Send the delete key to the tab - await tabs[1].sendKeys(Key.DELETE); - - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 3, - 'Sending DELETE to second tab should not change number of tabs' - ); - - // Put focus on the last tab - await openTabAtIndex(t, 2); - - // Send the delete key to the tab - await tabs[2].sendKeys(Key.DELETE); - - t.is( - (await t.context.queryElements(t, ex.tabSelector)).length, - 2, - 'Sending DELETE to third tab should change number of tabs' - ); - - assertNoElements( - t, - `#${ex.deletableId}`, - `Sending DELETE to third tab should have delete tab with id: ${ex.deletableId}` - ); - } -);