Profile photo of Travis Horn Travis Horn

Buttons with custom shapes

2018-01-18
Buttons with custom shapes

Did you know you can create hyperlinks on SVG shapes? The <a> tag is valid inside <svg>.

Most links (<a>) elements on the web are rectangles.

<a href="#">I'm a link.</a>

The text "I'm a link." appears in blue with a red rectangular
outline

And for most cases, this is perfectly fine. But what if you want a circular link?

A primitive solution

One common method is to create and link an image.

<a href="#">
  <img src="circle-link.webp" alt="I'm a link." />
</a>

A blue circle with white text inside that reads "I'm a link". A red square
outline appears around the circle.

But there are quite a few disadvantages to this technique:

  1. The linked area is actually still rectangular.
  2. The text is not selectable.
  3. An alt attribute is required for accessibility and indexing purposes.
  4. You can’t (without creating another image) change the visual style of this link on focus, hover, visited, etc.

The reason the linked area above is still rectangular is because images on the web — even if they don’t appear to be — are always rectangular.

In fact, this is how most elements are. div, p, table, h1, and pretty much any element you may be familiar with: as far as the browser is concerned, they are all rectangular by default.

<div>I'm a div.</div>

<p>I'm a paragraph</p>

<table>
  <tbody>
    <tr>
      <td>I'm</td>
      <td>a</td>
      <td>table.</td>
    </tr>
  </tbody>
</table>

<h1>I'm a heading.</h1>

The text "I'm a div.", "I'm a paragraph.", "I'm a table.", and "I'm a
heading." are visible. Red rectangular outlines are visible around each
sentence. Even more outlines are visible inside the "I'm a table." outline,
outlining every word.

A better solution

You can, however, define elements of other shapes! The solution comes to us from scalable vector graphics (SVG).

Here’s a circle:

<svg width="120" height="120">
  <circle cx="60" cy="60" r="60" />
</svg>

A black circle with a red outline hugging the circle

You can even change the color.

<svg width="120" height="120">
  <circle cx="60" cy="60" r="60" fill="#007BFF" />
</svg>

A circle with a blue fill

And you can place text over it.

<svg width="120" height="120">
  <circle cx="60" cy="60" r="60" fill="#007BFF" />

  <text
    x="60"
    y="60"
    fill="#FFFFFF"
    text-anchor="middle"
    alignment-baseline="middle"
  >
    I'm a circle.
  </text>
</svg>

A circle with a blue fill, with the white text "I'm a circle"
inside

And best of all, you can wrap both the circle and the text in an <a> tag.

<svg width="120" height="120">
  <a href="#">
    <circle cx="60" cy="60" r="60" fill="#007BFF" />

    <text
      x="60"
      y="60"
      fill="#FFFFFF"
      text-anchor="middle"
      alignment-baseline="middle"
    >
      I'm a link.
    </text>
  </a>
</svg>

The clickable part of the link is the actual circle (and text) element.

An animation of a blue circle labeled "I'm a link". A mouse cursor hovers on
and off of the link. The cursor changes to the pointer (indicating a link) only
when it's over the circle itself.

You can use CSS to change the style on hover.

a:hover circle {
  fill: #00b2ff;
}

Custom shapes

You can link other elements besides circle. If you want to define a custom shape, you could use the <path> element.

<svg width="120" height="120">
  <a href="#">
    <path
      d="M   0   0
             L 120   0
             L 120 120
             L  60  80
             L   0 120
             Z"
      fill="#007BFF"
    />

    <text
      x="60"
      y="50"
      fill="#FFFFFF"
      text-anchor="middle"
      alignment-baseline="middle"
    >
      Bookmark
    </text>
  </a>
</svg>
a:hover path {
  fill: #00b2ff;
}

An animation. A shape, filled in blue, reminiscent of a classic bookmark with
two "tails". The text "Bookmark" appears in white inside the shape. A mouse
cursor hovers on and off of the shape. When on the shape, the cursor changes to
a pointer (indicating a link).

With <path> the possibilities really are endless. You can create the exact shape you need by defining its points in the d attribute.

I tend to use the terms link and button interchangeably nowadays because, while one might be more semantically correct in a given situation, anything one can do, the other can do, too.

Say you want to create a custom-shaped button that submits a form.

First, build the form.

<form id="myForm" method="post">
  <input name="userName" placeholder="Your name" /><br />

  <!-- submit button goes here -->
</form>

Add a custom-shaped button using SVG.

<form id="myForm" method="post">
  <input name="userName" placeholder="Your name" /><br />

  <svg width="120" height="50">
    <a href="#" id="submitBtn">
      <path
        d="M   0  10
               L  50  10
               L  60   0
               L  70  10
               L 120  10
               L 120 120
               L  60  80
               L   0 120
               Z"
        fill="#007BFF"
      />

      <text
        x="60"
        y="30"
        fill="#FFFFFF"
        text-anchor="middle"
        alignment-baseline="middle"
      >
        Submit
      </text>
    </a>
  </svg>
</form>

Notice the id attributes on both the form and the link.

Now, wire up the button in JavaScript.

document.getElementById("submitBtn").addEventListener("click", () => {
  document.getElementById("myForm").submit();
});

The code above…

  1. Gets a reference to the submit button
  2. Listens for a click event
  3. When clicked, gets a reference to the form itself
  4. Submits it

You can see the code and try out this technique in this CodePen.

Here are some more articles you might like: