How does border-image work with linear-gradient?

TL;DR

When using a gradient, the size of the image is the size of the element. The border-image-width will define the 9 regions where we will place the slices (if not defined, border-width is used). The border-image-slice will consider the initial image to create the slices. A unitless value is considered as a pixel value and a percentage value is resolved against the size of the element.

To have a perfect result we should have the slices equal to regions, and for this we need to have the border-image-slice equal to border-image-width (or border-width) when used without a unit. Using a percentage, the computed value should be the same.

In your case 80 in the slice means 80px and you have a border of 5em which is 5x16px = 80px.


Let's take an easy example.

div {
  width: 100px;
  height: 100px;
  display: inline-block;
  border: 10px solid transparent;
}

div.box {
  background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}

div.border {
  border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) 50 fill;
  border-image-width: 50px;
  background: red;
}
<div class="box"></div>

<div class="border"></div>

In the above I tried to create two divs with the same output using different techniques (background and border). Notice how in the second example I use the keyword fill and I specified a border-image-width different from the border width and used a slice equal to that border width.

Note that 50 in the slice is considered as pixels here since we're dealing with a non-vector image (a gradient).

Numbers represent pixels in the image (if the image is a raster image) or vector coordinates (if the image is a vector image). ref

Let's remove the fill keyword:

div {
  width: 100px;
  height: 100px;
  display: inline-block;
  border: 10px solid transparent;
}

div.box {
  background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}

div.border {
  border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) 50;
  border-image-width: 50px;
  background: red;
}
<div class="box"></div>

<div class="border"></div>

The fill keyword, if present, causes the middle part of the border-image to be preserved. (By default it is discarded, i.e., treated as empty.) ref

By default, the border image is not painted in the middle but only in the border. We can clearly see from the example that we have 50px on each side, with our custom border likewise defined by border-image-width.

And if we don't specify border-image-width the default value is 1 which means:

Numbers represent multiples of the corresponding computed border-width.

So either we explicitely specify border-image-width or we simply use border-width as reference. In most cases only border-width is needed since in most cases we want to only cover the border area and not more.

Now the slice will split the image in 9 parts:

This property specifies inward offsets from the top, right, bottom, and left edges of the image, dividing it into nine regions: four corners, four edges and a middle

enter image description here ref

Here are the steps that will better show how it's done for our example:

div {
  width: 100px;
  height: 100px;
  border: solid 10px transparent;
  display: inline-block;
  position: relative;
}

div:before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  right: -10px;
  bottom: -10px;
  background:
    linear-gradient(green,green) left 0 top    50px/100% 1px no-repeat,
    linear-gradient(green,green) left 0 bottom 50px/100% 1px no-repeat,
    linear-gradient(green,green) top 0 left  50px/1px 100% no-repeat,
    linear-gradient(green,green) top 0 right 50px/1px 100% no-repeat;
}

div.box {
  background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}

div.border {
  border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) 50;
  border-image-width: 50px;
  background: red;
}
<div class="box"></div>

<div class="border"></div>

The left image is the original one that we divide into 9 parts, then we put each one in the 9 regions of the right one. The middle one is empty because we didn't use fill. In this example we will notice nothing because the slices fit the regions.

Now let's reduce the slice to 25:

div {
  width: 100px;
  height: 100px;
  border: solid 10px transparent;
  display: inline-block;
  position: relative;
}

div.box:before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  right: -10px;
  bottom: -10px;
  background:
    linear-gradient(blue,blue) left 0 top    25px/100% 1px no-repeat,
    linear-gradient(blue,blue) left 0 bottom 25px/100% 1px no-repeat,
    linear-gradient(blue,blue) top 0 left  25px/1px 100% no-repeat,
    linear-gradient(blue,blue) top 0 right 25px/1px 100% no-repeat;
}

div.border:before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  right: -10px;
  bottom: -10px;
  background: 
    linear-gradient(green, green) left 0 top 50px/100% 1px no-repeat, 
    linear-gradient(green, green) left 0 bottom 50px/100% 1px no-repeat, 
    linear-gradient(green, green) top 0 left 50px/1px 100% no-repeat, 
    linear-gradient(green, green) top 0 right 50px/1px 100% no-repeat;
}

