No keyboard shortcut uses only printable characters
Applicability
This rule applies to any [keyboard event][] for which all of the following is true:
the event's attribute key is a [printable character][] key; and
the event's method getModifierState returns false for each of the [valid modifier keys][]; and
the event causes [changes in the content][changes in content] of the [HTML document][].
Expectation
For each test target at least one of the following is true:
disable/remap: there is at least one [set of clearly labeled instruments][] to [block events][blocked event] that use the [same key][same key events] as the test target and whose getModifierState method returns false for each of the [valid modifier keys][]; or
focus: the [event target][] is an [inheriting semantic][] widget.
Assumptions
If there are ways to disable the result of [keyboard events][keyboard event] that do not require the user to interact with the web page (e.g. a setting at the operating system level), failing this rule might not be a failure of the success criterion.
After being disabled, the event remains disabled until being re-enabled again. If the event is re-enabled through other non-user controlled means (e.g. a timeout) then this rule may pass while [Success Criterion 2.1.4: Character Key Shortcuts][sc2.1.4] is not satisfied.
Accessibility Support
Currently [keyboard events][keyboard event] only support the types keydown and keyup. [Keyboard events][keyboard event] of type keypressed are considered [legacy keyboard events][] and are thus ignored by this rule.
Background
The [instruments][instrument] used to pass this rule (if any), must meet all level A Success Criteria in order to fully satisfy [Success Criterion 2.1.4: Character Key Shortcuts][sc2.1.4]. These extra requirements are left out of this rule, and should be tested separately.
This rule allows [changes to the content][changes in content] when a user interface component has focus to meet the "Active only on focus" requirement from [Success Criterion 2.1.4][sc2.1.4]. As explained in the Event dispatch and DOM event flow section of the UI Events Working Draft, each [keyboard event][] is dispatched to an [event target][]. The [event target][] is the element that has focus. As such, the "Active only on focus" requirement from [Success Criterion 2.1.4][sc2.1.4] is implied by saying that the [event target][] is an [inheriting semantic][] widget.
The "Turn off" and "Remap" requirements from [Success Criterion 2.1.4][sc2.1.4] are combined in the disable/remap item of the Expectation section. For the disable requirement, [changes in content][] that are made through [keyboard events][keyboard event] with a [printable character][] value for the key attribute and a getModifierState return value of false for each of the [valid modifier keys][] effectively need to be [blocked][blocked event] (in other words, turned off or disabled). The remap requirement unblocks the events if the getModifierState query returns true for at least one of the [valid modifier keys][]. Once the getModifierState returns true for at least one of the [valid modifier keys][] of a [keyboard event][], such [keyboard event][] is no longer applicable for the rule and it passes the "Remap" requirement from [Success Criterion 2.1.4][sc2.1.4].
Bibliography
[Understanding Success Criterion 2.1.4: Character Key Shortcuts][sc2.1.4]
This [HTML document][] is listening to [keyboard events][keyboard event] for which the attribute key is a [printable character][] and the method getModifierState returns false, and which cause [changes in content][]. There exists an [instrument][] to disable the [keyboard event][] so that [same key][same key events] events are [blocked][blocked event] unless getModifierState("Control") returns true, therefore meeting the disable/remap expectation.
<html>
<head>
<title>Passed Example 1</title>
<script src="/test-assets/ffbc54/shortcut.js"></script>
</head>
<body onload="registerShortcut({id: 'singleShortcut', shortcutKey: '+'}); activateShortcuts();">
<label for="target">Add to list (press "+" to add):</label>
<input type="text" id="target" />
<div>
<div>Remap shortcut</div>
<div>
<label>
<input id="remap" type="checkbox" onclick="toggleModifier('singleShortcut', this.checked)" />
Use "ctrl" key together with the "+" key
</label>
</div>
</div>
<br />
<div>
To do list
</div>
<ul id="list"></ul>
</body>
</html>
Passed Example 2
This [HTML document][] is listening to [keyboard events][keyboard event] for which the attribute key is a [printable character][] and the method getModifierState returns false, and which cause [changes in content][]. There exists an [instrument][] to disable the [keyboard event][]. A disabled event implies that the event is disabled when the getModifierState method returns false.
<html>
<head>
<title>Passed Example 2</title>
<script src="/test-assets/ffbc54/shortcut.js"></script>
</head>
<body onload="registerShortcut({id: 'singleShortcut', shortcutKey: '+'}); activateShortcuts();">
<label for="target">Add to list (press "+" to add):</label>
<input type="text" id="target" />
<label>
<input type="checkbox" onclick="toggleDisabled('singleShortcut', !this.checked)" checked />
Toggle single character keyboard shortcut
</label>
<br />
<div>
To do list
</div>
<ul id="list"></ul>
</body>
</html>
Passed Example 3
This [HTML document][] is listening to [keyboard events][keyboard event] for which the attribute key is a [printable character][] and the method getModifierState returns false, and which cause [changes in content][]. For each [keyboard event][] causing [changes in content][], there exists an [instrument][] to disable it so that [same key][same key events] events are [blocked][blocked event] unless getModifierState("Control") returns true, therefore meeting the disable/remap expectation.
<html>
<head>
<title>Passed Example 3</title>
<script src="/test-assets/ffbc54/shortcut.js"></script>
</head>
<body
onload="registerShortcut({id: 'firstShortcut', shortcutKey: '+'}); registerShortcut({id: 'secondShortcut', shortcutKey: 'a'}); activateShortcuts();"
>
<label for="target">Add to list (press "+" or "a" to add):</label>
<input type="text" id="target" />
<div>
<div>Remap shortcut</div>
<div>
<label>
<input id="remap1" type="checkbox" onclick="toggleModifier('firstShortcut', this.checked)" />
Use "ctrl" key together with the "+" key
</label>
<label>
<input id="remap2" type="checkbox" onclick="toggleModifier('secondShortcut', this.checked)" />
Use "ctrl" key together with the "a" key
</label>
</div>
</div>
<br />
<div>
To do list
</div>
<ul id="list"></ul>
</body>
</html>
Passed Example 4
This [HTML document][] is listening to [keyboard events][keyboard event] for which the attribute key is a [printable character][] and the method getModifierState returns false, and which cause [changes in content][]. There exists an [instrument][] to disable those [keyboard events][keyboard event] that cause [changes in content][] so that [same key][same key events] events are [blocked][blocked event] unless getModifierState("Control") returns true. In this example, the same [instrument][] is used to remap all [keyboard events][keyboard event].
<html>
<head>
<title>Passed Example 4</title>
<script src="/test-assets/ffbc54/shortcut.js"></script>
</head>
<body
onload="registerShortcut({id: 'firstShortcut', shortcutKey: '+'}); registerShortcut({id: 'secondShortcut', shortcutKey: 'a'}); activateShortcuts();"
>
<label for="target">Add to list (press "+" or "a" to add):</label>
<input type="text" id="target" />
<div>
<div>Remap shortcut</div>
<div>
<label>
<input
id="remap"
type="checkbox"
onclick="toggleModifier('firstShortcut', this.checked); toggleModifier('secondShortcut', this.checked);"
/>
Use "ctrl" key together with the "+" or "a" key
</label>
</div>
</div>
<br />
<div>
To do list
</div>
<ul id="list"></ul>
</body>
</html>
Passed Example 5
This [HTML document][] is listening to [keyboard events][keyboard event] for which the attribute key is a [printable character][] and the method getModifierState returns false, and which cause [changes in content][], but the [events are blocked][blocked event] when no widget has [focus][].
<html>
<head>
<title>Passed Example 5</title>
<script src="/test-assets/ffbc54/shortcut.js"></script>
</head>
<body onload="registerShortcut({shortcutKey: '+', focusOnly: true}); activateShortcuts();">
<label for="target">Add to list (press "+" to add):</label>
<input type="text" id="target" />
<br />
<div>
To do list
</div>
<ul id="list"></ul>
</body>
</html>
Passed Example 6
This [HTML document][] is listening to [keyboard events][keyboard event] for which the attribute key is a [printable character][] and the method getModifierState returns false, and which cause [changes in content][]. There exists a [set of clearly labeled instruments][] to disable or to remap the [keyboard event][] so that [same key][same key events] events are [blocked][blocked event] unless getModifierState("Control") returns true.
<html>
<head>
<title>Passed Example 6</title>
<link rel="stylesheet" type="text/css" href="/test-assets/ffbc54/styles.css" />
<script src="/test-assets/ffbc54/shortcut.js"></script>
</head>
<body onload="registerShortcut({id: 'singleShortcut', shortcutKey: '+'}); activateShortcuts();">
<div id="overlay">
<p>Disable/remap shortcut</p>
<label>
<input type="checkbox" onclick="toggleDisabled('singleShortcut', !this.checked)" checked />
Toggle single character keyboard shortcut
</label>
<br />
<label>
<input id="remap" type="checkbox" onclick="toggleModifier('singleShortcut', this.checked)" />
Use "ctrl" key together with the "+" key
</label>
<br />
<button onclick="closeModal();">Dismiss</button>
</div>
<p>To control the shortcuts activate the "Control shortcuts" button.</p>
<input type="button" onclick="openModal()" value="Control shortcuts" />
<label for="target">Add to list (press "+" to add):</label>
<input type="text" id="target" />
<div>
To do list
</div>
<ul id="list"></ul>
</body>
</html>
Failed
Failed Example 1
This [HTML document][] is listening to [keyboard events][keyboard event] for which the attribute key is a [printable character][] and the method getModifierState returns false, and which cause [changes in content][]. There is no [instrument][] to disable or remap the [keyboard event][] and the [keyboard events][keyboard event] are not [blocked][blocked event] when no widget has [focus][].
<html>
<head>
<title>Failed Example 1</title>
<script src="/test-assets/ffbc54/shortcut.js"></script>
</head>
<body onload="registerShortcut({shortcutKey: '+', disabled: false}); activateShortcuts();">
<label for="target">Add to list (press "+" to add):</label>
<input type="text" id="target" />
<br />
<div>
To do list
</div>
<ul id="list"></ul>
</body>
</html>
Failed Example 2
This [HTML document][] is listening to [keyboard events][keyboard event] for which the attribute key is a [printable character][] and the method getModifierState returns false, and which cause [changes in content][]. There is an [instrument][] to disable or remap the [keyboard event][] so that [same key][same key events] events are [blocked][blocked event] unless getModifierState("Control") returns true, but the [instrument][] is not in a [clearly labeled location][].
<html>
<head>
<title>Failed Example 2</title>
<link rel="stylesheet" type="text/css" href="/test-assets/ffbc54/styles.css" />
<script src="/test-assets/ffbc54/shortcut.js"></script>
</head>
<body onload="registerShortcut({id: 'singleShortcut', shortcutKey: '+'}); activateShortcuts();">
<div id="overlay">
<p>Disable/remap shortcut</p>
<label>
<input type="checkbox" onclick="toggleDisabled('singleShortcut', !this.checked)" checked />
Toggle single character keyboard shortcut
</label>
<br />
<label>
<input id="remap" type="checkbox" onclick="toggleModifier('singleShortcut', this.checked)" />
Use "ctrl" key together with the "+" key
</label>
<br />
<button onclick="closeModal();">Dismiss</button>
</div>
<input type="button" onclick="openModal()" value="Open modal" />
<label for="target">Add to list (press "+" to add):</label>
<input type="text" id="target" />
<div>
To do list
</div>
<ul id="list"></ul>
</body>
</html>
Inapplicable
Inapplicable Example 1
This [HTML document][] has a [keyboard event][] [dispatched][] to an [event target][] but it only causes [changes in content][] if the event's attribute key is not a [printable character][] (in this example, the Escape key).
<html>
<head>
<title>Inapplicable Example 1</title>
<script src="/test-assets/ffbc54/shortcut.js"></script>
</head>
<body onload="registerShortcut({shortcutKey: 'Escape'}); activateShortcuts();">
<label for="target">Add to list (press "esc" to add):</label>
<input type="text" id="target" />
<br />
<div>
To do list
</div>
<ul id="list"></ul>
</body>
</html>
Inapplicable Example 2
This [HTML document][] has a [keyboard event][] [dispatched][] to an [event target][] with the attribute key being a [printable character][] but it does not cause [changes in content][] unless the getModifierState returns true.
<html>
<head>
<title>Inapplicable Example 2</title>
<script src="/test-assets/ffbc54/shortcut.js"></script>
</head>
<body onload="registerShortcut({shortcutKey: '+', ctrlKey: true}); activateShortcuts();">
<label for="target">Add to list (press "ctrl" and "+" to add):</label>
<input type="text" id="target" />
<br />
<div>
To do list
</div>
<ul id="list"></ul>
</body>
</html>