Skip to content

Instantly share code, notes, and snippets.

@zizoclimbs
Created June 4, 2020 16:20
Show Gist options
  • Save zizoclimbs/5dc1a976f7d642dc1d409bb2de145399 to your computer and use it in GitHub Desktop.
Save zizoclimbs/5dc1a976f7d642dc1d409bb2de145399 to your computer and use it in GitHub Desktop.
Roving Focus
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Accessibility Solutions - Focus - Roving Focus</title>
<link rel="stylesheet" href="presentation.css" />
<link rel="stylesheet" href="roving-focus.css" />
</head>
<body>
<h3 id="drink-options">Drink Options</h3>
<ul
id="group1"
class="radiogroup"
role="radiogroup"
aria-labelledby="drink-options"
>
<li tabindex="0" class="radio" role="radio" checked>Water</li>
<li tabindex="-1" class="radio" role="radio">Tea</li>
<li tabindex="-1" class="radio" role="radio">Coffee</li>
<li tabindex="-1" class="radio" role="radio">Cola</li>
<li tabindex="-1" class="radio" role="radio">Ginger Ale</li>
</ul>
<script src="roving-focus.js"></script>
</body>
</html>
body {
color: #101e31;
font-family: Helvetica, Arial, sans-serif;
font-style: normal;
height: 100%;
margin: 0;
padding: 20px;
}
a,
a:visited {
color: #dc3542;
font-weight: bold;
transition: all 0.3s ease;
}
a:hover,
a:focus {
color: #d21a28;
}
blockquote {
border-left: 8px solid #d21a28;
font-size: 1.1rem;
margin-left: auto;
margin-right: auto;
padding: 20px;
position: relative;
}
button,
input,
.btn {
border: 1px solid #333;
border-radius: 5px;
color: #333;
font-size: 16px;
font-weight: normal;
padding: 8px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #1d3557;
font-family: Georgia, Times, serif;
}
hr {
border: 0;
border-top: 3px solid #d21a28;
margin: -3px 0 0 0;
}
/* ==========================================================================
Vertical Rhythm & Modular Scale
========================================================================== */
blockquote {
margin: 20px 0;
}
dl,
p,
ul,
ol,
pre,
table {
margin-bottom: 20px;
}
dl dd,
dl dt,
ul ul,
ol ol,
ul ol,
ol ul {
margin-bottom: 0;
}
a,
b,
i,
strong,
em,
small,
code {
line-height: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 30px 0 15px 0;
}
aside,
p {
line-height: 1.3;
}
body > h1,
body > .hero {
margin-top: 0;
}
main p:first-child,
.main p:first-child {
margin-top: 0;
}
nav ul {
padding-left: 0;
}
sub,
sup {
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* ==========================================================================
Shared styling
========================================================================== */
.sr-only {
border: 0;
clip: rect(0, 0, 0, 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.sr-only-focusable:active,
.sr-only-focusable:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
width: auto;
}
/* ==========================================================================
Presentation Helpers
========================================================================== */
.super-long-space {
height: 1500px;
}
.radiogroup {
list-style: none;
}
.radio {
position: relative;
}
.radio::before {
content: '';
display: block;
width: 10px;
height: 10px;
border: 1px solid black;
position: absolute;
left: -18px;
top: 3px;
border-radius: 50%;
}
.radio[checked]::after {
content: '';
display: block;
width: 8px;
height: 8px;
background: red;
position: absolute;
left: -16px;
top: 5px;
border-radius: 50%;
}
(function() {
'use strict';
// Define values for keycodes
var VK_LEFT = 37;
var VK_UP = 38;
var VK_RIGHT = 39;
var VK_DOWN = 40;
// Helper function to convert NodeLists to Arrays
function slice(nodes) {
return Array.prototype.slice.call(nodes);
}
function RadioGroup(id) {
this.el = document.querySelector(id);
this.buttons = slice(this.el.querySelectorAll('.radio'));
this.selected = 0;
this.focusedButton = this.buttons[this.selected];
this.el.addEventListener('keydown', this.handleKeyDown.bind(this));
this.el.addEventListener('click', this.handleClick.bind(this));
}
RadioGroup.prototype.handleKeyDown = function(e) {
switch(e.keyCode) {
case VK_UP:
case VK_LEFT: {
e.preventDefault();
if (this.selected === 0) {
this.selected = this.buttons.length - 1;
} else {
this.selected--;
}
break;
}
case VK_DOWN:
case VK_RIGHT: {
e.preventDefault();
if (this.selected === this.buttons.length - 1) {
this.selected = 0;
} else {
this.selected++;
}
break;
}
}
this.changeFocus(this.selected);
};
RadioGroup.prototype.handleClick = function(e) {
var children = e.target.parentNode.children
for (var i = 0; i < children.length; i++) {
if (e.target == children[i]) break;
}
this.selected = i;
this.changeFocus(this.selected);
}
RadioGroup.prototype.changeFocus = function(idx) {
// Set the old button to tabindex -1
this.focusedButton.tabIndex = -1;
this.focusedButton.removeAttribute('checked');
// Set the new button to tabindex 0 and focus it
this.focusedButton = this.buttons[idx];
this.focusedButton.tabIndex = 0;
this.focusedButton.focus();
this.focusedButton.setAttribute('checked', 'checked');
};
var group1 = new RadioGroup('#group1');
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment