Button with border gradient


3 min read

Recently I had to do a button that had a gradient border. There are a couple of ways of doing this.

Set up

The initial HTML is just a button:


And then I've added some CSS to centre the button on the page, and to create a rainbow gradient. I've also set the font-size and padding to make the button nice and big. And make sure the cursor is a hand when you hover on it (my pet peeve is that buttons don't do that by default).

:root {
  --red: rgb(255, 0, 0);
  --orange: rgb(255, 165, 0);
  --yellow: rgb(255, 255, 0);
  --green: rgb(0, 128, 0);
  --blue: rgb(0, 0, 255);
  --indigo: rgb(75, 0, 130);
  --gradient: conic-gradient(var(--yellow), var(--green), var(--blue), var(--indigo), var(--red), var(--orange));

html, body {
  height: 100%;

  display: grid;
  place-content: center;

button {
  padding: 1em 2em;
  font-size: 2rem;
  cursor: pointer;

Gradient as a border

We can add the gradient as a border, using the CSS used to add images to your border.

button {
  background: white;
  border: 0.15em solid;
  border-radius: .25em;
  border-image-source: var(--gradient);
  border-image-slice: 1;

I've made the background white, as opposed to the button's default grey. And added a small border, as well as a border-radius.

Then border-image-source allows you to use an image in the border. Similar to the way you can add a background-image. Except that instead of giving it an image, we're giving it a gradient. Same as you do if you want a background gradient.

The clever part comes with the border-image-slice. Used with an image, it allows you to define how much of the image should be seen. With a gradient, if you leave this off, it adds the gradient at the corners. Adding border-image-slice: 1 means that the gradient fills the border.

And then it looks like this:

Although it looks fine, you might recall that the CSS included a border-radius. But the borders are not rounded. This is because you can't have a border-image with a border-radius. But there is an alternative method that will allow you both.

Gradient as a background

The alternative is to set the gradient as a background and then cover up everything apart from the edges.

We start with the same set up, but then add the background and borders:

button {
  border: none;
  border-radius: 0.25em;
  background: var(--gradient);

This gives us something that looks hard to read, but it is a button filled with a background, with rounded corners.

Then we can use a pseudo-element to cover it up:

button {
  position: relative;
  z-index: 1;

button::before {
  content: '';
  position: absolute;
  inset: 0.15em;
  background-color: white;
  z-index: -1;

We're making the button relatively positioned, so we can position the before element relative to it. inset: 0.15em means you want top, right, bottom and left all to be 0.15em. Which means that this background will leave a gap of 0.15em around it. Which makes it look like the gradient is a border.

Then we need the z-indexes to make sure that the text, which is on the button, is still visible above the white background pseudo-element.

And then we get this, which looks very similar to the last example, but with rounded corners:

Another difference

To those codepens I've added a hover/focus state that makes the border twice the size. In the first example the border grows outwards. In the second example the border grows inwards. This is because one is a border and the other is not. I could have increased the padding on the second one on hover/focus, which would have made it look the same, but how I've done it highlights the difference: the first button is bigger. They're the same size, but one has an additional border, the other doesn't.