Skip to content

Commit

Permalink
fix(a11y): add aria-current="page" attribute to links (#20413)
Browse files Browse the repository at this point in the history
closes #20399

Co-authored-by: Kael <kaelwd@gmail.com>
  • Loading branch information
paul-thebaud and KaelWD authored Sep 3, 2024
1 parent 45e0c8a commit bc647f6
Show file tree
Hide file tree
Showing 6 changed files with 23 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@ export const VBreadcrumbsItem = genericComponent()({
{ !link.isLink.value ? slots.default?.() ?? props.title : (
<a
class="v-breadcrumbs-item--link"
href={ link.href.value }
aria-current={ isActive.value ? 'page' : undefined }
onClick={ link.navigate }
{ ...link.linkProps }
>
{ slots.default?.() ?? props.title }
</a>
Expand Down
2 changes: 1 addition & 1 deletion packages/vuetify/src/components/VBtn/VBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,10 @@ export const VBtn = genericComponent<VBtnSlots>()({
]}
aria-busy={ props.loading ? true : undefined }
disabled={ isDisabled.value || undefined }
href={ link.href.value }
tabindex={ props.loading || props.readonly ? -1 : undefined }
onClick={ onClick }
value={ valueAttr.value }
{ ...link.linkProps }
>
{ genOverlays(true, 'v-btn') }

Expand Down
2 changes: 1 addition & 1 deletion packages/vuetify/src/components/VCard/VCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,10 @@ export const VCard = genericComponent<VCardSlots>()({
locationStyles.value,
props.style,
]}
href={ link.href.value }
onClick={ isClickable.value && link.navigate }
v-ripple={ isClickable.value && props.ripple }
tabindex={ props.disabled ? -1 : undefined }
{ ...link.linkProps }
>
{ hasImage && (
<div key="image" class="v-card__image">
Expand Down
2 changes: 1 addition & 1 deletion packages/vuetify/src/components/VChip/VChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,11 @@ export const VChip = genericComponent<VChipSlots>()({
]}
disabled={ props.disabled || undefined }
draggable={ props.draggable }
href={ link.href.value }
tabindex={ isClickable.value ? 0 : undefined }
onClick={ onClick }
onKeydown={ isClickable.value && !isLink.value && onKeyDown }
v-ripple={[isClickable.value && props.ripple, null]}
{ ...link.linkProps }
>
{ genOverlays(isClickable.value, 'v-chip') }

Expand Down
2 changes: 1 addition & 1 deletion packages/vuetify/src/components/VList/VListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,11 @@ export const VListItem = genericComponent<VListItemSlots>()({
dimensionStyles.value,
props.style,
]}
href={ link.href.value }
tabindex={ isClickable.value ? (list ? -2 : 0) : undefined }
onClick={ onClick }
onKeydown={ isClickable.value && !isLink.value && onKeyDown }
v-ripple={ isClickable.value && props.ripple }
{ ...link.linkProps }
>
{ genOverlays(isClickable.value || isActive.value, 'v-list-item') }

Expand Down
27 changes: 18 additions & 9 deletions packages/vuetify/src/composables/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import {
computed,
nextTick,
onScopeDispose,
onScopeDispose, reactive,
resolveDynamicComponent,
toRef,
} from 'vue'
Expand Down Expand Up @@ -47,6 +47,7 @@ export interface UseLink extends Omit<Partial<ReturnType<typeof _useLink>>, 'hre
isLink: ComputedRef<boolean>
isClickable: ComputedRef<boolean>
href: Ref<string | undefined>
linkProps: Record<string, string | undefined>
}

export function useLink (props: LinkProps & LinkListeners, attrs: SetupContext['attrs']): UseLink {
Expand All @@ -58,10 +59,12 @@ export function useLink (props: LinkProps & LinkListeners, attrs: SetupContext['
})

if (typeof RouterLink === 'string' || !('useLink' in RouterLink)) {
const href = toRef(props, 'href')
return {
isLink,
isClickable,
href: toRef(props, 'href'),
href,
linkProps: reactive({ href }),
}
}
// vue-router useLink `to` prop needs to be reactive and useLink will crash if undefined
Expand All @@ -74,20 +77,26 @@ export function useLink (props: LinkProps & LinkListeners, attrs: SetupContext['
// Actual link needs to be undefined when to prop is not used
const link = computed(() => props.to ? routerLink : undefined)
const route = useRoute()
const isActive = computed(() => {
if (!link.value) return false
if (!props.exact) return link.value.isActive?.value ?? false
if (!route.value) return link.value.isExactActive?.value ?? false

return link.value.isExactActive?.value && deepEqual(link.value.route.value.query, route.value.query)
})
const href = computed(() => props.to ? link.value?.route.value.href : props.href)

return {
isLink,
isClickable,
isActive,
route: link.value?.route,
navigate: link.value?.navigate,
isActive: computed(() => {
if (!link.value) return false
if (!props.exact) return link.value.isActive?.value ?? false
if (!route.value) return link.value.isExactActive?.value ?? false

return link.value.isExactActive?.value && deepEqual(link.value.route.value.query, route.value.query)
href,
linkProps: reactive({
href,
'aria-current': computed(() => isActive.value ? 'page' : undefined),
}),
href: computed(() => props.to ? link.value?.route.value.href : props.href),
}
}

Expand Down

0 comments on commit bc647f6

Please sign in to comment.