div.box {
  background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}

div.border {
  border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) 25;
  border-image-width: 50px;
  background: red;
}
<div class="box"></div>

<div class="border"></div>

It's a bit tricky but the same logic applies. From the left image we cut using 25px from each side to get our 9 portions that we will put in the right one, where the border-width is still the same (50px). You can clearly notice how the parts in the corners are simply scaled and the edges are distorted.

In each corner we are using a 25px 25px image inside a 50px 50px area and in the top edge for example we are using a 60px 25px image inside a 10px 50px area.

You can also define different values for each side to have something like below:

div {
  width: 100px;
  height: 100px;
  border: solid 10px transparent;
  display: inline-block;
  position: relative;
}

div.box:before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  right: -10px;
  bottom: -10px;
  background: 
    linear-gradient(blue, blue) left 0 top 20px/100% 1px no-repeat, 
    linear-gradient(blue, blue) left 0 bottom 30px/100% 1px no-repeat, 
    linear-gradient(blue, blue) top 0 left 20px/1px 100% no-repeat, 
    linear-gradient(blue, blue) top 0 right 60px/1px 100% no-repeat;
}

div.border:before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  right: -10px;
  bottom: -10px;
  background: 
    linear-gradient(green, green) left 0 top 50px/100% 1px no-repeat, 
    linear-gradient(green, green) left 0 bottom 50px/100% 1px no-repeat, 
    linear-gradient(green, green) top 0 left 50px/1px 100% no-repeat, 
    linear-gradient(green, green) top 0 right 50px/1px 100% no-repeat;
}

div.box {
  background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}

div.border {
  border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px);
  border-image-slice: 20 60 20 30;
  border-image-width: 50px;
  background: red;
}
<div class="box"></div>

<div class="border"></div>

Now it's more clear how we slice the image then we put them in the different region by scaling stretching them. It's also clear that the best value is to have the slices in all the sides equal to the border-width which is the case in your example since 5em is 5x16px = 80px thus a slice of 80


From the specification we can also read:

The regions given by the border-image-slice values may overlap. However if the sum of the right and left widths is equal to or greater than the width of the image, the images for the top and bottom edge and the middle part are empty, which has the same effect as if a nonempty transparent image had been specified for those parts. Analogously for the top and bottom values.

If you specify a left and right slice bigger than the image width then logically you will get nothing to put on the top/bottom/middle part:

div {
  width: 100px;
  height: 100px;
  border: solid 10px transparent;
  display: inline-block;
  position: relative;
}

div.box:before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  right: -10px;
  bottom: -10px;
  background: 
    linear-gradient(blue, blue) left 0 top 20px/100% 1px no-repeat, 
    linear-gradient(blue, blue) left 0 bottom 30px/100% 1px no-repeat, 
    linear-gradient(blue, blue) top 0 left 60px/1px 100% no-repeat, 
    linear-gradient(blue, blue) top 0 right 60px/1px 100% no-repeat;
}

div.border:before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  right: -10px;
  bottom: -10px;
  background: 
    linear-gradient(green, green) left 0 top 50px/100% 1px no-repeat, 
    linear-gradient(green, green) left 0 bottom 50px/100% 1px no-repeat, 
    linear-gradient(green, green) top 0 left 50px/1px 100% no-repeat, 
    linear-gradient(green, green) top 0 right 50px/1px 100% no-repeat;
}

div.box {
  background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}

div.border {
  border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px);
  border-image-slice: 20 60 20 60;
  border-image-width: 50px;
  background: red;
}
<div class="box"></div>

<div class="border"></div>

The same logic applies to top/bottom too.

Here is an example where we will only have the corners:

div {
  width: 100px;
  height: 100px;
  border: solid 10px transparent;
  display: inline-block;
  position: relative;
}

