128

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?

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
paulwilde
  • 1,329
  • 2
  • 11
  • 9

11 Answers11

127

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

                                   screenshot

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.

Camilo Martin
  • 37,236
  • 20
  • 111
  • 154
  • 5
    Works flawlessly in WebKit. – jorisw Jun 09 '15 at 15:36
  • 3
    @jorisw Yeah, the point is that this works in all the browsers. – Camilo Martin Jun 10 '15 at 13:30
  • 1
    nice update to this old problem, i came back and was pleased to find a new solution :) – RozzA Nov 26 '15 at 03:04
  • while it still technically isn't using border-image, it does achieve the intended effect. Nicely done! – mix3d Apr 13 '16 at 18:49
  • 1
    If you put it in a
    with background, it doesn't work. see fiddle: http://jsfiddle.net/osw11t96/344/
    – bolshas Sep 04 '16 at 17:35
  • 4
    @bolshas added `position:relative;z-index:-1` to the background and presto: http://jsfiddle.net/osw11t96/346/ – Camilo Martin Sep 07 '16 at 14:43
  • This is old, but no it does not work well. I would like to do it without an additional background color – Caleb Prenger Oct 13 '16 at 20:33
  • @CalebPrenger what do you mean, an additional background color? You mean you want the div to be transparent in the center and have only a gradient on the border? – Camilo Martin Oct 15 '16 at 10:38
  • 8
    Works kinda, as long as you don't have transparency in your background color =/ – daleyjem Mar 17 '17 at 21:43
  • @CamiloMartin You can remove `background-clip: padding-box;` and `border: 4px solid transparent;` from `.grounded-radiants`. These styles are redundant. – Vadim Ovchinnikov Jun 22 '17 at 08:19
  • 1
    Nice try. But having to add position:relative;z-index:-1 to the parent make it practically unusable. – v1nce Aug 30 '17 at 12:31
  • @v1nce Not really. You can have Parent with position relative, child div with the content(position relative) and antother child element with position absolute. Then it should work with background set on either of them. Z-index would still be used tho. – IvRRimUm Feb 26 '18 at 20:48
  • 1
    Doesn't work on self-closing tags such as input elements, unfortunately. – Matt Huggins Jul 22 '18 at 03:06
  • Note; using with Bootstrap 4 requires `display: inline;` and `z-index: 1;` on the main element (`.grounded-radiants`). – Seth Reeser Nov 05 '18 at 16:25
  • 1
    what happens when you have a transparent background and really needing a BORDER ? you don't get a border, you get a background – Oneezy Nov 22 '18 at 17:36
  • 4
    Doesn't work in current Safari/Webkit (12.0.3) or Chrome/Blink (73.0.3683.86) – Nilloc Apr 17 '19 at 20:21
  • 2
    I found an example of this approach working here: https://css-tricks.com/gradient-borders-in-css/ – Eric Johnson Dec 17 '19 at 16:28
  • Warning ! Since 2019, this solution does not work anymore ! Be aware, best answer available on css-tricks.com for now... – Emidomenge Jan 07 '20 at 10:41
124

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

vsync
  • 118,978
  • 58
  • 307
  • 400
alphazwest
  • 3,483
  • 1
  • 27
  • 39
  • 27
    This is the best answer. If you are using `padding` on your element, change the `background-clip` property to `padding-box, border-box`. – igneosaur Aug 06 '19 at 12:47
  • 8
    this works with a known solid background, but it's useless when you need a transparent space between the colored border and the content – Soldeplata Saketos Jan 03 '21 at 08:38
  • why linear-gradien(white, white) is necessary ? – allan.simon Apr 21 '21 at 21:35
  • 2
    @allan.simon That `linear-gradient(white, white)` is to add background to the box itself while the `linear-gradient(green, gold)` is for the border. Change the values in `linear-gradient(white, white)` to some other color and see the results yourself. –  Jun 12 '21 at 07:41
39

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

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
33

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.

Shauna
  • 9,495
  • 2
  • 37
  • 54
  • Yeah, I assumed it wasn't possible, but just wanted to make sure. Image it is. – paulwilde Apr 18 '11 at 18:39
  • 1
    Gerben has a potential work-around in his answer, though it does add some extrenuous markup. – Shauna Apr 19 '11 at 20:30
  • It sounds like nonsense to me. Browsers can compute (the mask and colors for) a border-radius:10px; border-top:10px dotted blue; border-left:4px groove green; but they won't be able to apply the same mask on a border-image ? – v1nce Aug 30 '17 at 12:43
  • 1
    This question is 6 years old at this point. While it's still not part of the spec AFAIK (so even if/though browsers could, they probably don't), things do evolve and it may be more feasible now and the spec may change. That said, keep in mind that things that look simple aren't always so easy to actually do, especially when technical debt is involved. – Shauna Aug 30 '17 at 12:49
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>
Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
1

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

  • 1
    this works really nicely if you have a solid-color background to go against, but in the interest of having rounded border-image against a changing background (such as a scrolling page on a static bg) this will not work. – RozzA Feb 09 '14 at 23:54
