Let’s take a look at a quite new CSS feature: Gap Decorations. As its name suggests, it allows us to decorate gaps across different layout types (e.g., flexbox, grid, and multi-column). With a few lines of code, you can easily add decorative lines between elements.
At the time of writing, only Chrome and Edge fully support the features we will be using.
Take a classic grid of items and add the following CSS:
.grid {
/* basic grid setup... + */
gap: 20px;
rule: 4px solid darkred;
}Code language: CSS (css)
Within the gaps, we have solid lines with a 4px thickness. A similar syntax to the border property.
Let’s try with flexbox configuration and the following CSS:
.ul {
/* basic flexbox setup... + */
gap: 5px 1em;
column-rule: 2px solid purple;
}Code language: CSS (css)
This time, I specified the logic for the column only, which gives a line separator between the items.
The above is the very basic usage of this new feature, yet we were able to create decorations that would otherwise require complex code. Now, imagine all that we can do if we push this feature to its limit.
In this article, I will explore Gap Decorations in my own way, which means the “hacky” way! Decorating the gaps with lines is good, but let’s see what other cool stuff we can do.
Before we start, I invite you to check this playground created by the Microsoft team. It gives you a good overview of the different properties/values of this feature. There are also a bunch of nice demos featuring real-world use cases.
Fancy Underlines
Let’s start with a simple heading element.
<h1>Heading</h1>Code language: HTML, XML (xml)
Having the following CSS:
h1 {
display: grid;
gap: 10px;
}Code language: CSS (css)
Nothing special will happen. Making the heading a grid container with a gap does nothing visually, as within that grid, we only have one item (the text content).
Now let’s add the following:
h1 {
...
rule: 5px solid blue;
}
h1::after {
content: "";
}Code language: CSS (css)
Quite a strange code, but think a moment about it. What will it produce?
It adds an underline to the heading. If you still don’t get why it’s doing that, inspect the code of the element, and you will notice that the pseudo-element is a new item inside the grid container placed below the text content. Two items mean a gap between them, and this gap is decorated with the rule property!

But that’s doable with a simple border-bottom . Why such overengineering?
Unlike with border we have another property that lets us control the width of that line: rule-inset.
Add the following code:
rule-inset: 1em;Code language: CSS (css)
We reduced the size of the line by 1em from each side to get a partial underline. Let’s try with a negative value instead:
rule-inset: -1em;Code language: CSS (css)
Now, the line extends by 1em for each side. We can also control each side individually by specifying two values:
rule-inset: -1em 1em;Code language: CSS (css)
A lot of possible ways to underline the text with a simple code.
The only drawback is that we cannot rely on percentage values to have more control over the line’s width. For example, the following (if valid) would allow me to have a line in the center equal to exactly 1em.
rule-inset: calc(50% - .5em);Code language: CSS (css)
The same width but placed on the left instead:
rule-inset: 0 calc(100% - 1em);Code language: CSS (css)
In reality, the above code is technically valid because rule-inset accepts percentage values, but they resolve to 0 in most cases. I won’t bother you with this small quirk, but try to avoid percentages with rule-inset as they will rarely do what you want. Meanwhile, we can still create a lot of fancy decorations. Here are more examples for you to explore.
In the second example, I added another pseudo-element to activate a gap at the top and get another line. A line that I can style differently because I can do the following:
rule: 5px blue;
rule-style: solid, dashed; /* 1st line is solid and 2nd line dahsed */Code language: CSS (css)
Cool, right? You can also define different thicknesses and different colors! We will see more examples later.
Horizontal Line Decoration
Let’s take the previous configuration, keep both pseudo-elements, and switch to a flexbox configuration:
The default configuration is row, which means the gap between items is horizontal; hence, we get vertical lines.
Let’s increase the gap and the line thickness.
gap: 120px;
rule: 100px solid blue;Code language: CSS (css)
Two ugly blocks on each side, but we can reduce their height to simulate horizontal lines:
rule-inset: calc(.5lh - 3px);Code language: CSS (css)
Since I am not able to use percentage, I can rely on the lh unit which refers to the height of the line box and since, in this configuration, our content is only text, it’s also the height of the element. If I reduce the height of the decoration by half the line-height minus 3px from each side, I get a line with 6px of thickness.
Another cool decoration:
Wait, I am lost! Didn’t we use rule-inset to control the width in the previous examples?!
This part can be a bit confusing, but don’t forget that we changed the configuration from column (with CSS grid) to row (with Flexbox). When dealing with vertical gaps, we get horizontal lines with a thickness (or height) controlled by the rule property (or the rule-width) while the rule-inset controls the width.
When the gaps are horizontal, the lines become vertical. The thickness becomes a width controlled by rule-width, and rule-inset controls the height. In the previous example, I created a line with a width equal to 100px and a height equal to 6px.
rule: 100px solid blue; /* width defined here */
rule-inset: calc(.5lh - 3px); /* height defined here */Code language: CSS (css)
Don’t worry if you are a bit lost. It’s still a new feature, so it takes time to get used to it. The more you play with it, the clearer it becomes, and we still have many examples to look at.
In the previous code, I have also used justify-content: center to get the effect correctly, but if we change the alignment, we get something different:
Here, it’s crucial to understand how gap decoration works to understand why the lines are positioned that way.
We are dealing with a flexbox configuration having 3 flex items (2 pseudo-elements and the text content). We defined a gap of 120px, but, the gap can be bigger depending on the alignment value, and when we define a gap decoration, the line is centered within the gap. To determine the position of the decorative lines, you need to know the size and position of the gap.
Let’s make the pseudo-elements visible to better understand what’s happening:
All the space between the red lines (pseudo-elements) and the text content is a gap, and the blue line is placed in the middle of it. This configuration is interesting because if I had the ability to rely on percentages, I could use the following:
rule: 100% solid blue;Code language: CSS (css)
And the line will automatically fill the whole space.

