Summary: if it’s for navigation, use a link; if it’s for an action, use a button. Then use CSS to make it look like the designs.
Using the right element for the job is not just about doing the right thing for users of assistive tech like screen readers. It’s also about ease of understanding and readability of the code (is it clear what this thing does?) and consistent and clear UX for all users (what will happen when I click / tab / space this?).
Jeremy Keith calls it material honesty: using the right element lets users know what to expect. For example: if it looks like a link, I should be able to open it in a new tab, copy the address or bookmark it for later. You can’t do that with a bookmark.
Red flags
A few things that stop me in my tracks and make me ask questions.
- Adding a
tabindex="0"
to make an element focusable. Can you use a focusable element like ana
or abutton
instead? - Adding a
role="button"
orrole="link"
. Can you use abutton
ora
element instead? - Using a
div
or aspan
for an element that does an action like navigation or show / hde. Can you use ana
or abutton
instead? - Using
href="#"
on ana
(i.e. a link that goes nowhere). Can you use a button instead? - Using
disabled
on ana
. That’s not a valid attribute on ana
. Can you use a button instead?
You can make a div
or a span
into a link or a button by adding a role
, tabindex="0"
, and handlers for click, Space, Enter events. But you’re making more work for yourself, so please just use an a
or a button
instead! 😅
Links
Links are used to navigate the user to a new page or view or bit of a page: to move away from current context. They:
- are focusable by default when they have an
href
attribute (no need to add atabindex
); - respond with a click event to the
Enter key; - can’t be disabled (they can be made inert with
tabindex="-1"
andaria-hidden="true"
, but be sure that’s what you need); - accept
:link
,:visited
,:focus
,:hover
,:active
styles; - are communicated as links to screen readers (no need to add
role="link"
)
<!-- yes! -->
<a href="/profile">View your profile</a>
<a href="/profile">Profile <img src="/icons/profile.png" alt="" /></a>
<a href="/profile" aria-label="View your Profile"><img src="/icons/profile" alt="" /></a>
<!-- please no -->
<a href="#">Click here</a> <!-- link goes nowhere. should be a button? -->
<a href="#" title="Click here">Click here</a> <!-- title is redundant -->
<a href="/profile" role="link">Click here</a> <!-- role="link" is redundant -->
<a href="/profile" tabindex="0">Click here</a> <!-- tabindex="0" is redundant -->
<span role="link" tabindex="0" href="/profile">click here</a> <!-- faking a link instead of using a link -->
Link Do’s
- Do add
alt
text to an image that’s the content of ana
. - Do make sure links are visually distinct from the surrounding text.
- Do make sure
:focus
styles are clear and distinct from the regular styles. - Do try and make link text unique on the page. This benefits users of assistive tech and makes for better UX.
- Do add
aria-current="page"
to thea
in a nav list, if it’s the current page.
Link Don’ts
- Don’t add a
title
attribute: use the text of the link instead. - Don’t wrap chunks of content in an
a
, even though it’s technically allowed in the spec. Screen readers read the whole chunk of content as the content of the link (rather than the more appropriate “Read the rest of this article.”) - Don’t use a link with
href="#"
. You should probably be using a button in that case! 😅
Buttons
Buttons are for Doing Things like submitting forms or showing and hiding things.
- A
button
inside aform
has an implicittype="submit"
(no need to add it). - Other buttons should have a
type="button"
to avoid automagically submitting a form.
They:
- receive keyboard focus by default (no need to add
tabindex
); - respond with a click event to the
Space key orEnter key; - can be disabled with the
disabled
attribute; - are communicated as buttons to screen readers (no need to add
role="button"
); - accept
:focus
,:hover
,:active
,:disabled
styles.
<!-- yes! -->
<button type="button">Show messages</button>
<button type="button">
Show messages <img src="/icons/messages.png" alt="" />
</button>
<button type="button">
<img src="/icons/messages.png" alt="Show messages" />
</button>
<button
type="button"
disabled
aria-describedby="id-of-a-paragraph-explaining-why"
>
Show messages
</button>
<!-- please no -->
<button type="button"><img src="/icons/messages.png" /></button>
<!-- no accessible name -->
<button type="button"><div>Show messages</div></button>
<!-- divs aren't valid inside buttons -->
Button Do’s
- Do always provide an accessible name. - Put text inside the button. If there’s also an image, that can have
alt=""
. - Use anaria-label
on thebutton
oralt
text on the image if the image is the only thing inside thebutton
. - Do make the accessible name describe the action that will occur when the user hits the button. Ideally it should be unique too: “Show messages” rather than just “Show”. This benefits users of assistive tech and makes for better UX.
- Do have an explanation why if you make a
button
disabled. Usearia-describedby
to point at the ID of the element containing the explanation.
Button Don’ts
- Don’t add
div
s as children elements of abutton
. But you can add inline things likespan
s.