Reputation: 3142
I'm building an interactive widget that consists in making a button list out of a list of HTML markups. To achieve that, the HTML markup is wrapped with a button-like component <div role="button" tabindex="0" aria-label="Action...">
. When clicking on the button, some script is executed.
I don't have control over that child HTML markup, and I want to prevent all other interactive elements from being interacted with. They should stay visible, though.
From the point of view of a mouse user, this can be achieved by setting pointer-events: none;
on all child elements.
From the point of view of a keyboard user, I need a way to prevent interactive elements from being interacted with.
In this example, the focus should go from the first div with tabindex="0" to the second div with tabindex="0", without focusing on the <a>
tag.
.focusable {
border: 1px solid blue;
margin: 10px;
padding: 10px;
}
.focusable > * {
pointer-events: none;
}
.focusable:hover {
background-color: lightgrey;
}
<div role="button" tabindex="0" class="focusable" aria-label="Action 0">
<div>Some html markup
<a href="#">Should not be focusable, but should be visible</a>
</div>
</div>
<div role="button" tabindex="0" class="focusable" aria-label="Action 1">
<div>Some html markup
<input type="text" placeholder="should not be focusable" />
</div>
</div>
One way I thought of achieving that is to set tabindex="-1"
on the child interactive elements. It will remove those from the tab order. However, it will not prevent them from being accessed programmatically, for example, from a screen reader links menu.
I wondered if there are other techniques to make all child elements of an element non-interactive. Especially something that can be applied on a wrapper, so there is no need to find all interactive elements and replace them.
Upvotes: 6
Views: 6653
Reputation: 6170
In the end, you would need to assign the following:
<button aria-label="Label text … additional text">
<div inert>
…
<a tabindex="-1"
aria-hidden="true"
style="pointer-events: none">
…
Regarding the ARIA standard, part of this is already recommended standard behavior of button children. For the button role, it reads:
Children Presentational: True
Some roles have presentational children. That is to say, they should remove any roles from their children. Hence a link would not be exposed in the link menu.
The DOM descendants are presentational. User agents SHOULD NOT expose descendants of this element through the platform accessibility API.
Unfortunately, it’s not a MUST and not implemented in browsers. So you’d need to implement that part manually.
Usually, you’d be adding role="presentation"
or role="none"
to the children so that they are not presented in the generated menus anymore.
Unfortunately, this does not work if applied by a script. A link will stay exposed as a link.
What works is to hide the element entirely through aria-hidden
. This will also remove it from the accessible name calculation.
Usually, the button’s name would be calculated from the texts of all children. Once you hide them all, the button is empty.
So you need to provide a name yourself with aria-label
. Beware of Label in Name, though.
Many screen reader users are sighted, and to direct voice control software towards a button, the visible text needs to match at least partially with the accessible name. So the aria-label
value should start with the visible text. You could, for example, provide a backdrop that renders the original contents blurry and adds that label.
The above will not prevent user interaction directly with the children. The inert
attribute on a wrapper does that, but it’s not supported everywhere yet.
[…it] makes the browser "ignore" user input events for the element, including focus events and events from assistive technologies. […]
So you can also apply tabindex="-1"
as you suggested, to make sure browsers that don’t support inert don’t allow keyboard interaction.
document.querySelectorAll('button *').forEach(n => {
n.setAttribute('inert', '');
n.setAttribute('tabindex', '-1');
n.setAttribute('aria-hidden', '')
});
button * {
pointer-events: none;
}
<button>
This should not be <a href="#" onclick="alert('damn, it‘s still a link')">a link</a>
</button>
Upvotes: 3
Reputation: 3142
The easiest solution is to give inert attribute to the first child element of the <div role="button">
. According to MDN:
Specifically, inert does the following:
- Prevents the click event from being fired when the user clicks on the element.
- Prevents the focus event from being raised by preventing the element from gaining focus.
- Hides the element and its content from assistive technologies by excluding them from the accessibility tree.
However browser support is not good, for example no support on Firefox.
For better compatibility the solution is to use:
aria-hidden="true"
: hides from accessibility treetabindex="-1"
: removes from tab navigationpointer-events: 'none'
: prevents pointer interactionUpvotes: 9
Reputation: 1
Adding tabindex="-1" and aria-hidden="true" should be good enough as element is not conveying anything extra to the screen reader users.
Upvotes: 0