Improve accessibility (#535)

* fix(theme/index): Use nav element for Table of Content

* fix(renderer/html_handlebars/helpers/toc): Use ol instead of ul

Chapters and sections are ordered, so we should use the appropriate HTML tag.

* fix(renderer/html_handlebars/helpers/toc): Hide section number from screen readers

Screen readers have this functionality build-in, no need to present this. Ideally, this should not even be in the DOM tree, since the numbers can be shown by using CSS.

* fix(theme/index): Remove tabIndex="-1" from .page

Divs are not focusable by default

* fix(theme): Make sidebar accessible

Using aria-hidden (together with tabIndex) takes the links out of the tab order.
http://heydonworks.com/practical_aria_examples/#progressive-collapsibles

* fix(theme/index): Wrap content inside main tag

The main tag helps users skip additional content on the page.

* fix(theme/book): Don't focus .page on page load

The main content is identified by the main tag, not by auto-focusing it on page load.

* fix(theme/index): Make page controls accessible

* fix: Make theme selector accessible

- Use ul and li (since it is a list)
- Add aria-expanded and aria-haspopup to the toggle button
- Use button instead of div (buttons are accessible by default)
- Handle Esc key (close popup)
- Adjust CSS to keep same visual style

* fix(theme/stylus/sidebar): Make link clickable area wider

Links now expand to fill the entire row.

* fix(theme): Wrap header buttons and improve animation performance

Previously, the header had a fixed height, which meant that sometimes the print button was not visible. Animating the left property is expensive, which lead to laggy animations - transform is much cheaper and has the same effect.

* fix(theme/stylus/theme-popup): Theme button inherits color

Bug introduced while making the popup accessible

* fix(theme/book): Handle edge case when toggling sidebar

Bug introduced when switching from animating left to using transform.
This commit is contained in:
Sorin Davidoi 2018-01-15 14:26:53 +01:00 committed by Michael Bryan
parent 9ab54412ea
commit 61fad2786b
10 changed files with 162 additions and 88 deletions

View File

@ -25,7 +25,7 @@ impl HelperDef for RenderToc {
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
.replace("\"", ""); .replace("\"", "");
rc.writer.write_all(b"<ul class=\"chapter\">")?; rc.writer.write_all(b"<ol class=\"chapter\">")?;
let mut current_level = 1; let mut current_level = 1;
@ -45,13 +45,13 @@ impl HelperDef for RenderToc {
if level > current_level { if level > current_level {
while level > current_level { while level > current_level {
rc.writer.write_all(b"<li>")?; rc.writer.write_all(b"<li>")?;
rc.writer.write_all(b"<ul class=\"section\">")?; rc.writer.write_all(b"<ol class=\"section\">")?;
current_level += 1; current_level += 1;
} }
rc.writer.write_all(b"<li>")?; rc.writer.write_all(b"<li>")?;
} else if level < current_level { } else if level < current_level {
while level < current_level { while level < current_level {
rc.writer.write_all(b"</ul>")?; rc.writer.write_all(b"</ol>")?;
rc.writer.write_all(b"</li>")?; rc.writer.write_all(b"</li>")?;
current_level -= 1; current_level -= 1;
} }
@ -96,7 +96,7 @@ impl HelperDef for RenderToc {
if !self.no_section_label { if !self.no_section_label {
// Section does not necessarily exist // Section does not necessarily exist
if let Some(section) = item.get("section") { if let Some(section) = item.get("section") {
rc.writer.write_all(b"<strong>")?; rc.writer.write_all(b"<strong aria-hidden=\"true\">")?;
rc.writer.write_all(section.as_bytes())?; rc.writer.write_all(section.as_bytes())?;
rc.writer.write_all(b"</strong> ")?; rc.writer.write_all(b"</strong> ")?;
} }
@ -129,12 +129,12 @@ impl HelperDef for RenderToc {
rc.writer.write_all(b"</li>")?; rc.writer.write_all(b"</li>")?;
} }
while current_level > 1 { while current_level > 1 {
rc.writer.write_all(b"</ul>")?; rc.writer.write_all(b"</ol>")?;
rc.writer.write_all(b"</li>")?; rc.writer.write_all(b"</li>")?;
current_level -= 1; current_level -= 1;
} }
rc.writer.write_all(b"</ul>")?; rc.writer.write_all(b"</ol>")?;
Ok(()) Ok(())
} }
} }

View File

@ -61,17 +61,21 @@ table thead td {
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
-webkit-transition: left 0.5s; -webkit-transition: -webkit-transform 0.5s;
-moz-transition: left 0.5s; -moz-transition: -moz-transform 0.5s;
-o-transition: left 0.5s; -o-transition: -o-transform 0.5s;
-ms-transition: left 0.5s; -ms-transition: -ms-transform 0.5s;
transition: left 0.5s; transition: transform 0.5s;
} }
.sidebar code { .sidebar code {
line-height: 2em; line-height: 2em;
} }
.sidebar-hidden .sidebar { .sidebar-hidden .sidebar {
left: -300px; -webkit-transform: translateX(-300px);
-moz-transform: translateX(-300px);
-o-transform: translateX(-300px);
-ms-transform: translateX(-300px);
transform: translateX(-300px);
} }
.chapter { .chapter {
list-style: none outside none; list-style: none outside none;
@ -79,6 +83,7 @@ table thead td {
line-height: 2.2em; line-height: 2.2em;
} }
.chapter li a { .chapter li a {
display: block;
padding: 5px 0; padding: 5px 0;
text-decoration: none; text-decoration: none;
} }
@ -105,8 +110,6 @@ table thead td {
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
box-sizing: border-box; box-sizing: border-box;
min-height: 100%;
width: 100%;
-webkit-transition: padding-left 0.5s, margin-left 0.5s; -webkit-transition: padding-left 0.5s, margin-left 0.5s;
-moz-transition: padding-left 0.5s, margin-left 0.5s; -moz-transition: padding-left 0.5s, margin-left 0.5s;
-o-transition: padding-left 0.5s, margin-left 0.5s; -o-transition: padding-left 0.5s, margin-left 0.5s;
@ -143,7 +146,18 @@ table thead td {
} }
.menu-bar { .menu-bar {
position: relative; position: relative;
height: 50px; display: -webkit-box;
display: -moz-box;
display: -webkit-flex;
display: -ms-flexbox;
display: box;
display: flex;
-webkit-box-lines: multiple;
-moz-box-lines: multiple;
-o-box-lines: multiple;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
} }
.menu-bar i, .menu-bar i,
.menu-bar .icon-button { .menu-bar .icon-button {
@ -161,24 +175,24 @@ table thead td {
.menu-bar .icon-button:hover { .menu-bar .icon-button:hover {
cursor: pointer; cursor: pointer;
} }
.menu-bar .left-buttons {
float: left;
}
.menu-bar .right-buttons {
float: right;
}
.menu-title { .menu-title {
display: inline-block; display: inline-block;
font-weight: 200; font-weight: 200;
font-size: 20px; font-size: 20px;
line-height: 50px; line-height: 50px;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
text-align: center; text-align: center;
margin: 0; margin: 0;
-webkit-box-flex: 1;
-moz-box-flex: 1;
-o-box-flex: 1;
box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
white-space: nowrap;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
opacity: 0; opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0); filter: alpha(opacity=0);
@ -252,7 +266,7 @@ table thead td {
right: 15px; right: 15px;
} }
@media only screen and (max-width: 1080px) { @media only screen and (max-width: 1080px) {
.nav-chapters { .nav-wide-wrapper {
display: none; display: none;
} }
.nav-wrapper { .nav-wrapper {
@ -260,7 +274,7 @@ table thead td {
} }
} }
@media only screen and (max-width: 1380px) { @media only screen and (max-width: 1380px) {
.sidebar-visible .nav-chapters { .sidebar-visible .nav-wide-wrapper {
display: none; display: none;
} }
.sidebar-visible .nav-wrapper { .sidebar-visible .nav-wrapper {
@ -275,11 +289,18 @@ table thead td {
font-size: 0.7em; font-size: 0.7em;
} }
.theme-popup .theme { .theme-popup .theme {
display: inline;
border: 0;
margin: 0; margin: 0;
padding: 2px 10px; padding: 2px 10px;
line-height: 25px; line-height: 25px;
width: 100%;
white-space: nowrap; white-space: nowrap;
text-align: left;
cursor: pointer; cursor: pointer;
color: inherit;
background: inherit;
font-size: inherit;
} }
.theme-popup .theme:hover:first-child, .theme-popup .theme:hover:first-child,
.theme-popup .theme:hover:last-child { .theme-popup .theme:hover:last-child {
@ -349,6 +370,10 @@ table thead td {
color: #333; color: #333;
background: #fafafa; background: #fafafa;
border: 1px solid #ccc; border: 1px solid #ccc;
margin: 0;
padding: 0;
list-style: none;
display: none;
} }
.light .theme-popup .theme:hover { .light .theme-popup .theme:hover {
background-color: #e6e6e6; background-color: #e6e6e6;
@ -481,6 +506,10 @@ table thead td {
color: #98a3ad; color: #98a3ad;
background: #141617; background: #141617;
border: 1px solid #43484d; border: 1px solid #43484d;
margin: 0;
padding: 0;
list-style: none;
display: none;
} }
.coal .theme-popup .theme:hover { .coal .theme-popup .theme:hover {
background-color: #1f2124; background-color: #1f2124;
@ -613,6 +642,10 @@ table thead td {
color: #bcbdd0; color: #bcbdd0;
background: #161923; background: #161923;
border: 1px solid #737480; border: 1px solid #737480;
margin: 0;
padding: 0;
list-style: none;
display: none;
} }
.navy .theme-popup .theme:hover { .navy .theme-popup .theme:hover {
background-color: #282e40; background-color: #282e40;
@ -745,6 +778,10 @@ table thead td {
color: #262625; color: #262625;
background: #e1e1db; background: #e1e1db;
border: 1px solid #b38f6b; border: 1px solid #b38f6b;
margin: 0;
padding: 0;
list-style: none;
display: none;
} }
.rust .theme-popup .theme:hover { .rust .theme-popup .theme:hover {
background-color: #99908a; background-color: #99908a;
@ -877,6 +914,10 @@ table thead td {
color: #c5c5c5; color: #c5c5c5;
background: #14191f; background: #14191f;
border: 1px solid #5c6773; border: 1px solid #5c6773;
margin: 0;
padding: 0;
list-style: none;
display: none;
} }
.ayu .theme-popup .theme:hover { .ayu .theme-popup .theme:hover {
background-color: #191f26; background-color: #191f26;

View File

@ -38,7 +38,8 @@ $( document ).ready(function() {
var KEY_CODES = { var KEY_CODES = {
PREVIOUS_KEY: 37, PREVIOUS_KEY: 37,
NEXT_KEY: 39 NEXT_KEY: 39,
ESCAPE_KEY: 27,
}; };
$(document).on('keydown', function (e) { $(document).on('keydown', function (e) {
@ -56,15 +57,16 @@ $( document ).ready(function() {
window.location.href = $('.nav-chapters.previous').attr('href'); window.location.href = $('.nav-chapters.previous').attr('href');
} }
break; break;
case KEY_CODES.ESCAPE_KEY:
e.preventDefault();
hideThemes();
break;
} }
}); });
// Interesting DOM Elements // Interesting DOM Elements
var sidebar = $("#sidebar"); var sidebar = $("#sidebar");
// Help keyboard navigation by always focusing on page content
$(".page").focus();
// Toggle sidebar // Toggle sidebar
$("#sidebar-toggle").click(sidebarToggle); $("#sidebar-toggle").click(sidebarToggle);
@ -101,36 +103,37 @@ $( document ).ready(function() {
} }
}); });
function showThemes() {
$('.theme-popup').css('display', 'block');
$('#theme-toggle').attr('aria-expanded', true);
}
function hideThemes() {
$('.theme-popup').css('display', 'none');
$('#theme-toggle').attr('aria-expanded', false);
}
// Theme button // Theme button
$("#theme-toggle").click(function(){ $("#theme-toggle").click(function(){
if($('.theme-popup').length) { if ($('.theme-popup').css('display') === 'block') {
$('.theme-popup').remove(); hideThemes();
} else { } else {
var popup = $('<div class="theme-popup"></div>') showThemes();
.append($('<div class="theme" id="light">Light <span class="default">(default)</span><div>')) }
.append($('<div class="theme" id="rust">Rust</div>')) });
.append($('<div class="theme" id="coal">Coal</div>'))
.append($('<div class="theme" id="navy">Navy</div>'))
.append($('<div class="theme" id="ayu">Ayu</div>'));
popup.insertAfter(this);
$('.theme').click(function(){ $('.theme').click(function(){
var theme = $(this).attr('id'); var theme = $(this).attr('id');
set_theme(theme); set_theme(theme);
}); });
}
});
// Hide theme selector popup when clicking outside of it // Hide theme selector popup when clicking outside of it
$(document).click(function(event){ $(document).click(function(event){
var popup = $('.theme-popup'); var popup = $('.theme-popup');
if(popup.length) { if(popup.css('display') === 'block') {
var target = $(event.target); var target = $(event.target);
if(!target.closest('.theme').length && !target.closest('#theme-toggle').length) { if(!target.closest('.theme').length && !target.closest('#theme-toggle').length) {
popup.remove(); hideThemes();
} }
} }
}); });
@ -375,7 +378,7 @@ function sidebarToggle() {
} else if (html.hasClass("sidebar-visible")) { } else if (html.hasClass("sidebar-visible")) {
hideSidebar(); hideSidebar();
} else { } else {
if ($("#sidebar").position().left === 0){ if (getComputedStyle($('#sidebar')[0])['transform'] === 'none'){
hideSidebar(); hideSidebar();
} else { } else {
showSidebar(); showSidebar();
@ -385,11 +388,17 @@ function sidebarToggle() {
function showSidebar() { function showSidebar() {
$('html').removeClass('sidebar-hidden').addClass('sidebar-visible'); $('html').removeClass('sidebar-hidden').addClass('sidebar-visible');
$('#sidebar a').attr('tabIndex', 0);
$('#sidebar-toggle').attr('aria-expanded', true);
$('#sidebar').attr('aria-hidden', false);
store.set('mdbook-sidebar', 'visible'); store.set('mdbook-sidebar', 'visible');
} }
function hideSidebar() { function hideSidebar() {
$('html').removeClass('sidebar-visible').addClass('sidebar-hidden'); $('html').removeClass('sidebar-visible').addClass('sidebar-hidden');
$('#sidebar a').attr('tabIndex', -1);
$('#sidebar-toggle').attr('aria-expanded', false);
$('#sidebar').attr('aria-hidden', true);
store.set('mdbook-sidebar', 'hidden'); store.set('mdbook-sidebar', 'hidden');
} }

View File

@ -68,22 +68,29 @@
$("html").addClass("sidebar-" + sidebar); $("html").addClass("sidebar-" + sidebar);
</script> </script>
<div id="sidebar" class="sidebar"> <nav id="sidebar" class="sidebar" aria-label="Table of contents">
{{#toc}}{{/toc}} {{#toc}}{{/toc}}
</div> </nav>
<div id="page-wrapper" class="page-wrapper"> <div id="page-wrapper" class="page-wrapper">
<div class="page" tabindex="-1"> <div class="page">
{{> header}} {{> header}}
<div id="menu-bar" class="menu-bar"> <div id="menu-bar" class="menu-bar">
<div class="left-buttons"> <div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle sidebar"> <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i> <i class="fa fa-bars"></i>
</button> </button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme"> <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i> <i class="fa fa-paint-brush"></i>
</button> </button>
<ul id="theme-list" class="theme-popup" aria-label="submenu">
<li><button class="theme" id="light">Light <span class="default">(default)</span></button></li>
<li><button class="theme" id="rust">Rust</button></li>
<li><button class="theme" id="coal">Coal</button></li>
<li><button class="theme" id="navy">Navy</button></li>
<li><button class="theme" id="ayu">Ayu</button></li>
</ul>
</div> </div>
<h1 class="menu-title">{{ book_title }}</h1> <h1 class="menu-title">{{ book_title }}</h1>
@ -95,39 +102,50 @@
</div> </div>
</div> </div>
<div id="content" class="content"> <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
{{{ content }}} <script type="text/javascript">
$('#sidebar-toggle').attr('aria-expanded', sidebar === 'visible');
$('#sidebar').attr('aria-hidden', sidebar !== 'visible');
$('#sidebar a').attr('tabIndex', sidebar === 'visible' ? 0 : -1);
</script>
<div class="nav-wrapper"> <div id="content" class="content">
<main>
{{{ content }}}
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons --> <!-- Mobile navigation buttons -->
{{#previous}} {{#previous}}
<a rel="prev" href="{{link}}" class="mobile-nav-chapters previous" title="Previous chapter"> <a rel="prev" href="{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
{{/previous}} {{/previous}}
{{#next}} {{#next}}
<a rel="next" href="{{link}}" class="mobile-nav-chapters next" title="Next chapter"> <a rel="next" href="{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
{{/next}} {{/next}}
<div style="clear: both"></div> <div style="clear: both"></div>
</div> </nav>
</div> </div>
</div> </div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
{{#previous}} {{#previous}}
<a href="{{link}}" class="nav-chapters previous" title="You can navigate through the chapters using the arrow keys"> <a href="{{link}}" class="nav-chapters previous" title="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i> <i class="fa fa-angle-left"></i>
</a> </a>
{{/previous}} {{/previous}}
{{#next}} {{#next}}
<a href="{{link}}" class="nav-chapters next" title="You can navigate through the chapters using the arrow keys"> <a href="{{link}}" class="nav-chapters next" title="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i> <i class="fa fa-angle-right"></i>
</a> </a>
{{/next}} {{/next}}
</nav>
</div> </div>

View File

@ -1,6 +1,7 @@
.menu-bar { .menu-bar {
position: relative position: relative
height: 50px display: flex
flex-wrap: wrap
i, .icon-button { i, .icon-button {
position: relative position: relative
@ -12,9 +13,6 @@
&:hover { cursor: pointer } &:hover { cursor: pointer }
} }
.left-buttons { float: left }
.right-buttons { float: right }
} }
.menu-title { .menu-title {
@ -22,13 +20,12 @@
font-weight: 200 font-weight: 200
font-size: 20px font-size: 20px
line-height: 50px line-height: 50px
position: absolute
top: 0
left: 0
right: 0
bottom: 0
text-align: center text-align: center
margin: 0 margin: 0
flex: 1
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
opacity: 0 opacity: 0
transition: opacity 0.5s ease-in-out transition: opacity 0.5s ease-in-out

View File

@ -43,13 +43,13 @@
} }
@media only screen and (max-width: $page-plus-sidebar-width) { @media only screen and (max-width: $page-plus-sidebar-width) {
.nav-chapters { display: none } .nav-wide-wrapper { display: none }
.nav-wrapper { display: block } .nav-wrapper { display: block }
} }
@media only screen and (max-width: $page-plus-sidebar-width + $sidebar-width) { @media only screen and (max-width: $page-plus-sidebar-width + $sidebar-width) {
.sidebar-visible { .sidebar-visible {
.nav-chapters { display: none } .nav-wide-wrapper { display: none }
.nav-wrapper { display: block } .nav-wrapper { display: block }
} }
} }

View File

@ -3,9 +3,6 @@
.page-wrapper { .page-wrapper {
box-sizing: border-box box-sizing: border-box
min-height: 100%
width: 100%
// Animation: slide away // Animation: slide away
transition: padding-left 0.5s, margin-left 0.5s transition: padding-left 0.5s, margin-left 0.5s
} }

View File

@ -13,7 +13,7 @@
-webkit-overflow-scrolling: touch -webkit-overflow-scrolling: touch
// Animation: slide away // Animation: slide away
transition: left 0.5s transition: transform 0.5s
code { code {
line-height: 2em; line-height: 2em;
@ -21,7 +21,7 @@
} }
.sidebar-hidden .sidebar { .sidebar-hidden .sidebar {
left: - $sidebar-width transform: translateX(- $sidebar-width)
} }
.chapter { .chapter {
@ -30,6 +30,7 @@
line-height: 2.2em line-height: 2.2em
li a { li a {
display: block;
padding: 5px 0 padding: 5px 0
text-decoration: none text-decoration: none

View File

@ -8,11 +8,18 @@
font-size: 0.7em font-size: 0.7em
.theme { .theme {
display: inline
border: 0
margin: 0 margin: 0
padding: 2px 10px padding: 2px 10px
line-height: 25px line-height: 25px
width: 100%
white-space: nowrap white-space: nowrap
text-align: left
cursor: pointer cursor: pointer
color inherit
background: inherit;
font-size: inherit;
&:hover:first-child, &:hover:first-child,
&:hover:last-child { &:hover:last-child {

View File

@ -67,6 +67,10 @@
color: $fg color: $fg
background: $theme-popup-bg background: $theme-popup-bg
border: 1px solid $theme-popup-border border: 1px solid $theme-popup-border
margin: 0;
padding: 0;
list-style: none;
display: none;
.theme:hover { background-color: $theme-hover } .theme:hover { background-color: $theme-hover }