Everything you didn’t know you need to know about buttons
Tags: html, css, javascript, react
Published: 2023-01-31 15:19:03

Show you use an anchor tag, a button tag, or something else entirely (a div??) for clickable elements in HTML?

```jsx
// 🚩
export function MyButton() {
  return <div onClick={...}>Click me</div>
}
//❓
export function MyButton() {
  return <button onClick={...}>Click me</button>
}
//❓
export function MyButton() {
  return <a onClick={...}>Click me</a>
}
```

The answer is surprisingly nuanced, and some of it might surprise you.

## The problem with divs

Let’s start by making one thing clear - you should not use `div`s for clickable elements (at least in *99%* of cases). But why?

Simply put, a div != a button. A div is simply a [generic container](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div), and is missing a number of qualities that a properly clickable element should have, such as:

- Divs are not [focusable](https://allyjs.io/what-is-focusable.html), for instance your `tab` key will not focus a div like it will for any other button on your device
- Screen readers and other assistive devices don’t recognize divs as clickable elements
- Divs do not translate certain key inputs, like space bars or return keys, to clicks when focused

Now, you can work around some of these issues with a few attributes like [`tabindex="0"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) and [`role=”button”`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/button_role):

```jsx
// 🚩 Trying to make a div *mostly* behave like a button...
export function MyButton() {
  function onClick() { ... }
  return (
    <div
      className="my-button"
      tabindex="0" // Makes the div focusable
      role="button" // Hint to screen readers this is clickable
      onClick={onClick}
      onKeydown={(event) => {
        // Listen to the enter and space keys when focused and call the
        // click handler manually
        if (event.key === "Enter" || event.key === "Space") {
          onClick()
        }
      }}
    >
      Click me
    </div>
  )
}
```

Oh yeah, and we need to make sure to style the focused state so there is user feedback that this element was focused too. And we must make sure this passes all [accessibility concerns](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus#accessibility_concerns), like:

```css
.my-button:focus-visible {
  outline: 1px solid blue;
}
```

This is becoming a lot of work to try and chase down all of the nuanced (but critical) behaviors of buttons and implement it all manually. 

But we’re in luck, because there is a better way (*most of the time*)!

### The `button` tag to the rescue

The beauty of the `button` tag is it behaves just like any other `button` on your device, and is exactly what users and accessibility tools expect. 

It is focusable, accessible, keyboard inputable, has compliant focus state styling, the works!

```jsx
// ✅
export function MyButton() {
  return (
    <button onClick={...}>
      Click me
    </button>
  )
}
```

But, buttons come with a couple problems we need to be aware of.

### Problems styling `button`s

The biggest annoyance I’ve always had with buttons is styling them. 

For instance, if you simply want to give a button a nice light purple background:

```html
<button class="my-button">
  Click me
</button>
<style>
  /* 🤢 */
  .my-button { 
    background-color: purple; 
  }
</style>
```

You will end up with this atrocity:

![A very ugly button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d155192h9oghehp5efh1.png)

That looks right out of Windows 95.

Yes, browsers try to force all kinds of weird styling to button elements and applying your own styles just makes a mess.

This is why we all love divs. They come with no added styling or behavioral baggage. They work and look exactly as expected, every time.

Now you could say, oh! `appearance: none` will reset the appearance! But no, that does not quite do what you think.

```html
<button class="my-button">
  Click me
</button>
<style>
  .my-button { 
    appearance: none; /* 🤔 */
    background-color: purple; 
  }
</style>
```

Our monster still remains:

![A very ugly button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d155192h9oghehp5efh1.png)

### Fixing `button` styling

That’s right, we have to actually reset properties *line by line*:

```css
/* ✅ */
button {
  padding: 0;
  border: none;
  outline: none;
  font: inherit;
  color: inherit;
  background: none
}
```

*This* will now finally give us a button that looks and behaves like a div, with one additional benefit that it still uses the browsers default focus styling.

An alternative option you have is to use [`all: unset`](https://developer.mozilla.org/en-US/docs/Web/CSS/all) to get back to no special styling in one simple property:

```css
/* ✅ */
button { all: unset; }
button:focus-visible { outline: 1px solid var(--your-color); }
```

Just don’t forget to add your own focus state (e.g. an outline with your brand color, assuming it has sufficient contrast), and you’re solid.

### Fixing form behaviors of `button`s

There is one last issue to be aware of when using the `button` tag. Any button inside of a `form` by default is treated as a submit button, and when clicked will *submit the form*. What??

```jsx
function MyForm() {
  return (
    <form onSubmit={...}>
      ...
      <button type="submit">Submit</button>
      {/* 🚩 Clicking "Cancel" will also submit the form! */}
      <button onClick={...}>Cancel</button>
    </form>
  )
}
```

That’s right, the default `type` attribute for buttons is `submit`. Yeah, it’s weird. And annoying.

To fix this, unless your button *actually* is mean to submit a form, always add `type="button"` to it, like so:

```jsx
export function MyButton() {
  return (
    <button 
      type="button" // ✅
      onClick={...}>
       Click me
    </button>
  )
}
```

And now our buttons will no longer attempt to find their closest `form` parent and submit it. Whew, that almost got weird.

## Linking to other pages

Here is the one big exception to our rule. We do *not* want to use buttons for links to other pages:

```jsx
// 🚩
function MyLink() {
  return (
    <button
      type="button"
      onClick={() => {
        location.href = "/"
      }}
    >
      Don't do this
    </button>
  )
}
```

A few problems with buttons that use click events to link to pages:

- They are not crawlable (so very bad for SEO)
- Users cannot open this link in new tabs or windows (e.g. with cmd/ctrl click, right click “open in new tab”, etc)

As a result, let’s not use buttons for navigation. That’s where we want our good ole `a` tag.

```jsx
// ✅
function MyLink() {
  return (
    <a href="/">
      Do this for links
    </button>
  )
}
```

And the best part, they have all of the above mentioned benefits of buttons (accessible, focusable, keyboard inputtable, etc) *and* they don’t come with a bunch of funky styling!

But, before you say *waaait, if an `a` has the benefits of a button, without all the funky styling, should we not just use them for anything clickable and save ourselves some headaches?*

```jsx
// 🚩
function MyButton() {
  return (
    <a onClick={...}>
      Do this for links
    </a>
  )
}
```

Well, no. That’s because an `a` tag *without* an `href` no longer behaves like a button. That’s right, it only has the full button behaviors, such as being focusable, when it also has an `href` with a value. 

So, let’s be sure to stick to buttons for buttons, and anchors for links.

## Putting it all together

One pattern I quite like is to encapsulate these rules in a component, so you can simply use your `MyButton` component and if you provide a URL, it becomes a link, otherwise is a button, like so:

```jsx
// ✅
function MyButton(props) {
  if (props.href) {
    return <a class="my-button" {...props} />
  }
  return <button type="button" class="my-button" {...props} />
}

// Renders an <a href="/">
<MyButton href="/">Click me</MyButton>

// Renders a <button type="button">
<MyButton onClick={...}>Click me</MyButton>
```

This way, we can have a consistent developer experience and user experience, regardless of if the purpose of the button is a click handler or a link to another page.

## Conclusion

Ok, that was a lot! In short: for links, use an anchor tag with the `href` property, for all other buttons use the `button` tag with `type="button"`. 

## About me

Hi! I'm [Steve](https://twitter.com/Steve8708?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor), CEO of [Builder.io](https://www.builder.io/).

We make a way to drag + drop with **your components** to create pages and other CMS content on your site or app, [visually](https://www.builder.io/blog/headless-cms-workflow).

So this:

```tsx
import { BuilderComponent, registerComponent } from '@builder.io/react'
import { Hero, Products } from './my-components'

// Dynamically render compositions of your components
export function MyPage({ json }) {
  return <BuilderComponent content={json} />
}

// Use your components in the drag and drop editor
registerComponent(Hero)
registerComponent(Products)
```

Gives you this:

![Gif of Builder.io](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/au5t8ti43cci5n6t6cfb.gif)