Skip to content

Instantly share code, notes, and snippets.

@rodneyrehm
Last active August 29, 2015 14:07
Show Gist options
  • Save rodneyrehm/4ea45b9c5b81ec8b9b69 to your computer and use it in GitHub Desktop.
Save rodneyrehm/4ea45b9c5b81ec8b9b69 to your computer and use it in GitHub Desktop.
A11Y: questions about focus

Focus!

let's focus on tabindex and :focus for a bit…

What is focus?

  • focus (and blur) event emitted on element
  • document.activeElement updated
  • :focus style applied
  • browser's default focus outline visible (not testable programatically, only visually?!)

When is focus?

  • touchstart, touchend
  • mousedown, mouseup, click
  • keydown, keyup, keypress (tab: keyCode: 9)
  • forced style (elem.offsetTop) necessary?

Questions

  • operating system?!

  • activating focus for <span>, <span tabindex="-1">, <span tabindex="0">

  • activating focus for <span>, <span tabindex="-1">, <span tabindex="0"> with :focus

  • activating focus for <span>, with :focus

  • activating focus for <div><a>, <div tabindex="-1"><a>, <div tabindex="-1"><a tabindex="0">


  • is focus preventable? (matrix of when and what)
  • what is focused on clicking <a href="#exists"> vs. <a href="#unknown">

Dumb Questions

  • how does AT (e.g. iOS8 AssistiveTouch and or VoiceOver) affect all of the above?

  • what are -webkit-focus-ring-color and -moz-focusring about?

  • any side-effects from

    • :active
    • pointer-events
    • -webkit-tap-highlight-color (note)
    • -webkit-touch-callout
    • -webkit-appearance
    • user-select (-webkit-, -moz-)
  • color constants (currentcolor, -webkit-activelink, -webkit-focus-ring-color, -webkit-link, -webkit-text)


Resources

Test Cases

Specifications

