Border-image: Nine Slices, Infinite Possibilities
CSS border-image has been around for nearly two decades, yet it still flies under the radar compared to other layout tools. The property's reputation for awkward syntax and unclear practical use cases likely keeps many developers away. But once you understand how its slicing model works, border-image becomes a surprisingly versatile tool for highly graphical designs.
Breaking Down the Shorthand
Most tutorials start with the full shorthand, which can look intimidating:
border-image: \[source\] [slice]/\[width]/[outset\] [repeat]
That single declaration packs several sub-properties. To actually understand what's happening, it helps to look at each piece individually.
Choosing a Source
The first step is defining where the image comes from, via border-image-source:
border-image-source: url('/img/scroll.png');
Your source can be an external SVG file:
border-image-source: url('/img/scroll.svg');
A data URI (though this is generally discouraged for SVG, since both SVG and HTML are XML-based):
border-image-source: url('data:image/svg+xml;base64,…');
Or you can inline the SVG markup directly in the URL, saving an HTTP request:
border-image-source: url('data:image/svg+xml;utf8,…');
You can even skip images entirely and use a CSS gradient as the border source:
border-image-source: conical-gradient(…);
One useful detail to remember: the browser draws the border image above the element's background and box-shadow, but below its content.
Slicing the Image
The core concept behind border-image is that invisible cut-lines divide your source into nine regions — four corners, four edges, and a center. Those regions then map to the corresponding parts of the element's border.

The border-image-slice property controls where those cut-lines fall. You can define the inset from each edge, and the values can be uniform:
border-image-slice: 65
Combine vertical and horizontal values:
border-image-slice: 115 65;
Or set all four distances in clockwise order (top, right, bottom, left):
border-image-slice: 65 65 115 125;
Whatever ends up in the top-left slice lands in the element's top-left corner, and so on.

For bitmap sources, you don't need units — the browser assumes pixels. SVG sources work differently because of the viewBox, so it's wise to also declare the image's height and width:
<svg height="600px" width="600px">…</svg>
Of course, none of this works without an actual border width. Always remember to set it, or there's nowhere for the image to appear:
border-image-width: 65px 65px 115px 125px;
Using the Center
By default, the nine-slice model discards the center region. But that area doesn't have to go to waste. Adding the fill keyword to your border-image-slice declaration tells the browser to use the center piece inside the element as well:
border-image-slice: 65px 65px 115px 125px fill;
That single keyword unlocks a whole class of designs where the border image also serves as a textured background for the element's interior.
Controlling the Edges
Once the corner slices are locked down, the four edges between them follow the same slicing logic: the top slice of the source image fills the top border, the right slice fills the right border, and so on. Since a border's length is rarely fixed, the border-image-repeat property determines how those edge slices adapt.

Stretch: The edge slice is scaled to fill the entire length. This works best when the graphic is flat or has a uniform texture; even a 65px slice can cover a much larger area without visible artifacts.
border-image-repeat: stretch;
Repeat: The edge slice is tiled at its original size. This is the right choice when the image has detail that would look warped by stretching.
border-image-repeat: repeat;
Round: A tiled repeat, but the browser scales the slices slightly so that only a whole number of them fits within the edge. This is useful when the pattern needs to connect seamlessly from one tile to the next.
border-image-repeat: round;
Space: Like round, only whole tiles are displayed, but the browser inserts gaps between them rather than resizing the tiles themselves.
border-image-repeat: space;
You can assign a different keyword to each edge by declaring them in order:
border-image-repeat: stretch round;
Expanding Beyond the Border Box
The border-image-outset property pushes the rendered border image outward, beyond the element's border-box. A single value applies uniformly, as with this 10px outset:
border-image-outset: 10px;
Naturally, the four sides can each have their own outset value:
border-image-outset: 20px 10px;
/* or */
border-image-outset: 20px 10px 0;
Use Case: A Retro Brand Redesign
For a recent project, I was tasked with building a highly graphical website for Mike Worth, an Emmy-winning video game composer with a love for 1990s animation like Disney's Duck Tales. The design needed to be visually bold but also performant, especially on mobile. In many of the components, border-image turned out to be the leanest way to achieve a complex look.
Decorative Buttons
The first component was a button styled like a chipped stone tablet. Rather than slicing the graphic into multiple background layers, I drew an SVG of the shape and applied it as a single border image.

