primevue
primevue copied to clipboard
Improve Accessibility and HTML Compilation for Button Component
I'm writing to propose a critical improvement for the Button
component in PrimeVue. Currently, there's a significant accessibility and HTML compilation issue when using buttons with links or router functionality. This has been reported multiple times in past issues (#1920, #3118, #3552).
Problem
Currently, to use a link or router functionality with a button, users have to resort to the following workaround:
<RouterLink to="/">
<Button label="Go home" />
</RouterLink>
However, this approach can lead to accessibility issues and invalid HTML compilation, resulting in something like this:
<a href="/">
<button>Go home</button>
</a>
Another problem that can occur is if the Button
has the disabled
property enabled, but a RouterLink
encapsulates it, this can generate usability problems (see #5180).
As you can see in HTML5 Spec Document from W3C, it isn't valid.
Content model: Transparent, but there must be no interactive content descendant.
The a element may be wrapped around entire paragraphs, lists, tables, and so forth, even entire sections, so long as there is no interactive content within (e.g. buttons or other links).
Solution
I propose adding two new parameters to the Button
component: to
and href
. Additionally, the root component should change from <button>
to <component>
. This way, it will be possible to dynamically control the component to be rendered.
For instance:
- If
to
is used andRouterLink
is available in the application, the "component is" attribute will be set toRouterLink
. - If
href
is used, the<a>
tag will be utilized (and all other valid parameters can be used, such astarget
). - If none of the parameters are used, then
<button>
will be rendered by default.
Example
Here's a simple example of how the proposed solution would work in a Vue 3 component using Composition API:
<template>
<component :is="buttonComponent" :to="to" :href="href" :label="label" />
</template>
<script setup>
import { computed, defineProps, resolveComponent } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({
tag: {
default: 'button',
type: String
},
to: {
default: '',
// TypeScript: see about RouteLocationRaw interface.
type: [String, Object]
},
href: {
default: '',
type: String
},
label: {
default: '',
type: String
}
})
const router = useRouter()
const buttonComponent = computed(() => {
if (props.to && router) {
return resolveComponent('RouterLink')
}
return props.href ? 'a' : props.tag
})
</script>
Major Vue UI frameworks already provide solutions to this problem.
- https://quasar.dev/vue-components/button
- https://element-plus.org/en-US/component/button.html
- https://vuetifyjs.com/en/api/v-btn/#links
This issue/solution is valid not only for Button
, but also other components like Breadcrumb
, Menu
, MenuItem
and others that require similar functionality.
Totally agree, just stumbled upon this myself and was shocked that the documentation is suggesting using invalid HTML5 syntax as a way to achieve link button pattern. Pretty disappointed by this design decision
https://stackoverflow.com/a/6393863/7980639
@tugcekucukoglu I'd like to contribute by developing this improvement in v4 branch. Do you have plans to add this?
Fully agreed, this issue and the devs' unwillingness to address it single-handedly put me off using PrimeVue. Components having valid HTML5 syntax seems like a bare minimum for an UI library.
We also think this is crucial for improved developer experience and most of all, accessibility.
@caiotarifa Perhaps its worth a shot, implement this and open PR?
Any updates for this? Seems like a no brainer...
Hello,
We added as
and asChild
properties to Button in v4. Please try it; https://primevue.org/button/#link