1. Tunisia
  2. Botswana
  3. South Africa
  4. Kenya
  5. Nigeria
  6. Tanzania

As a general rule, hiding important information inside a scrolling element is a poor idea, one more frequently used by designers because it “looks cool” rather than for solid UI reasons. But there are occasions when it’s necessary to do so… and when you do, the links inside still need to be via the keyboard.

The Basic Markup & CSS

Most any element can be set to overflow: scroll, so we can concentrate on the semantics of the markup. For this example, I’ll take locations in Africa:

<ol id="scrolling-list">
	<li><a href=#><img src="tunisia.jpg" alt>Tunisia</a>
	<li><a href=#><img src="botswana.jpg" alt>Botswana</a>
	<li><a href=#><img src="south-africa.jpg" alt>South Africa</a>
	<li><a href=#><img src="kenya.jpg" alt>Kenya</a>
	<li><a href=#><img src="nigeria.jpg" alt>Nigeria</a>
	<li><a href=#><img src="tanzania.jpg" alt>Tanzania</a>
</ol>

The CSS we will use presents the list inside a set height. By itself, height does nothing, as the general rule is that content always displays, even if it means breaking boundaries. If we want to counteract this behaviour, we must take explicit steps to do so: in this case, by using overflow-y: scroll to cause the list to scroll vertically. Note the use of currentcolor and other shortcuts to make development easier.

ol#scrolling-list {
	font-size: 0;
	background: #333;
	list-style-type: none;
	padding-left: 0;
	height: 230px;
	overflow-y: scroll;
	font-weight: 100;
	color: #999;
}
ol#scrolling-list li { 
	border-bottom: 1px dashed; 
}
ol#scrolling-list li a {
	font-size: 1.2rem;
	text-decoration: none;
	line-height: 2;
	color: currentcolor;
	display: block;
	transition: .4s background;
}
ol#scrolling-list li a img {
	width: 20%;
	vertical-align: top;
	margin-right: .5rem;
}

Customisation of the scroll bar is limited: at one time Internet Explorer and Firefox used CSS selectors to do so, but dropped them. Customisation is still possible in Webkit / Blink, with a series of vendor-prefixed “Shadow DOM” pseudo-element selectors, similar to those used to customize the range input:

ol#scrolling-list::-webkit-scrollbar { 
	background: #000; 
}
ol#scrolling-list::-webkit-scrollbar-thumb {
	background-color: hsl(33,100%,50%); 
}

Next, we’ll customize the :hover state for the links, being sure to add an equivalent :focus state via a group combinator to cover access of the same links via the keyboard:

ol#scrolling-list li a:hover,
ol#scrolling-list li a:focus { 
	background: #111; 
}

Improving Accessibility With JavaScript

Assuming you have full keyboard navigation in your browser (Chrome is probably the easiest to test in, as the others require a little setting up to do so) you’ll find that pressing the TAB key will advance you through the links in the list. Once you’re in the list, you can go forwards with TAB or backwards with SHIFT+TAB: the cursor keys control the overflow slider on the right of the window. There’s nothing wrong with this UI model, but in some cases you might want to subvert it by allowing the cursor keys to select items in the list, rather than scrolling the window. First, we need to identify some objects with JavaScript:

var locales = document.getElementById('scrolling-list'),
listItems = locales.children,
allLnks = new Array();
for (var i = 0;i<listItems.length;i++) {
	allLnks[i] = listItems[i].firstElementChild;
}

listItems is exactly that, whereas allLnks is an array of the links inside those list items. The fact that we’ve nested these elements will make the rest of the script slightly more complex:

locales.addEventListener('keydown', function(e) {
	var focusedElement = document.activeElement,
	index = allLnks.indexOf(focusedElement);
	if (index >= 0) {
		if (e.keyCode == 40 || e.keyCode == 39) {
			if (focusedElement.parentNode.nextElementSibling) {
				var nextNode = focusedElement.parentNode.nextElementSibling.firstElementChild;
				nextNode.focus();
			} else {
				listItems[0].firstElementChild.focus(); 
			}
		}
		if (e.keyCode == 38 || e.keyCode == 37) {
			if (focusedElement.parentNode.previousElementSibling) {
				var previousNode = focusedElement.parentNode.previousElementSibling.firstElementChild;
				previousNode.focus();
			} else {
				locales.lastElementChild.firstElementChild.focus();
			}
		}
	}
});

We create a keydown event listener for the locales object that listens for keypresses, but we only want to subvert control of the cursor keys if an element in the list is already in focus: otherwise, we’d be taking control of the cursor keys from the moment the page loads, which would prevent users from scrolling the page.

To do this, we look at the current focused element (document.activeElement) and see if it matches our array of links. If it does, we check if the pressed key corresponds to the code for the down or right cursor, then if the current focused link has another list item after it. If it does, we switch focus to the link inside that item; otherwise, we switch to selecting the first link in the list.

The code for the up / left cursor is very similar, but in reverse: it “climbs” up the list, and chooses the last link after it reaches the top.

Conclusion

As you’ve a little forethought and planning, it’s entirely possible to make “hidden” navigation both attractive and accessible, with a little progressive enhancement.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/txzuA