button {
border-image-repeat: stretch;
border-image-slice: 10 10 10 10 fill;
border-image-source: url('data:image/svg+xml;utf8,…');
border-image-width: 20px;
}
I set border-image-repeat to stretch on all edges. Combined with the fill keyword for the center slice, the button scales cleanly to fit whatever content it contains.
Long-Form Content as a Scroll
To extend the cartoon theme into the site's articles, I designed the text pages to look like a paper scroll. My first instinct was to piece the effect together from three SVG files: one for the top, one for the repeating middle, and one for the bottom. This involved a CSS background image on the article itself plus two pseudo-elements to anchor the top and bottom caps.

<article>
<!-- ... -->
</article>
article {
padding: 10rem 8rem;
box-sizing: border-box;
/* Scroll middle */
background-image: url('data:image/svg+xml;utf8,…');
background-position: center;
background-repeat: repeat-y;
background-size: contain;
}
article:before {
display: block;
position: relative;
top: -30px;
/* Scroll top */
content: url('data:image/svg+xml;utf8,…');
}
article:after {
display: block;
position: relative;
top: 50px;
/* Scroll bottom */
content: url('data:image/svg+xml;utf8,…');
}
That solution worked, but managing three assets and two pseudo-elements was clunky. The border-image approach is far more elegant: a single SVG defines the complete scroll shape, with cut-lines placed to separate the roll at the top and bottom from the main text area.


Applying this one image to the element's border—with the top and bottom slices set to stretch and the sides set to round—yields an effect that flexes with any viewport width and any amount of content, with no extra markup.
article {
border-image-slice: 150 95 150 95 fill;
border-image-width: 150px 95px 150px 95px;
border-image-repeat: stretch round;
border-image-source: url('data:image/svg+xml;utf8,…');
}
A Hidden Use: Hover Overlays
One of the subtler applications for border-image came up in the site's home page hero. The design features a foreground SVG of Worth's orangutan mascot layered over a zooming background graphic.

<section>
<!-- content -->
<div>...</div>
<!-- ape -->
<div>
<svg>…</svg>
</div>
</section>
The section is set as the positioning context, and a pseudo-element holds the animated background. I added a simple spin animation and then used a CSS mask to fade its edges.
section {
position: relative;
}
section:before {
content: "";
position: absolute;
z-index: -1;
background-image: url('data:image/svg+xml;utf8,…');
background-position: center center;
background-repeat: no-repeat;
background-size: 100%;
}
@keyframes spin-bg {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
section:before {
animation: spin-bg 240s linear infinite;
}
section:before {
mask-image: radial-gradient(circle, rgb(0 0 0) 0%, rgb(0 0 0 / 0) 60%);
mask-repeat: no-repeat;
}
To add interactivity, the graphic needed to shift opacity and take on a color tint on hover. A quick method for that color shift is applying a gradient overlay not with a pseudo-element, but with border-image itself. First, I gave the element a default opacity and a transition.
section:before {
opacity: 1;
transition: opacity .25s ease-in-out;
}
Then, on hover, the opacity drops to .5.
section:hover::before {
opacity: .5;
border-image: fill 0 linear-gradient(rgba(0,0,255,.25),rgba(255,0,0,1));
}
Look closely at that border-image declaration. The slice value is 0, meaning the outer eight regions are empty, while the fill keyword tells the browser to render the middle region. A linear gradient from blue to red serves as the source. The browser paints this gradient above the element's background, but behind its content—exactly where you'd want a tinted overlay.
Why border-image Deserves Another Look
Often thought of as a tool for ornamental frames, border-image is far more versatile than its reputation suggests. The combination of slicing, repeating, and outsetting handles frame-like decorations, but the technique also scales down to button skins and scales up to dynamic overlays with very little CSS.
In this project, relying on border-image meant fewer HTTP requests, no extra HTML hooks, and generally smoother performance on devices with limited resources.
Hint: The full set of live examples is available on CodePen. Worth's site is slated to launch in April 2025.



