Links vs. Buttons in Modern Web Applications

Github interface buttons
Github: Links or buttons?

Something that comes up again and again in front-end accessibility is the issue of links versus buttons. You know, the HTML elements that open links in new windows or submit forms? In JavaScript web applications, it seems we’re still confused about which element to choose for user interaction. To try and clarify the haziness, I’ll define use cases for links and buttons in client-rendered applications and help you make better UI decisions, from design to development.

Buttons

Somehow people become web developers without learning about the HTML <button> element. (I’ll admit it took me a few years before I knew what h1-h6 headings were for, so it happens.) The mighty button is actually really cool. It can do all these things:

  • Receive keyboard focus by default
  • “Click” with the Space key
  • Submit form data to a server
  • Reset a form
  • Be disabled with the disabled attribute
  • Instruct a screen reader with the implicit button role
  • Show :focus, :hover, :active, :disabled

With a little scripting, a button is the perfect element for:

  • Opening a modal window
  • Triggering a popup menu
  • Toggling an interface
  • Playing media content
  • Inserting with JS if they only work with JS

Links

Here are a few of the basic features of links, a.k.a. anchors, a.k.a. the foundation of the Web:

  • Create hypertext, a network of online resources
  • Navigate the user to a new page or view
  • Change the URL
  • Cause a browser redraw/refresh
  • Support page jumps with internal href attributes
  • Deep-link client-rendered applications
  • Are focusable by default with the href attribute
  • Register a click with the Enter key
  • Have the implicit link role
  • Can’t be disabled like buttons but can be made inert with tabindex="-1" and aria-hidden="true"
  • Allow opening in new windows (and back in the day, framesets)
  • Show :link, :visited, :focus, :hover, :active

The starkest difference between a link and a button to me is that a link navigates the user to a new resource, taking them away from the current context (internal links are the only wrinkle here). A button toggles something in the interface, like a video player; or triggers new content in that same context, like a popup menu using aria-haspopup.

What is navigation? What is routing?

Changing the URL means a user is navigating to a new page. It refreshes the browser with new resources and redrawn pixels. Navigation can be triggered with form actions, anchor links and JavaScript location.

Much like filtering in a mail room, routing is the mechanism for connecting network requests with the appropriate content in an application. Routers are common technology in various web development frameworks–I remember lusting at PHP ones early in my career–by mapping URL fragments with views and subviews. By writing dynamic routes, new content can be created without hard-coding.

Where does the confusion come from?

In the world of client-rendered web applications built with Angular, Ember or React, a browser redraw can be triggered at any time. It’s somewhat hazy which element is right for the job when you can execute the same code as a route but with a button click handler and no URL change. For example, if I open a panel over the existing page content, am I navigating there or toggling the interface? Does it depend if the panel is deep-linked with a URL? Here’s some code I saw recently:

<a href="#" tabindex="0" ng-click="userPicker.userClicked(true)" aria-label="Some username"></a>

My immediate recommendation was to use a button element since there’s no href value, but there is tabindex="0" and an ng-click binding. It looks obvious, right? I got a response that it should remain an anchor because “it routes to external page links and re-routes to profile pages.” That would only be clear by studying the JavaScript code, which is invoked in many similar instances by ng-click bindings on button elements. Should it require such a deep analysis to recommend the right HTML element?

In a client-rendered app with proper focus management, the user may not be impacted by this–a link with ng-click and tabindex will still have the implicit link role. However, it would be much more declarative to use a populated href and routing if an anchor navigates the user to a new resource. Tabindex and click events tacked onto an anchor tag are a markup anti-pattern that will trip up future developers and code reviewers.

Making routing a core part of a web application makes it easier for developers to use links where appropriate and helps to clarify the purpose of buttons. It also makes it easier to create progressively-enhanced, server-rendered JavaScript applications using URL schemes instead of relying on click events.

The Role of UX in Accessible Development

I’ll say it out loud: this confusion often begins with Design and UX. A design comp comes to you with boxy interface buttons, and–because reasons–they have to be coded as links. Where does that become a problem?

If a screen reader user calls tech support and gets instructions to “click the button” in your UI that’s really coded as a link, they may have trouble finding it. Also, consider voice interfaces: if you say a command to click a button but it’s really coded as a link, you might have problems, no?

Frameworks admittedly blur this line and make it easy to choose the wrong element, like the above ng-click example. We can use JavaScript to trigger asynchronous form submissions on any element we want (without a form tag, even, but you still need one). Similarly, we can initiate view changes without routing, even if it’s best handled with link. Some elements are better for the job than others: it’s all about taking advantage of the most native features of that element as possible.

Github with developer tools open showing buttons next to identical links
Can you spot the difference?

What can we do about it?

Push back on Design to make links look like links and buttons look like buttons. Removing the ambiguity makes it easier for developers to code more accessibly and better meet user expectations. (Can I right-click this boxy button to open in a new window?)

In development:

  • If a screen reader user tabbed onto an interactive element, would its role tell them what to expect? (Would it navigate away from the page? They’d want to know.)
  • Suppressing link features like URL changes or right click? Consider a button.
  • Encourage routing in your application with href, ng-href, etc.
  • Page navigation deserves title changes and history.

This isn’t over.

I’m sure this conversation will continue to swirl around into eternity, as it has for many years. Each time you code an interface, you’ll have to make a judgment call about which element is the correct one….and there are millions of ways to code the same thing. As long as you’re making educated decisions, that’s the best you can do.

For developers who work on the code after you, being declarative may help to educate them on this subject. And most importantly, consider your users’ interaction expectations rather than getting Radical™ with interface design. Making intuitive interfaces reduces friction and keeps users happy.

  • jpdevries

    This is great! HTML5 buttons can actually “open things” in new a new window as well with the formtarget. They can also use formaction to say “hey let’s submit the form data but to a different endpoint than the form’s action” which I think is pretty cool. Really comes in handy when trying to support .no-js.

  • I did not realize that about the formtarget! Sounds useful for iframes but potentially confusing to a screen reader user.

  • Thanks, great read. I was really surprised to find new and refreshing content on this topic;-)
    Does the keydown event on buttons with the space key, actually registers/triggers a mouse click event (like enter on links)?

  • Vicente d’Avila Melo Sarmento

    Great article!

  • Extragorey

    You’re talking about elements, but how are they different from ? Is it the same thing?

  • Andrew Ritter

    is for a form. doesnt need to be in a form.

  • Extragorey

    Well, neither does , in fact, they’re usually outside forms considering most forms only need an .

  • Andrew Ritter

    I did read your comment too quickly, and saw . My bad.
    However, I’d argue, being that it is a type of an input element, still belongs in a web-based form — especially with the existence of .

    While is identified as having superseded , the former is a semantic element for a clickable element, the latter is a specific version of the element used to create a clickable button with no default value.
    Perhaps that is the only difference, semantics.

  • Extragorey

    Yeah, I’m getting the feeling the main difference is convention. Or semantics, take your pick.
    Thanks for the explanation though.

  • For the most part, yes–buttons trigger click events. If you have multiple event bindings it depends on the browser and assistive technology which handler will be called. In JAWS and Firefox, for example, only click is registered, not keydown. Makes things like accessible reordering pretty difficult to achieve!

  • They both have a role of button, interestingly enough. I agree an input should be used in a form, but given the role of button I can’t think of any reason other than convention.

  • Jonathan Abreu

    When located in a form, will capture any hit of the Return key, from anywhere, if something does not capture the event and prevent this default.
    Will capture the hit of the Return key only if the focus was on it.

  • Extragorey

    Oh wow, just tested this. Why would you want that behaviour?
    Having an will already make the Return key submit the form from anywhere in the form, and having a as well means two events are fired.

  • “Push back on Design to make links look like links and buttons look like buttons. Removing the ambiguity makes it easier for developers to code more accessibly and better meet user expectations.”

    I understand where you’re coming from (I think), but just because there’s some complexity around what element to choose for development and accessibility reasons, doesn’t mean that it should fall on the designer to conform visual design to code-specific semantics. Yes, there is a distinction in some contexts, but the presentation of the button or link is typically styled for what’s contextually appropriate. I’m not going to make a blanket defense of all button/link design choices, but most users are familiar with buttons (in a variety of styles) as being action-oriented. The text, iconography, and context communicate what the ‘button’ (or button-styled element) does, whether or not it’s coded as a link or a button.

    Small point of contention aside, I do agree that both design and code should conform to appropriate standards to make sites/apps broadly usable (i.e. accessible) for their audiences. Thanks for the write-up.

  • Jonathan Abreu

    It is not a behavior that I want that’s the spec. It’s just that you could end with several buttons fired by the event, and if you don’t want to submit the form and just execute an action from a button, you face also a submit (when it was not intended because you were focused on a secondary button).

  • Marek

    Cool article, but what would you use to open a modal which is routed (has it’s own URL)? A button or a link?

  • In an ideal world, it would be a link with an href and a modal-free fallback if JavaScript is disabled (embedding the content directly in the page, thus not requiring JS to open). Then it would gracefully degrade without the modal and the user could still navigate to the content.

  • I’m saying I don’t think it should be entirely the developer’s responsibility to consider navigation vs. UI buttons. What the thing does is just as important as how it looks.

  • Jose Gonzalez

    Good article! What about having a button as a CTA for mobile instead of using just a text link? Text links alone on mobile are usually hard to click on due to different thumb sizes.

  • You can use CSS to add padding to prominent text links. Perhaps the font size should be larger on mobile as well? I still think you need to choose the right semantic element for its function, regardless of the medium.

  • Paul

    Great article Marcy. I’ve seen people use in the past and still style it to look like a button, which seems crazy, but then that’s why need more articles like this to bring awareness :)

  • Nick Beranek

    Hey Marcy, what do you think about the ngAria dependency automatically adding a role=”button” to an element with a ng-click? I think that it’s great to make an element focusable with tabindex=”0″, actionable with a keypress event listener to account for the Enter and Spacebar, and have an accessible role for the user, but I have an issue with the choice of the role itself. What are your thoughts?

  • I think it had good intentions but ultimately was too fraught with edge cases to be useful at the framework level. And I say that as the person who wrote the feature! You can turn it off with the ngAria config fortunately, along with tabindex–which I also think causes more problems than it solves.