diff --git a/.vnurc b/.vnurc index 01d8027396..e4fd70beb0 100644 --- a/.vnurc +++ b/.vnurc @@ -23,3 +23,5 @@ Section lacks heading. Consider using “h2”-“h6” elements to add identify # https://github.com/validator/validator/issues/1096 Bad value “none” for attribute “role” on element “svg”. Bad value “presentation” for attribute “role” on element “svg”. +# https://github.com/validator/validator/issues/1122 +Element “input” is missing required attribute “aria-checked”. diff --git a/aria-practices.html b/aria-practices.html index ff39739861..a16704e028 100644 --- a/aria-practices.html +++ b/aria-practices.html @@ -2581,11 +2581,9 @@

Switch

Examples

diff --git a/examples/index.html b/examples/index.html index 7e6f27f50e..548854cd33 100644 --- a/examples/index.html +++ b/examples/index.html @@ -367,6 +367,7 @@

Examples by Role

@@ -624,6 +625,7 @@

Examples By Properties and States

  • Vertical Temperature Slider (HC)
  • Date Picker Spin Button
  • Switch Using HTML Button (HC)
  • +
  • Switch Using HTML Checkbox Input (HC)
  • Switch (HC)
  • Sortable Table (HC)
  • Toolbar
  • diff --git a/examples/switch/css/switch-checkbox.css b/examples/switch/css/switch-checkbox.css new file mode 100644 index 0000000000..b99637e684 --- /dev/null +++ b/examples/switch/css/switch-checkbox.css @@ -0,0 +1,85 @@ +fieldset { + width: 22em; +} + +legend { + font-size: 110%; +} + +label { + display: block; + margin: 0.5em; + padding: 4px 4px 6px 6px; + border: 0 solid #005a9c; + border-radius: 5px; + width: 16em; +} + +label .label { + display: inline-block; + width: 9em; + -webkit-user-select: none; + user-select: none; +} + +label input[role="switch"] { + opacity: 0; +} + +label input[role="switch"] ~ .state { + display: inline-block; + -webkit-user-select: none; + user-select: none; +} + +label input[role="switch"] ~ .state > .container { + position: relative; + top: 2px; + display: inline-block; + border: 2px solid black; + width: 40px; + height: 20px; + border-radius: 11px; +} + +label input[role="switch"] ~ .state > .container > .position { + position: relative; + top: 1px; + left: 2px; + display: inline-block; + border: 2px solid black; + border-radius: 9px; + width: 14px; + height: 14px; + background: black; + opacity: 0.6; +} + +label input[role="switch"]:not(:checked) ~ .state span.on { + display: none; +} + +label input[role="switch"]:checked ~ .state > span.off { + display: none; +} + +label input[role="switch"]:checked ~ .state > .container > .position { + left: 20px; + border-color: green; + background: green; + opacity: 1; +} + +label.focus, +label:hover { + padding: 2px 2px 4px 4px; + border-width: 2px; + outline: none; + background-color: #def; + cursor: pointer; +} + +label.focus span.container, +label:hover span.container { + background-color: white; +} diff --git a/examples/switch/js/switch-checkbox.js b/examples/switch/js/switch-checkbox.js new file mode 100644 index 0000000000..7c6a88b0b1 --- /dev/null +++ b/examples/switch/js/switch-checkbox.js @@ -0,0 +1,34 @@ +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: switch-checkbox.js + * + * Desc: Switch widget using input[type=checkbox] that implements ARIA Authoring Practices + */ + +'use strict'; + +class CheckboxSwitch { + constructor(domNode) { + this.switchNode = domNode; + this.switchNode.addEventListener('focus', () => this.onFocus(event)); + this.switchNode.addEventListener('blur', () => this.onBlur(event)); + } + + onFocus(event) { + event.currentTarget.parentNode.classList.add('focus'); + } + + onBlur(event) { + event.currentTarget.parentNode.classList.remove('focus'); + } +} + +// Initialize switches +window.addEventListener('load', function () { + // Initialize the Switch component on all matching DOM nodes + Array.from( + document.querySelectorAll('input[type=checkbox][role^=switch]') + ).forEach((element) => new CheckboxSwitch(element)); +}); diff --git a/examples/switch/switch-button.html b/examples/switch/switch-button.html index 6462223906..101d60752f 100644 --- a/examples/switch/switch-button.html +++ b/examples/switch/switch-button.html @@ -33,7 +33,7 @@

    Switch Example Using HTML Button

    Similar examples include:

    diff --git a/examples/switch/switch-checkbox.html b/examples/switch/switch-checkbox.html new file mode 100644 index 0000000000..1bdf259824 --- /dev/null +++ b/examples/switch/switch-checkbox.html @@ -0,0 +1,208 @@ + + + + + Switch Example Using HTML Checkbox Input | WAI-ARIA Authoring Practices 1.2 + + + + + + + + + + + + + + +
    +

    Switch Example Using HTML Checkbox Input

    +

    + This example illustrates implementing the Switch design pattern with an HTML input[type="checkbox"] as the switch element and using CSS borders to provide graphical rendering of switch states. + It also demonstrates using the HTML fieldset and legend elements to present multiple switches in a labeled group. +

    +

    Similar examples include:

    + + +
    +
    +

    Example

    +
    + +
    +
    + Accessibility Preferences + + + +
    +
    + +
    + +
    +

    Accessibility Features

    +
      +
    • + To help assistive technology users understand that each switch belongs to a set of switches related to Accessibility Preferences, the switches are wrapped in a fieldset element labeled with a legend element. +
    • +
    • + To make understanding the state of the switch easier for users with visual or cognitive disabilities, a text equivalent of the state (on or off) is displayed adjacent to the graphical state indicator. + CSS attribute selectors ensure the label displayed is synchronized with the value of the input.
      + NOTE: To prevent redundant announcement of the state by screen readers, the text indicators of state are hidden from assistive technologies with aria-hidden. +
    • +
    • Spacing, border widths and fill are important to ensure the graphical states are visible and discernible to people with visual impairments, including when browser or operating system high contrast settings are enabled: +
        +
      • To make the graphical representation of the state of a switch readily perceivable, two pixel borders are used for the switch state container and a solid color is used for the fill of the circles indicating the on and off states.
      • +
      • To ensure users can perceive the difference between the container and the circles used to indicate the state of the switch, there are two pixels of space between the container border and the circles.
      • +
      +
    • +
    • To enhance perceivability when operating the switches, visual keyboard focus and hover are styled using the CSS :hover pseudo-class and onfocus and onblur event handlers: +
        +
      • To make it easier to perceive focus and the relationship between a label and its associated switch, focus creates a border around both the switch and the label and also changes the background color.
      • +
      • To make it easier to perceive that clicking either the label or switch will activate the switch, the hover indicator is the same as the focus indicator.
      • +
      • To help people with visual impairments identify the switch as an interactive element, the cursor is changed to a pointer when hovering over the switch.
      • +
      • + Note: Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements. + Instead of using transparency, the focused element has a thicker border and less padding. + When an element receives focus, its border changes from zero to two pixels and padding is reduced by two pixels. + When an element loses focus, its border changes from two pixels to two and padding is increased by two pixels. +
      • +
      +
    • +
    +
    + +
    +

    Keyboard Support

    + + + + + + + + + + + + + + + + + +
    KeyFunction
    TabMoves keyboard focus to the switch.
    SpaceToggles the state of the switch between on and off.
    +
    + +
    +

    Role, Property, State, and Tabindex Attributes

    + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    switchinput[type="checkbox"] +
      +
    • Identifies the element as an ARIA switch.
    • +
    • The accessible name is defined using the label element.
    • +
    +
    aria-hidden="true"span.on and span.off +
      +
    • Removes the strings on and off that appear to the right of the switch from the accessible name of the switch.
    • +
    • These strings are included only for enhancing visual comprehension of the state; element states are not permitted in accessible names.
    • +
    +
    +
    + +
    +

    Javascript and CSS Source Code

    + +
    + +
    +

    HTML Source Code

    + +
    + + + +
    +
    + + + diff --git a/examples/switch/switch.html b/examples/switch/switch.html index 7d8eecfacd..96b5ad9185 100644 --- a/examples/switch/switch.html +++ b/examples/switch/switch.html @@ -33,7 +33,7 @@

    Switch Example

    Similar examples include:

    diff --git a/test/tests/switch_switch-checkbox.js b/test/tests/switch_switch-checkbox.js new file mode 100644 index 0000000000..1fd0dd3f93 --- /dev/null +++ b/test/tests/switch_switch-checkbox.js @@ -0,0 +1,68 @@ +const { ariaTest } = require('..'); +const { By, Key } = require('selenium-webdriver'); +const assertAriaRoles = require('../util/assertAriaRoles'); +const assertAttributeValues = require('../util/assertAttributeValues'); +const assertTabOrder = require('../util/assertTabOrder'); +const assertInputChecked = require('../util/assertInputChecked'); + +const exampleFile = 'switch/switch-checkbox.html'; + +const ex = { + switchSelector: '#ex1 [role="switch"]', + spanOnSelector: '#ex1 span.on', + spanOffSelector: '#ex1 span.off', + switches: [ + '#ex1 fieldset label:nth-child(2) input', + '#ex1 fieldset label:nth-child(3) input', + ], +}; + +// Attributes +ariaTest( + 'role="switch" elements exist', + exampleFile, + 'switch-role', + async (t) => { + await assertAriaRoles(t, 'ex1', 'switch', '2', 'input'); + } +); + +ariaTest( + '"aria-hidden" set to "true" on SPAN elements containing "on" and "off" ', + exampleFile, + 'aria-hidden', + async (t) => { + await assertAttributeValues(t, ex.spanOnSelector, 'aria-hidden', 'true'); + await assertAttributeValues(t, ex.spanOffSelector, 'aria-hidden', 'true'); + } +); + +ariaTest( + 'key TAB moves focus between switches', + exampleFile, + 'key-tab', + async (t) => { + await assertTabOrder(t, ex.switches); + } +); + +ariaTest( + 'key SPACE turns switch on and off', + exampleFile, + 'key-space', + async (t) => { + for (let switchSelector of ex.switches) { + // Send SPACE key to check box to select + await t.context.session + .findElement(By.css(switchSelector)) + .sendKeys(Key.SPACE); + await assertInputChecked(t, switchSelector, true); + + // Send SPACE key to check box to unselect + await t.context.session + .findElement(By.css(switchSelector)) + .sendKeys(Key.SPACE); + await assertInputChecked(t, switchSelector, false); + } + } +); diff --git a/test/util/assertInputChecked.js b/test/util/assertInputChecked.js new file mode 100644 index 0000000000..e1520fe199 --- /dev/null +++ b/test/util/assertInputChecked.js @@ -0,0 +1,26 @@ +const assert = require('assert'); + +/** + * Confirm the input element checked state. + * + * @param {object} t - ava execution object + * @param {string} elementSelector - the input element + */ + +module.exports = async function assertInputChecked(t, elementSelector, value) { + const inputChecked = await t.context.session.executeScript( + async function () { + const selector = arguments[0]; + let el = document.querySelector(selector); + return el.checked === arguments[1]; + }, + elementSelector, + value + ); + + assert.ok( + inputChecked, + 'input[type=checked] at ' + elementSelector + ' should have value: ' + value + ); + t.pass(); +};