1

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>
1

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.

Gerben
  • 16,747
  • 6
  • 37
  • 56
  • I'd add that instead of adding such markup on the HTML document, one could do it using JavaScript. That way you get the best of both worlds. – Gui Prá Apr 20 '11 at 14:15
  • 1
    once you figure out what he is saying, this is a simple & obvious way to do it (adding extra markup however) – RozzA Feb 09 '14 at 23:53
-1

You could use the CSS clip-path property.

.rounded-border-image {
    --border-image: linear-gradient(to bottom, orange, skyblue, magenta);
    --border-radius: 4px;
    background-image: var(--border-image);
    background-origin: border-box;
    border-color: transparent;
    border-radius: var(--border-radius);
    border-style: solid;
    border-width: 4px;
    clip-path: inset(0% 0% 0% 0% round var(--border-radius));
}

Which looks something like this when used around an img element of a plain, red, 40x40 image.

.rounded-border-image {
    --border-image: linear-gradient(to bottom, orange, skyblue, magenta);
    --border-radius: 4px;
    background-image: var(--border-image);
    background-origin: border-box;
    border-color: transparent;
    border-radius: var(--border-radius);
    border-style: solid;
    border-width: 4px;
    clip-path: inset(0% 0% 0% 0% round var(--border-radius));
}
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAN0lEQVR42u3OQQ0AAAjEMM6/aMAECY8uE9B01f63AAICAgICAgICAgICAgICAgICAgICAgIC3jTyeE/ZxiLJ7wAAAABJRU5ErkJggg==" class="rounded-border-image">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAN0lEQVR42u3OQQ0AAAjEMM6/aMAECY8uE9B01f63AAICAgICAgICAgICAgICAgICAgICAgIC3jTyeE/ZxiLJ7wAAAABJRU5ErkJggg==" class="rounded-border-image" style="--border-radius: 16px;">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAN0lEQVR42u3OQQ0AAAjEMM6/aMAECY8uE9B01f63AAICAgICAgICAgICAgICAgICAgICAgIC3jTyeE/ZxiLJ7wAAAABJRU5ErkJggg==" class="rounded-border-image" style="--border-radius: 40px;">

Hope this helps!

Calculamatrise
  • 364
  • 3
  • 6
-2

Solution for Gradient Border.

This code work fine for me!

div#id123::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 31px;
    padding: 3px;
    width: 100%;
    height: 100px;
    background: linear-gradient(90deg, rgba(235,163,225,1) 0%, rgba(228,161,228,1) 13%, rgba(163,99,233,1) 47%, rgba(212,129,166,1) 62%, rgba(237,172,70,1) 89%, rgba(255,57,250,0.8733377659574468) 100%);
    -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 id = "id123">
<div class="" id="" data-de-type="input" data-title="input" data-delay="500" type="name" style="margin-top: 10px; outline: none; cursor: pointer; font-family: Nunito, Helvetica, sans-serif !important;" aria-disabled="false" data-google-font="Nunito">
<input type="name" placeholder="Enter Full Name" name="name" class="" data-type="extra">
</div>
<div class="" id="" data-de-type="input" data-title="input" data-delay="500" type="name" style="margin-top: 10px; outline: none; cursor: pointer; font-family: Nunito, Helvetica, sans-serif !important;" aria-disabled="false" data-google-font="Nunito">
<input type="name" placeholder="Enter Full Name" name="name" class="" data-type="extra">
</div>
<div class="" id="" data-de-type="input" data-title="input" data-delay="500" type="name" style="margin-top: 10px; outline: none; cursor: pointer; font-family: Nunito, Helvetica, sans-serif !important;" aria-disabled="false" data-google-font="Nunito">
<input type="name" placeholder="Enter Full Name" name="name" class="" data-type="extra">
</div>
</div>
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 05 '22 at 22:08
  • How is that different from the top voted answer? – Arusekk Oct 06 '22 at 13:56
  • this is a simple copy of my answer – Temani Afif Oct 08 '22 at 08:45
-5

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>