Unlike rule-inset, rule-width doesn’t accept percentage values, so let’s hope this will be added in the future, as it will unlock many possibilities and some flexibility. Actually, we are obliged to define values for the gap and for the line thickness.
gap: 15px;
rule: 12px solid blue;Code language: CSS (css)
With a percentage value, the rule-width value will be relative to the gap, and we can control everything by changing only one value.
gap: 15px;
rule: 80% solid blue; /* 80% of 15px = 12px */Code language: CSS (css)
Background Patterns
Let’s try something different and play with many lines this time. Take the following code:
.box {
position: fixed;
inset: 0;
display: grid;
grid-template:
repeat(auto-fill,minmax(30px,1fr))/
repeat(auto-fill,minmax(30px,1fr));
}Code language: CSS (css)
I define a full-screen grid with as many rows and columns as needed (well, the classic auto-fill configuration). Now let’s add a gap and a basic decoration:
gap: 5px;
rule: 2px solid #000;Code language: CSS (css)
Every gap is filled with a line, creating a grid-like pattern!
Well, a gradient can do this. What are you doing?
I know, I know — the cool part is still to come. Let’s update the previous code with the following:
rule: solid #000;
rule-width: 1px, 1px, 3px;Code language: CSS (css)
See that? I can set a different thickness for each line, and it will repeat across all the lines. Two lines with 1px thickness, then one line with 3px thickness, and so on.
Let’s do the same with the color:
rule-style: solid;
rule-color: red, red, blue;
rule-width: 1px, 1px, 3px;Code language: CSS (css)
Repeating the same color and thickness isn’t ideal, right? No problem, let’s fix that!
rule-color: repeat(2,red), blue;
rule-width: repeat(2,1px), 3px;Code language: CSS (css)
You can use the same repeat() function we have in CSS Grid. And while we are at it, let’s make some of the lines dashed instead:
rule-style: repeat(5,solid) dashed;
rule-color: repeat(2,red), blue;
rule-width: repeat(2,1px), 3px;Code language: CSS (css)
Do you see the potential of this? It’s not the feature I would consider to create patterns, but as you can see, it allows us to create complex ones easily. If you are not comfortable with gradients, you have a new tool to play with.
Let me show one more trick with the following code:
rule-break: intersection;Code language: CSS (css)
Previously, all the lines were on top of each other (they intersect), but this behavior can be controlled with the rule-break property. I won’t get into fine details of how it works, but I can disable the line intersection by using the intersection value (none is the default value).
Instead of a single line that spans the whole grid, we have many lines with tiny gaps between them. This also means that using rule-inset will apply to each one of those lines.
rule-inset: 5px;Code language: CSS (css)
A lot of possible configurations. As a CSS Pattern fanatic, I really like this part. Tweaking different values to get different patterns is quite satisfying and allows you to better understand how Gap Decorations works.
As a small homework, here are a few more patterns for you to dissect
Don’t be surprised by the dots pattern. Don’t forget that rule is similar to border, and we have the dotted style. A style I will use right away in the next example.
Responsive List Separator
Let’s take one of the first examples I shared in the article. The one with a line separator between items:
It’s one of the basic uses of gap decoration. We get a line separator, but only between two adjacent items, never at the beginning or the end of a row. That’s the responsive behavior, and without gap decoration, we had to rely on hacks to achieve it.
Now what if we want the lines to be horizontal? Think for a moment about how we can do it.
We already did a similar thing.
You got it?
I hope so. Here is the code:
column-rule: .75em solid purple;
rule-inset: calc(.5lh - 2px);Code language: CSS (css)
I make the width bigger (.75em in this case), and I use rule-inset combined with lh to specify the height (4px in this case)
Now let’s adjust the style and make it dotted:
I specified rule: 8px dotted, and the browser draws a dotted line with 3 dots. It could have been more or less, depending on many factors. If, for example, I make the font-size bigger, the line will have more space, and we get more dots.
Now the question is: how to force the browser to draw only one dot? Is this even possible?
Yes, it’s possible. To have only one dot, we need to ensure that the space reserved for the decoration can contain only one dot. In other words, that space needs to be a square.
In the previous example, we used 8px as the line thickness, which is also the dot diameter. To make sure we have only one dot, the line height needs to be equal to 8px as well, and we know how to do that!
rule-inset: calc(.5lh - 4px);Code language: CSS (css)
Tada! We have a dot separator!
Let’s add a size variable to make things easier to adjust:
ul {
--s: .5em; /* the dot size */
column-rule: var(--s) dotted purple;
rule-inset: calc(0.5lh - var(--s)/2);
}Code language: CSS (css)
If you didn’t understand how I made the checkboard or the dots pattern, now you have a strong hint.
Conclusion
Wasn’t it cool to hack with Gap Decorations?!
I know that most of the stuff we saw can be done using common, well-supported features such as gradients, pseudo-elements, border-image, etc. I am not proposing a replacement for the known solutions. It was more of a fun exercise in practicing Gap Decoration.
Thinking outside the box and coming up with fancy ideas is, for me, the best way to explore a new feature. Understanding the basics is good, but hacking with it to find other use cases is even better. And who knows, maybe some of my ideas will become common tricks that everyone will use in the future.
