Skip to content

Commit

Permalink
feat(a11y): Collection of Accessibility-related fixes. (vuejs#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
LinusBorg authored Dec 13, 2021
1 parent ab43309 commit 6c329b8
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 43 deletions.
14 changes: 7 additions & 7 deletions src/core/components/VTSwitch.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<template>
<div class="vt-switch" role="button">
<div class="vt-switch-check">
<div class="vt-switch-icon" v-if="$slots.default">
<slot />
</div>
</div>
</div>
<button class="vt-switch" type="button">
<span class="vt-switch-check">
<span class="vt-switch-icon" v-if="$slots.default">
<slot />
</span>
</span>
</button>
</template>
13 changes: 13 additions & 0 deletions src/core/styles/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ button:focus, button:focus-visible {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
button:focus:not(:focus-visible) {
outline: none !important;
}

input:focus,
textarea:focus,
Expand Down Expand Up @@ -191,3 +194,13 @@ fieldset {
margin: 0;
padding: 0;
}

.visually-hidden {
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}
12 changes: 6 additions & 6 deletions src/core/styles/vt-doc-base.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,17 @@
}

.vt-doc h1:hover .header-anchor,
.vt-doc h1:focus .header-anchor,
.vt-doc h1 .header-anchor:focus,
.vt-doc h2:hover .header-anchor,
.vt-doc h2:focus .header-anchor,
.vt-doc h2 .header-anchor:focus,
.vt-doc h3:hover .header-anchor,
.vt-doc h3:focus .header-anchor,
.vt-doc h3 .header-anchor:focus,
.vt-doc h4:hover .header-anchor,
.vt-doc h4:focus .header-anchor,
.vt-doc h4 .header-anchor:focus,
.vt-doc h5:hover .header-anchor,
.vt-doc h5:focus .header-anchor,
.vt-doc h5 .header-anchor:focus,
.vt-doc h6:hover .header-anchor,
.vt-doc h6:focus .header-anchor {
.vt-doc h6 .header-anchor:focus {
opacity: 1;
}

Expand Down
1 change: 1 addition & 0 deletions src/core/styles/vt-switch.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

.vt-switch-icon {
position: relative;
display: block;
width: 18px;
height: 18px;
border-radius: 50%;
Expand Down
7 changes: 7 additions & 0 deletions src/vitepress/components/VPAlgoliaSearchBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ function getRelativePath(absoluteUrl: string) {
.DocSearch-Button:hover {
background: transparent;
}
.DocSearch-Button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
.DocSearch-Button:focus:not(:focus-visible) {
outline: none !important;
}
@media (min-width: 768px) {
.DocSearch-Button {
Expand Down
20 changes: 18 additions & 2 deletions src/vitepress/components/VPApp.vue
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
<script lang="ts" setup>
import { onKeyUp } from '@vueuse/core'
import { VTBackdrop } from '../../core'
import { useSidebar } from '../composables/sidebar'
import VPNav from './VPNav.vue'
import VPLocalNav from './VPLocalNav.vue'
import VPSidebar from './VPSidebar.vue'
import VPContent from './VPContent.vue'
import { provide } from 'vue'
import { provide, watchEffect } from 'vue'
const {
isOpen: isSidebarOpen,
open: openSidebar,
close: closeSidebar
} = useSidebar()
// A11y: cache the element that opened the Sidebar (the menu button)
// then focus that button again when Menu is closed with Escape key
let triggerElement: HTMLButtonElement | undefined
watchEffect(() => {
triggerElement = isSidebarOpen.value
? document.activeElement as HTMLButtonElement
: undefined
})
onKeyUp('Escape', () => {
if (isSidebarOpen.value) {
closeSidebar()
triggerElement?.focus()
}
})
provide('close-sidebar', closeSidebar)
</script>

<template>
<div class="VPApp">
<VTBackdrop class="backdrop" :show="isSidebarOpen" @click="closeSidebar" />
<VPNav />
<VPLocalNav @open-menu="openSidebar" />
<VPLocalNav :open="isSidebarOpen" @open-menu="openSidebar" />
<VPSidebar :open="isSidebarOpen">
<template #top>
<slot name="sidebar-top" />
Expand Down
1 change: 1 addition & 0 deletions src/vitepress/components/VPContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import VPNotFound from './VPNotFound.vue'
const route = useRoute()
const { frontmatter } = useData()
const { hasSidebar } = useSidebar()
</script>

<template>
Expand Down
18 changes: 11 additions & 7 deletions src/vitepress/components/VPContentDoc.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,6 @@ const { page, frontmatter, theme } = useData()
:class="{ 'has-aside': frontmatter.aside !== false }"
>
<div class="container">
<div class="content">
<slot name="content-top" />
<Content class="vt-doc" />
<slot name="content-bottom" />
<VPContentDocFooter v-if="frontmatter.footer !== false" />
</div>

<div class="aside" v-if="frontmatter.aside !== false">
<div class="aside-container">
<slot name="aside-top" />
Expand All @@ -31,6 +24,15 @@ const { page, frontmatter, theme } = useData()
<slot name="aside-bottom" />
</div>
</div>
<div class="content">
<slot name="content-top" />
<main>
<Content class="vt-doc" />
</main>
<slot name="content-bottom" />
<VPContentDocFooter v-if="frontmatter.footer !== false" />
</div>

</div>
</div>
</template>
Expand Down Expand Up @@ -97,12 +99,14 @@ const { page, frontmatter, theme } = useData()
.content {
min-width: 620px;
margin: 0;
order: 1;
}
.VPContentDoc:not(.has-aside) .content {
min-width: 688px;
}
.aside {
display: block;
order: 2;
}
}
Expand Down
29 changes: 19 additions & 10 deletions src/vitepress/components/VPContentDocOutline.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,31 @@ const filterHeaders = inject('filter-headers') as any
const filteredHeaders = computed(() => {
return filterHeaders ? filterHeaders(page.value.headers) : page.value.headers
})
const handleClick = ({ target: el }: Event) => {
const id = '#' + (el as HTMLAnchorElement).href!.split('#')[1]
const heading = document.querySelector(id) as HTMLAnchorElement
heading?.focus()
}
</script>

<template>
<div class="VPContentDocOutline" ref="container">
<div class="outline-marker" ref="marker" />
<div class="outline-title">On this page</div>
<ul class="root">
<li v-for="{ text, link, children } in resolveHeaders(filteredHeaders)">
<a class="outline-link" :href="link">{{ text }}</a>
<ul v-if="children && frontmatter.aside === 'deep'">
<li v-for="{ text, link } in children">
<a class="outline-link nested" :href="link">{{ text }}</a>
</li>
</ul>
</li>
</ul>
<nav aria-labelledby="doc-outline-aria-label">
<span id="doc-outline-aria-label" class="visually-hidden">Table of Contents for current page</span>
<ul class="root">
<li v-for="{ text, link, children } in resolveHeaders(filteredHeaders)">
<a class="outline-link" :href="link" @click="handleClick">{{ text }}</a>
<ul v-if="children && frontmatter.aside === 'deep'">
<li v-for="{ text, link } in children">
<a class="outline-link nested" :href="link" @click="handleClick">{{ text }}</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</template>

Expand Down
4 changes: 3 additions & 1 deletion src/vitepress/components/VPContentPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ const { frontmatter } = useData()

<template>
<div class="VPContentPage">
<Content />
<main>
<Content />
</main>

<slot name="footer-before" />
<VPFooter v-if="frontmatter.footer !== false" />
Expand Down
9 changes: 8 additions & 1 deletion src/vitepress/components/VPLocalNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { VTIconAlignLeft } from '../../core'
import { useSidebar } from '../composables/sidebar'
import { useData } from 'vitepress'
defineProps<{ open: boolean }>()
const { hasSidebar } = useSidebar()
const { frontmatter } = useData()
Expand All @@ -13,7 +15,12 @@ function scrollToTop() {

<template>
<div v-if="hasSidebar" class="VPLocalNav">
<button class="menu" @click="$emit('open-menu')">
<button
class="menu"
:aria-expanded="open"
aria-controls="VPSidebarNav"
@click="$emit('open-menu')"
>
<VTIconAlignLeft class="menu-icon" />
<span class="menu-text">Menu</span>
</button>
Expand Down
3 changes: 2 additions & 1 deletion src/vitepress/components/VPNavBarMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const { config } = useConfig()
</script>

<template>
<nav v-if="config.nav" class="VPNavBarMenu">
<nav v-if="config.nav" aria-labelledby="main-nav-aria-label" class="VPNavBarMenu">
<span id="main-nav-aria-label" class="visually-hidden">Main Navigation</span>
<template v-for="item in config.nav" :key="item.text">
<VPNavBarMenuLink v-if="'link' in item" :item="item" />
<VPNavBarMenuGroup v-else :item="item" />
Expand Down
23 changes: 19 additions & 4 deletions src/vitepress/components/VPNavScreenMenuGroup.vue
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { VTIconPlus, MenuItemChild } from '../../core'
import VPNavScreenMenuGroupLink from './VPNavScreenMenuGroupLink.vue'
import VPNavScreenMenuGroupSection from './VPNavScreenMenuGroupSection.vue'
defineProps<{
const props = defineProps<{
text: string
items: MenuItemChild[]
}>()
const isOpen = ref(false)
const groupId = computed(() =>
`NavScreenGroup-${props.text.replace(' ', '-').toLowerCase()}`
)
function toggle() {
isOpen.value = !isOpen.value
}
</script>

<template>
<div class="VPNavScreenMenuGroup" :class="{ open: isOpen }">
<button class="button" @click="toggle">
<button
class="button"
:aria-controls="groupId"
:aria-expanded="isOpen"
@click="toggle"
>
<span class="button-text">{{ text }}</span>
<VTIconPlus class="button-icon" />
</button>

<div class="items">
<div :id="groupId" class="items">
<template v-for="item in items" :key="item.text">
<div v-if="'link' in item" :key="item.text" class="item">
<VPNavScreenMenuGroupLink
Expand All @@ -50,6 +59,12 @@ function toggle() {
overflow: hidden;
transition: border-color 0.5s;
}
.VPNavScreenMenuGroup .items {
visibility: hidden;
}
.VPNavScreenMenuGroup.open .items {
visibility: visible;
}
.VPNavScreenMenuGroup.open {
padding-bottom: 10px;
Expand Down
26 changes: 22 additions & 4 deletions src/vitepress/components/VPSidebar.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
<script lang="ts" setup>
import { nextTick, ref, watchEffect } from 'vue'
import { useSidebar } from '../composables/sidebar'
import VPSidebarGroup from './VPSidebarGroup.vue'
const { sidebar, hasSidebar } = useSidebar()
defineProps<{
const props = defineProps<{
open: boolean
}>()
// A11y: Focus Nav element when menu has opened
// Should we focus the first link instead?
const navEl = ref<HTMLElement & { focus: () => void }>()
watchEffect(async () => {
if (props.open) {
await nextTick()
navEl.value?.focus()
}
}, { flush: 'post'})
</script>

<template>
<aside v-if="hasSidebar" class="VPSidebar" :class="{ open }" @click.stop>
<slot name="top" />
<div v-for="group in sidebar" :key="group.text" class="group">
<VPSidebarGroup :text="group.text" :items="group.items" />
</div>
<nav id="VPSidebarNav" ref="navEl" aria-labelledby="sidebar-aria-label" tabindex="-1">
<span id="sidebar-aria-label" class="visually-hidden">Sidebar Navigation</span>
<div v-for="group in sidebar" :key="group.text" class="group">
<VPSidebarGroup :text="group.text" :items="group.items" />
</div>
</nav>
<slot name="bottom" />
</aside>
</template>
Expand All @@ -30,6 +44,7 @@ defineProps<{
width: calc(100vw - 64px);
max-width: var(--vp-sidebar-width-mobile);
opacity: 0;
visibility: hidden;
background-color: var(--vt-c-bg);
box-shadow: var(--vt-c-shadow-3);
overflow-x: hidden;
Expand All @@ -47,6 +62,7 @@ defineProps<{
@media (min-width: 768px) {
.VPSidebar {
padding: 40px 32px 96px;
visibility: hidden;
}
}
Expand All @@ -59,6 +75,7 @@ defineProps<{
width: var(--vp-sidebar-width-small);
max-width: 100%;
opacity: 1;
visibility: visible;
box-shadow: none;
transform: translateX(0);
transition: border-color 0.5s, background-color 0.5s;
Expand All @@ -76,6 +93,7 @@ defineProps<{
.VPSidebar.open {
opacity: 1;
visibility: visible;
transform: translateX(0);
transition: background-color 0.5s, opacity 0.25s,
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
Expand Down

0 comments on commit 6c329b8

Please sign in to comment.