Various

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>focus-bla</title>
<style>
span {
display: block;
background: #DDD;
}
div {
display: block;
background: #EEE;
}
span + span {
margin-top: 10px;
}
.focus:focus {
background: hotpink;
}
</style>
</head>
<body>
<h3>non-interactive Container</h3>
<div>
<span data-label="span" data-focusable="false" data-tabbable="false">span</span>
<span data-label="span:focus" data-focusable="false" data-tabbable="false" class="focus">span:focus</span>
<span data-label="span[tabindex=-1]" data-focusable="true" data-tabbable="false" tabindex="-1">span[tabindex=-1]</span>
<span data-label="span[tabindex=-1]:focus" data-focusable="true" data-tabbable="false" tabindex="-1" class="focus">span[tabindex=-1]:focus</span>
<span data-label="span[tabindex=0]" data-focusable="true" data-tabbable="true" tabindex="0">span[tabindex=0]</span>
<span data-label="span[tabindex=0]:focus" data-focusable="true" data-tabbable="true" tabindex="0" class="focus">span[tabindex=0]:focus</span>
</div>
<h3>focusable Container</h3>
<div data-label="div[tabindex=-1]" data-focusable="true" data-tabbable="false" tabindex="-1">
<span data-label="div[tabindex=-1] span" data-focusable="false" data-tabbable="false">span</span>
<span data-label="div[tabindex=-1] span:focus" data-focusable="false" data-tabbable="false" class="focus">span:focus</span>
<span data-label="div[tabindex=-1] span[tabindex=-1]" data-focusable="true" data-tabbable="false" tabindex="-1">span[tabindex=-1]</span>
<span data-label="div[tabindex=-1] span[tabindex=-1]:focus" data-focusable="true" data-tabbable="false" tabindex="-1" class="focus">span[tabindex=-1]:focus</span>
<span data-label="div[tabindex=-1] span[tabindex=0]" data-focusable="true" data-tabbable="true" tabindex="0">span[tabindex=0]</span>
<span data-label="div[tabindex=-1] span[tabindex=0]:focus" data-focusable="true" data-tabbable="true" tabindex="0" class="focus">span[tabindex=0]:focus</span>
</div>
<h3>focusable Container with :focus style</h3>
<div data-label="div[tabindex=-1]:focus" data-focusable="true" data-tabbable="false" tabindex="-1" class="focus">
<span data-label="div[tabindex=-1]:focus span" data-focusable="false" data-tabbable="false">span</span>
<span data-label="div[tabindex=-1]:focus span:focus" data-focusable="false" data-tabbable="false" class="focus">span:focus</span>
<span data-label="div[tabindex=-1]:focus span[tabindex=-1]" data-focusable="true" data-tabbable="false" tabindex="-1">span[tabindex=-1]</span>
<span data-label="div[tabindex=-1]:focus span[tabindex=-1]:focus" data-focusable="true" data-tabbable="false" tabindex="-1" class="focus">span[tabindex=-1]:focus</span>
<span data-label="div[tabindex=-1]:focus span[tabindex=0]" data-focusable="true" data-tabbable="true" tabindex="0">span[tabindex=0]</span>
<span data-label="div[tabindex=-1]:focus span[tabindex=0]:focus" data-focusable="true" data-tabbable="true" tabindex="0" class="focus">span[tabindex=0]:focus</span>
</div>
<h3>tabbable Container</h3>
<div data-label="div[tabindex=0]" data-focusable="true" data-tabbable="true" tabindex="0">
<span data-label="div[tabindex=0] span" data-focusable="false" data-tabbable="false">span</span>
<span data-label="div[tabindex=0] span:focus" data-focusable="false" data-tabbable="false" class="focus">span:focus</span>
<span data-label="div[tabindex=0] span[tabindex=-1]" data-focusable="true" data-tabbable="false" tabindex="-1">span[tabindex=-1]</span>
<span data-label="div[tabindex=0] span[tabindex=-1]:focus" data-focusable="true" data-tabbable="false" tabindex="-1" class="focus">span[tabindex=-1]:focus</span>
<span data-label="div[tabindex=0] span[tabindex=0]" data-focusable="true" data-tabbable="true" tabindex="0">span[tabindex=0]</span>
<span data-label="div[tabindex=0] span[tabindex=0]:focus" data-focusable="true" data-tabbable="true" tabindex="0" class="focus">span[tabindex=0]:focus</span>
</div>
<h3>tabbable Container with :focus style</h3>
<div data-label="div[tabindex=0]" data-focusable="true" data-tabbable="true" tabindex="0" class="focus">
<span data-label="div[tabindex=0] span" data-focusable="false" data-tabbable="false">span</span>
<span data-label="div[tabindex=0] span:focus" data-focusable="false" data-tabbable="false" class="focus">span:focus</span>
<span data-label="div[tabindex=0] span[tabindex=-1]" data-focusable="true" data-tabbable="false" tabindex="-1">span[tabindex=-1]</span>
<span data-label="div[tabindex=0] span[tabindex=-1]:focus" data-focusable="true" data-tabbable="false" tabindex="-1" class="focus">span[tabindex=-1]:focus</span>
<span data-label="div[tabindex=0] span[tabindex=0]" data-focusable="true" data-tabbable="true" tabindex="0">span[tabindex=0]</span>
<span data-label="div[tabindex=0] span[tabindex=0]:focus" data-focusable="true" data-tabbable="true" tabindex="0" class="focus">span[tabindex=0]:focus</span>
</div>
<script src="prevent-pointer-focus.js"></script>
<script>(function() {
window.preventPointerFocus(document);
})();</script>
</body>
</html>
(function() {
'use strict';
function getElementPath(node) {
var path = [];
while (node) {
path.push(node);
node = node.parentElement;
}
return path;
}
function preventFocus(context, entryEvent, exitEvent) {
// remove [tabindex="-1"] from the element that is about to be clicked
// so the element (and its parents) cannot be given focus after the
// entryEvent (mousedown, touchstart) is processed. Restore tabindex
// attributes (attributes, not properties!) on exitEvent
// (mouseup, touchstart) so programmatic focus remains possible
function hideTabindex(event) {
// remember the elements we hacked
var revert = [];
// obtain the DOM hierarchy path of the element about to be clicked on,
// clean the negative tabindexes of every parental element as well, as
// the click would otherwise simply focus a parent element
getElementPath(event.target).forEach(function(node) {
// node.tabIndex === -1 even if the tabindex attribute was not set
// where getAttribute('tabindex') === null if it is not present
var tabindex = node.getAttribute('tabindex');
if (!tabindex || parseInt(tabindex, 10) >= 0) {
// [tabindex="0"] can be tabbed to, so we'll allow focus by mouse/touch
return;
}
// hide the tabindex from the browser, this is where we disengage browser's
node.setAttribute('data-disabled-tabindex', tabindex);
node.removeAttribute('tabindex');
revert.push(node);
});
// if we didn't do anything, there's no need to wait for the exitEvent
if (!revert.length) {
return;
}
function restoreTabindex() {
while (revert.length) {
var node = revert.pop();
// restore the tabindex we previously hid from the browser
var tabindex = node.getAttribute('data-disabled-tabindex');
node.setAttribute('tabindex', tabindex);
node.removeAttribute('data-disabled-tabindex');
}
// we only needed to listen to this event once, so kill it.
document.removeEventListener(exitEvent, restoreTabindex, false);
}
// engage pointer-focus hack undo
document.addEventListener(exitEvent, restoreTabindex, false);
}
// engage pointer-focus hack
context.addEventListener(entryEvent, hideTabindex, true);
// return callback to disengage pointer-focus hack
return function allowFocus() {
context.removeEventListener(entryEvent, hideTabindex, true);
};
}
// export convenience wrapper to engage pointer-focus prevention
window.preventPointerFocus = function(context) {
var allowMouse = preventFocus(context || document, 'mousedown', 'mouseup');
var allowTouch = preventFocus(context || document, 'touchstart', 'touchend');
// return callback to disengage the pointer-focus prevention
return function allowPointerFocus() {
allowMouse();
allowTouch();
};
};
})();
@patrickhlauke
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment