It is not uncommon for links to be styled like buttons when building websites.  Although this is not frowned upon, it is important to understand what HTML element to use depending on the purpose of that element.  For example, a <button> element is frequently used to perform an action such as submit a form, or interact with an element to change its behavior., whereas s <a> element is often used to direct the user to another page, site, or section of a page.  Visually it may not make much difference how links or buttons are styled, but choosing the right element matters from a semantic and even accessibility perspective.  Let’s go over how to dynamically render the right HTML element depending on the element’s purpose using Twig on a Drupal website.

Before we get into the details of the code, let’s understand how the two elements differentiate from each other which will help us qualify the right element to use.  Probably the easiest way that makes the <button> and <a> different from each other is that a <a> contains a href attribute and the <button> does not.  This alone can help us determine whether we should use a <button> or <a> element on our site.  The href attribute will always be present when using an anchor element.  So let’s use the href attribute to our advantage.

  1. Let's start by defining some key data elements we usually have at our disposal when building a button or anchor element in Twig or React:  url, text, class
  2. It’s possible we won’t always have url or class, but text will most likely be available so we can label the element (i.e. Read more, Submit, etc.)
  3. We know that a <a> will always need a url.  We also know a <button> will never have a url value

Using the logic above, we could start writing some code.  Let’s start with Twig and then we will repeat the process with JavaScript if you are working on a React or Gatsby project.

 

{% if url %}

  <a href="{{ url }}" class="button button--link">

    {{ text }}

  </a>

{% else %}

  <button class="button">

    {{ text }}

  </button>

{% endif %}

 

  • By using the url as the condition, we check whether a url in fact exists, and if so, we print a <a> element, otherwise we print a <button>
  • Whether we have a url or not, we print the text variable on either element
  • We are passing a class of button to each element, but for the <a> we are also passing a class of button--link.  This will help us style the links differently than buttons if that is something we may wish to do sometimes.  Otherwise, the link will automatically inherit the same styles as a <button> element.

 

JavaScript and/or Gatsby

The logic to follow in any other language will be the same. The syntax may vary but ultimately we are checking for a URL and if so, let’s print a <a> element, otherwise, let’s print a <button> element.  However, in Gatsby there is one more scenario to consider; is the URL internal or going to an external page/site?  One of Gatsby’s powerful core components or features is <Link>.  This is used to prefetch pages in a gatsby site which results in incredible performance improvements.  So our logic just got a little more complex because not only do we need to check for whether there is a URL, we also need to determine if the URL is internal or external.  If it is internal, we will use a <Link> component, otherwise, we will use a traditional <a> element.  Let’s see how we can make this work.  In an effort to keep this example short and to the point, I am excluding some pieces you normally would see in a real gatsby component.  This would typically be done in a button.js file.

import React from 'react';
import { Link } from 'gatsby';

const Button = ({
  children,
  to,
  href,
  ...props
}) => {

  // If the `to` prop exists, return a link.
  if (to) {
    return (
      <Link
        className='button button--link'
        to={to}
      >
        {children}
      </Link>
    );
  }

  // if the `to` prop does not exist but `href` does, return a <a> element.
  if (href) {
    return (
      <a
        className='button button--link'
        href={href}
        target="_blank"
        rel="noopener noreferrer"
      >
        {children}
      </a>
    );
  }

  // If the `to` or `href` props do not exist, return a <button> element.
  return (
    <button
      aria-expanded={props['aria-expanded'] || null}
      className='button'
      {...props}
    >
      {children}
    </button>
  );
};

export default Button;


If a to prop is identified, we return a <Link> Gatsby component. As we can see this is a more complex piece of code but ultimately does the same thing as when we wrote it in Twig.  The difference here is that we are detecting three scenarios to determine which element to return:

  • If href prop is detected instead of to, we return a traditional <a> element with target="blank" and rel="noopener noreferrer", According to Gatsby docs, routing to target="_blank" is the equivalent of an external page and Gatsby <Link> cannot be used. Docs here: https://www.gatsbyjs.com/docs/linking-between-pages/#using-a-for-external-links
  • Finally, if neither to nor href are detected, we return a <button> element.

In Closing

Some may not see the need to go to this extreme to simply decide whether we need a button or anchor element.  However, there are many reasons why this is something you should consider as this could impact accessibility, UX, and in the case of Gatsby, even your site performance.