I'm styling an input field which has a rounded border (border-radius), and attempting to add a gradient to said border. I can successfully make the gradient and the rounded border, however neither work together. It's either rounded with no gradient, or a border with a gradient, but no rounded corners.

-webkit-border-radius: 5px;
-webkit-border-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b0bbc4), to(#ced9de)) 1 100%;

Is there anyway to have both CSS properties work together, or is this not possible?

Solution 1

This is possible, and it does not require extra markup, but uses an ::after pseudo-element.

                                   

It involves putting a pseudo-element with a gradient background below and clipping that. This works in all current browsers without vendor prefixes or hacks (even IE), but if you want to support vintage versions of IE, you should either consider solid color fallbacks, javascript, and/or custom MSIE CSS extensions (i.e., filter, CSSPie-like vector trickery, etc).

Here's a live example (jsfiddle version):

@import url('//raw.githubusercontent.com/necolas/normalize.css/master/normalize.css');

html {
    /* just for showing that background doesn't need to be solid */
    background: linear-gradient(to right, #DDD 0%, #FFF 50%, #DDD 100%);
    padding: 10px;
}

.grounded-radiants {
    position: relative;
    border: 4px solid transparent;
    border-radius: 16px;
    background: linear-gradient(orange, violet);
    background-clip: padding-box;
    padding: 10px;
    /* just to show box-shadow still works fine */
    box-shadow: 0 3px 9px black, inset 0 0 9px white;
}

.grounded-radiants::after {
    position: absolute;
    top: -4px; bottom: -4px;
    left: -4px; right: -4px;
    background: linear-gradient(red, blue);
    content: '';
    z-index: -1;
    border-radius: 16px;
}
<p class="grounded-radiants">
    Some text is here.<br/>
    There's even a line break!<br/>
    so cool.
</p>

The extra styling above is to show:

  • This works with any background
  • It works just fine with box-shadow, inset or not
  • Does not require you to add the shadow to the pseudo-element

Again, this works with IE, Firefox and Webkit/Blink browsers.

Solution 2

Working on this same problem. Came across a non-svg solution which is more succinct than others here:

div{
  width: 300px;
  height: 80px;
  border: double 1em transparent;
  border-radius: 30px;
  background-image: linear-gradient(white, white), 
                    linear-gradient(to right, green, gold);
  background-origin: border-box;
  background-clip: content-box, border-box;
}
<div></div>

This is not my own solution and has been taken from here: https://gist.github.com/stereokai/36dc0095b9d24ce93b045e2ddc60d7a0

Solution 3

Probably not possible, as per the W3C spec:

A box's backgrounds, but not its border-image, are clipped to the appropriate curve (as determined by background-clip). Other effects that clip to the border or padding edge (such as overflow other than visible) also must clip to the curve. The content of replaced elements is always trimmed to the content edge curve. Also, the area outside the curve of the border edge does not accept mouse events on behalf of the element.

This is likely because border-image can take some potentially complicated patterns. If you want a rounded, image border, you'll need to create one yourself.

Solution 4

Now we can use mask to easily achieve this while having transparency and responsiveness

.box {
  position: relative;
  padding: 20px 30px;
  margin: 5px;
  display: inline-block;
  font-size: 30px;
}

.box::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50px;
  padding: 10px; /* control the border thickness */
  background: linear-gradient(45deg, red, blue);
  -webkit-mask: 
    linear-gradient(#fff 0 0) content-box, 
    linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
<div class="box">
  Hello World
</div>

<div class="box">
  Hello World again
</div>
<div class="box">
  Hello World <br> two lines
</div>

More details: https://dev.to/afif/border-with-gradient-and-radius-387f

Solution 5

I would use SVG for this:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 220" width="100%" height="100%" preserveAspectRatio="none">
  <defs>
    <linearGradient id="gradient">
      <stop offset="0" style="stop-color:#0070d8" />
      <stop offset="0.5" style="stop-color:#2cdbf1" />
      <stop offset="1" style="stop-color:#83eb8a" />
    </linearGradient>
  </defs>
  <ellipse ry="100" rx="100" cy="110" cx="110" style="fill:none;stroke:url(#gradient);stroke-width:6;" />
</svg>

SVG can be used as separate file (preferred way) or like part of value of background (code below will work only in webkit-browsers):

div {
  width: 250px;
  height: 250px;
  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 220" width="100%" height="100%" preserveAspectRatio="none"><defs><linearGradient id="gradient"><stop offset="0" style="stop-color:#0070d8" /><stop offset="0.5" style="stop-color:#2cdbf1" /><stop offset="1" style="stop-color:#83eb8a" /></linearGradient></defs><ellipse ry="100" rx="100" cy="110" cx="110" style="fill:none;stroke:url(#gradient);stroke-width:6;" /></svg>');
}
<div></div>

For this to work in MS Edge and Firefox we should escape our markup after utf8, so we will be replacing double quotes " with single quotes ', # with %23 and % with %25:

div {
  width: 250px;
  height: 250px;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 220 220' width='100%25' height='100%25' preserveAspectRatio='none'><defs><linearGradient id='gradient'><stop offset='0' style='stop-color:%230070d8' /><stop offset='0.5' style='stop-color:%232cdbf1' /><stop offset='1' style='stop-color:%2383eb8a' /></linearGradient></defs><ellipse ry='100' rx='100' cy='110' cx='110' style='fill:none;stroke:url(%23gradient);stroke-width:6;' /></svg>");
  background-size: 100% 100%; /* Fix for Fifefox image scaling */
}
<div></div>

Solution 6

Solutions for transparent elements: working at least in Firefox.

There is actually one way I found without pseudo classes - but it only works for radial gradients:

body {
  background: linear-gradient(white, black), -moz-linear-gradient(white, black), -webkit-linear-gradient(white, black);
  height: 300px;
  
  }

div{
text-align: center;
  width: 100px;
  height: 100px;
  font-size:30px;
  color: lightgrey;
  border-radius: 80px;
  color: transparent;
  background-clip: border-box, text;
  -moz-background-clip: border-box, text;
  -webkit-background-clip: border-box, text;
  background-image: radial-gradient(circle,
      transparent, transparent 57%, yellow 58%, red 100%), repeating-linear-gradient(-40deg, yellow,
  yellow 10%, orange 21%, orange 30%, yellow 41%);
  line-height: 100px;
}
<body>
<div class="radial-gradient"> OK </div>
</body>

Getting a transparent element with pseudo classes I only found this way - ok it is not a gradient, but it is at least a multicolored striped border (looking like life-rings):

body {
  background: linear-gradient(white, black, white);
  height: 600px;
  }

div{
  position: absolute;
  width: 100px;
  height: 100px;
  font-size:30px;
  background-color:transparent;
  border-radius:80px;
  border: 10px dashed orange;
  color: transparent;
  background-clip: text;
  -moz-background-clip: text;
  -webkit-background-clip: text;
  background-image: repeating-linear-gradient(-40deg, yellow,
  yellow 10%, orange 11%, orange 20%, yellow 21%);
  text-align:center;
  line-height:100px;
}


div::after {
    position: absolute;
    top: -10px; bottom: -10px;
    left: -10px; right: -10px;
    border: 10px solid yellow;
    content: '';
    z-index: -1;
    border-radius: 80px;
    }
<body>
<div class="gradient"> OK </div>
</body>

with a svg (most satisfying in terms of variability but needs most codelines too):

body{
  margin: 0;
  padding: 0;
}

div {
  position: absolute;
  display: flex;
  align-items: center;
  left: 50%;
  transform: translateX(-50%);
  text-align: center;
}

span {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 100px;
  height: 100px;
  line-height: 105px;
  font-size:40px;
  background-clip: text;
  -moz-background-clip: text;
  -webkit-background-clip: text;
  background-image: repeating-linear-gradient(-40deg, yellow,
  yellow 10%, orange 11%, orange 20%, yellow 21%);
  color: transparent;
}

svg {
  fill: transparent;
  stroke-width: 10px; 
  stroke:url(#gradient);
  
}
<head>

</head>
<body>

<div>
<span>OK</span>
  <svg>
    <circle class="stroke-1" cx="50%" cy="50%" r="50"/>
    <defs>
      <linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="15%" gradientTransform="rotate(-40)" spreadMethod="reflect">
       
        <stop offset="0%" stop-color="orange" />
        <stop offset="49%" stop-color="orange" />
        <stop offset="50%" stop-color="yellow" />
        <stop offset="99%" stop-color="yellow" />

      </linearGradient>
  </defs>
  </svg>
  

</div>

</body>

Solution 7

This always works for me in WebKit, although its a bit tricky!

Basically you just make the border bigger then mask it out with bigger and smaller pseudo-element's borders : ).

.thing {
  display: block;
  position: absolute;
  left: 50px;
  top: 50px;
  margin-top: 18pt;
  padding-left: 50pt;
  padding-right: 50pt;
  padding-top: 25pt;
  padding-bottom: 25pt;
  border-radius: 6px;
  font-size: 18pt;
  background-color: transparent;
  border-width: 3pt;
  border-image: linear-gradient(#D9421C, #E8A22F) 14% stretch;
}
.thing::after {
  content: '';
  border-radius: 8px;
  border: 3pt solid #fff;
  width: calc(100% + 6pt);
  height: calc(100% + 6pt);
  position: absolute;
  top: -6pt;
  left: -6pt;
  z-index: 900;
}
.thing::before {
  content: '';
  border-radius: 2px;
  border: 1.5pt solid #fff;
  width: calc(100%);
  height: calc(100% + 0.25pt);
  position: absolute;
  top: -1.5pt;
  left: -1.5pt;
  z-index: 900;
}

http://plnkr.co/edit/luO6G95GtxdywZF0Qxf7?p=preview

Solution 8

What if you apply the gradient to the background. Than and add an extra div inside, with margin set to the old border-width and with a white background, and of course a borderradius. That way you have the effect of a border, but are actually using background, which is clipped correctly.

Solution 9

we need the background trans not white ..

div{
  width: 300px;
  height: 80px;
  border: double 1em transparent;
  border-radius: 30px;
  background-image: linear-gradient(transparent, transparent), 
                    linear-gradient(to right, green, gold);
  background-origin: border-box;
  background-clip: content-box, border-box;
}
<div></div>

Solution 10

Answer is Simple: To have the coloured, curved border, we first need to have a curved background. Then your problem will be solved (without a gradient situation):

.buttonBorder {
    -fx-border-color: red;
    -fx-background-color: white;
    -fx-background-radius: 30;
    -fx-border-radius: 30;
}