Did you know that the <button> element's content, and the content of its decendants may be concatenated to create an accessible name.
Lets start by looking at the different methods that can be used to generate an accessible name for the <button>.
Option 1
If an aria-labelledby is present, the contents of one or more another elements could be used to generate an accessible name:
Hello world
<button aria-labelledby="aaa"></button>
<p id="aaa">Hello world</p>
Option 2
If there is no aria-labelledby present, the content of the aria-label attribute could be used:
<button aria-label="Hello world"></button>
Option 3
If there is no aria-labelledby or aria-label present, the <button> element's content could be used. This is the preferred method as it provides a matching visible text label and accessible name.
<button>Hello world</button>
Option 4
And finally, if there is no aria-labelledby, aria-label or content present, the content of the title attribute could be used.
<button title="Hello world"></button>
A strange button example
In the example below, the <button> element's accessible name is concatenated into a text string from the accessible names of all of its descendant elements. The button below has an accessible name of:
Hello how are you doing today ?
<button>
<img src="cross.png" alt="Hello">
<span class="hidden">how are you</span>
<span role="img" aria-label="doing"></span>
<span role="img" aria-labelledby="aaa"></span>
<span role="img" title="?"></span>
</button>
<p id="aaa" style="display: none;">today</p>
Explanation
Buttons use “name from contents”, meaning their accessible name is built from the accessible text alternatives of descendant content. Different child elements can contribute text in different ways, and the final name is concatenated together.
The image provides alt text: "Hello"
<img src="cross.png" alt="Hello">
Even though visually hidden, the first <span> provides text: "how are you". Buttons build their accessible name from descendant content, so visually hidden text can still participate in the button’s name calculation. This works because the content is visually hidden only. It would not work if the element used display: none, visibility: hidden, or aria-hidden="true".
<span class="hidden">how are you</span>
The second <span> has a role of img and therefore provides a name via aria-label: "doing"
<span role="img" aria-label="doing"></span>
The third <span> has a role of img and therefore provides a name via aria-labelledby: "today"
<span role="img" aria-labelledby="aaa"></span>
...
<p id="aaa" style="display: none;">today</p>
The fourth <span> has a role of img and therefore provides a fallback accessible name via the title: "?"
<span role="img" title="?"></span>
Why is there a space before the question mark?
Hello how are you doing today ?
The space comes from how the browser computes accessible names. Each child element’s contribution is usually separated by a space when the final name is concatenated together. So the <span role="img" title="?"> contributes its own separate text token, producing "today ?" rather than "today?".