div.box:before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  right: -10px;
  bottom: -10px;
  background: 
    linear-gradient(blue, blue) left 0 top 20px/100% 1px no-repeat, 
    linear-gradient(blue, blue) left 0 bottom 100px/100% 1px no-repeat, 
    linear-gradient(blue, blue) top 0 left 60px/1px 100% no-repeat, 
    linear-gradient(blue, blue) top 0 right 60px/1px 100% no-repeat;
}

div.border:before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  right: -10px;
  bottom: -10px;
  background: 
    linear-gradient(green, green) left 0 top 50px/100% 1px no-repeat, 
    linear-gradient(green, green) left 0 bottom 50px/100% 1px no-repeat, 
    linear-gradient(green, green) top 0 left 50px/1px 100% no-repeat, 
    linear-gradient(green, green) top 0 right 50px/1px 100% no-repeat;
}

div.box {
  background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}

div.border {
  border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px);
  border-image-slice: 20 60 100 60;
  border-image-width: 50px;
  background: red;
}
<div class="box"></div>

<div class="border"></div>

Using a percentage value will also give the same result. We simply need to find the reference and since we are dealing with a gradient, the size of the gradient is simply the size of the element. A slice of 50 in our example is equal to 41.666% since the width/height is equal to 100px 2 * 10px = 120px

div {
  width: 100px;
  height: 100px;
  border: solid 10px transparent;
  display: inline-block;
  position: relative;
}

div:before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  right: -10px;
  bottom: -10px;
  background: 
    linear-gradient(blue, blue) left 0 top 50px/100% 1px no-repeat, 
    linear-gradient(blue, blue) left 0 bottom 50px/100% 1px no-repeat, 
    linear-gradient(blue, blue) top 0 left 50px/1px 100% no-repeat, 
    linear-gradient(blue, blue) top 0 right 50px/1px 100% no-repeat;
}


div.box {
  background: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) border-box, red;
}

div.border {
  border-image: repeating-linear-gradient(45deg, #000, #000 5px, transparent 5px, transparent 10px) 41.666%;
  border-image-width: 50px;
  background: red;
}
<div class="box"></div>

<div class="border"></div>

In the next example I'm using px instead of em because I think is clearer.

This is the image used for the border image.

div{ width: 416px; height: 368px;
	background:repeating-linear-gradient(45deg, 
			#000, #000 1.5%, 
			transparent 1.5%, transparent 5%);
}
<div></div>

This image will be sliced in 9 squares something like a grid.

enter image description here

The image is from this article: border-image-slice

If the value for border-image-slice is 80 this means that the offset is 80, i.e. C1, C2, C3 and C4 size is 80/80. All C slices are used for the corners of the border image. The E1,E2,E3 and E4 are used to draw the edges.

If instead of 80 you are using 208 or 50% the border image will get corners but no edges because there is nothing left for the edges.

Next comes a demo where you can see the evolution of the slices on the image used to draw the border image. I've changed the width of the div at 300 because I wanted to see both the div with the border image and the image used for the border one next to the other. In this case the edges of the border image are disappearing at border-image-slice:150;

itr.addEventListener("input",()=>{
	let v = itr.value;
	border.style.borderImageSlice = v;
	itrspan.innerHTML = v;
	let d = `M${v},0v300M${300-v},300v-300M0,${v}h300M300,${300-v}h-300`
	thePath.setAttributeNS(null,"d",d)
})
div{display:inline-block;}

#border {
	box-sizing: border-box;
	position: relative;
	border: solid 5em #000;
	border-image: repeating-linear-gradient(45deg, 
			#000, #000 1.5%, 
			transparent 1.5%, transparent 5%);
	border-image-slice:80;
	padding: 2em;
	width: 300px; height: 300px;	
}

#image{
	width: 300px; height: 300px;
	background: repeating-linear-gradient(45deg, 
			#000, #000 1.5%, 
			transparent 1.5%, transparent 5%);}



input{width:300px;}
<input id="itr" type="range" min="0" max="300" value="80" ><span id="itrspan">80</span>
<br>


<div id="border"></div>
<svg id="image" viewBox="0 0 300 300">
	
<path id="thePath" fill="none" stroke="red" d="M80,0v300M220,300v-300M0,80h300M300,220h-300" />
</svg>