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"))?
.replace("\"", "");
rc.writer.write_all(b"<ul class=\"chapter\">")?;
rc.writer.write_all(b"<ol class=\"chapter\">")?;
let mut current_level = 1;
@ -45,13 +45,13 @@ impl HelperDef for RenderToc {
if level > current_level {
while level > current_level {
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;
}
rc.writer.write_all(b"<li>")?;
} else if 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>")?;
current_level -= 1;
}
@ -96,7 +96,7 @@ impl HelperDef for RenderToc {
if !self.no_section_label {
// Section does not necessarily exist
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(b"</strong> ")?;
}
@ -129,12 +129,12 @@ impl HelperDef for RenderToc {
rc.writer.write_all(b"</li>")?;
}
while current_level > 1 {
rc.writer.write_all(b"</ul>")?;
rc.writer.write_all(b"</ol>")?;
rc.writer.write_all(b"</li>")?;
current_level -= 1;
}
rc.writer.write_all(b"</ul>")?;
rc.writer.write_all(b"</ol>")?;
Ok(())
}
}

View File

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

View File

@ -38,7 +38,8 @@ $( document ).ready(function() {
var KEY_CODES = {
PREVIOUS_KEY: 37,
NEXT_KEY: 39
NEXT_KEY: 39,
ESCAPE_KEY: 27,
};
$(document).on('keydown', function (e) {
@ -56,15 +57,16 @@ $( document ).ready(function() {
window.location.href = $('.nav-chapters.previous').attr('href');
}
break;
case KEY_CODES.ESCAPE_KEY:
e.preventDefault();
hideThemes();
break;
}
});
// Interesting DOM Elements
var sidebar = $("#sidebar");
// Help keyboard navigation by always focusing on page content
$(".page").focus();
// Toggle sidebar
$("#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-toggle").click(function(){
if($('.theme-popup').length) {
$('.theme-popup').remove();
if ($('.theme-popup').css('display') === 'block') {
hideThemes();
} else {
var popup = $('<div class="theme-popup"></div>')
.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(){
var theme = $(this).attr('id');
set_theme(theme);
});
showThemes();
}
});
$('.theme').click(function(){
var theme = $(this).attr('id');
set_theme(theme);
});
// Hide theme selector popup when clicking outside of it
$(document).click(function(event){
var popup = $('.theme-popup');
if(popup.length) {
if(popup.css('display') === 'block') {
var target = $(event.target);
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")) {
hideSidebar();
} else {
if ($("#sidebar").position().left === 0){
if (getComputedStyle($('#sidebar')[0])['transform'] === 'none'){
hideSidebar();
} else {
showSidebar();
@ -385,11 +388,17 @@ function sidebarToggle() {
function showSidebar() {
$('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');
}
function hideSidebar() {
$('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');
}

View File

@ -68,22 +68,29 @@
$("html").addClass("sidebar-" + sidebar);
</script>
<div id="sidebar" class="sidebar">
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
{{#toc}}{{/toc}}
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page" tabindex="-1">
<div class="page">
{{> header}}
<div id="menu-bar" class="menu-bar">
<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>
</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>
</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>
<h1 class="menu-title">{{ book_title }}</h1>
@ -95,39 +102,50 @@
</div>
</div>
<div id="content" class="content">
{{{ content }}}
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<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 -->
{{#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>
</a>
{{/previous}}
{{#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>
</a>
{{/next}}
<div style="clear: both"></div>
</div>
</nav>
</div>
</div>
{{#previous}}
<a href="{{link}}" class="nav-chapters previous" title="You can navigate through the chapters using the arrow keys">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
<nav class="nav-wide-wrapper" aria-label="Page navigation">
{{#previous}}
<a href="{{link}}" class="nav-chapters previous" title="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
{{#next}}
<a href="{{link}}" class="nav-chapters next" title="You can navigate through the chapters using the arrow keys">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
{{#next}}
<a href="{{link}}" class="nav-chapters next" title="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
</nav>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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