Introduction
I was doing a design revamp for this blog, and my designer came up with a button that looks like this.
Looks great, right?
I thought to myself, that would be simple enough to make: a button, with a transparent and blur background, and add in a gray-ish gradient border around it. Probably something like border: 1px solid linear-gradient()
would definitely work.
Spoiler: I was wrong
Border Gradient Attempt
So I set my goal, to build a proof of concept.
I whipped out a simple codepen, and created a button with a linear gradient border.
button {
all: unset;
font-family: sans-serif;
font-size: 2rem;
padding: 1rem 2rem;
background: #172554;
color: white;
border: 1px solid linear-gradient(to bottom right, red, orange, yellow, green, blue, indigo, violet);
}
That must be the most colorful gradient known to man. I was confident that it would work.
It must be a dry season because I can’t see any rainbow!
Yeah, turns out linear-gradient does not work with border.
Stacked Element Method
As a seasoned problem solver, I went to google, searched “How to make a gradient border in CSS”, and found an interesting method:
You basically create two divs, first is for the rainbow background, and second one is the button text itself. It sounds like a great idea.
I updated my code using a pseudo element as a background.
button {
all: unset;
position: relative;
font-family: sans-serif;
font-size: 2rem;
padding: 1rem 2rem;
background: #000;
color: white;
/* formula for 2 stacked radius is: (radius - (border-width / 2)) */
border-radius: calc(12px - (2px / 2));
}
button::before {
content: '';
position: absolute;
/* this is the border width */
inset: -2px;
z-index: -1;
background: linear-gradient(
to bottom right,
red,
orange,
yellow,
green,
blue,
indigo,
violet
);
border-radius: 12px;
}
IT WORKS!
The code looks a bit complicated, but it does the job. I was really happy about it.
Adding a transparent background
I continued my work by adding the transparent and blurred background.
button {
background: rgba(0, 0, 0, 0.1);
backdrop-filter: blur(1px);
}
And it looks like this:
Remember that we have a rainbow div behind? Introducing a translucent background means we will see the rainbow.
If you think that maybe we can try making a rainbow background with a hollow transparent background in the middle, that would be called a gradient border 🙃. So we're back to square one.
Ultimate Solution with Mask Exclude
So I did some more digging, and found the perfect solution that can cater all cases.
There’s a new property that’s recently gained a major support across browsers which is mask-composite.
According to CSS Tricks, we can remove a certain part of element by creating mask with 2 elements on top of each other, and use mask-composite exclude.
That means, if we create two boxes with one smaller than the other, we can have a mask that can show anything we like.
With mask, the element's background will be reflected. This even includes background-image.
Final Code
This is how the final code looks like!
button {
all: unset;
position: relative;
font-family: sans-serif;
font-size: 1rem;
padding: 1rem 2rem;
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(2px);
color: white;
border-radius: 12px;
}
button::before {
content: '';
position: absolute;
z-index: -1;
inset: 0px;
border-radius: inherit;
/* this is the border width */
padding: 1px;
background: linear-gradient(
to bottom right,
#171717 0%,
#525252 62%,
#171717 100%
);
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
}
Reference: https://stackoverflow.com/a/51496341
Final Words
There you have it! A working set of gradient border with translucent background. With this new widely-supported property, we can create anything that comes to mind.
Including something like this:
ps: I can’t open-source this because it’s from work ✌️. But it's using the same method!