Looking for a Front-End Developer and Design Systems Practitioner?

I currently have some availabilty. Let's talk!.

Always Twisted

Styling The gap with CSS

Back in 2017, while working at Monotype on their Design System, I was tasked with coding a new design for a set of logos. This design featured either a vertical line separator at larger viewports or a horizontal one at smaller screens, with the logos stacking as needed. Eager to implement the latest technology, I opted for CSS Grid. However, the challenge was that, at the time, there was no straightforward way to add a graphical rule to the component without resorting to pseudo-elements and "magic numbers" to achieve a 1px wide line evenly spaced in the grid's margin.

Fast forward to 2025, and we’re on the brink of using new CSS features to create those horizontal or vertical rules (but with bells on).

In June, Patrick and Sam from Microsoft updated us on the Chrome blog about a ~previous announcement~ regarding the ability to add ~decorations to the gaps in our flex and grid layouts~. Excited by this news, I spent a couple of hours tinkering in Chrome (Canary) to explore the new rules and their potential use cases. And here we are!

CSS Gap Decorations

As I mentioned, before now (browser support not withstanding) we had to be intuitive if we cam across a design that needed verricla or horizontal lines (or both) as part of a layout. We could opt for using pseudo elements (⁠::beforeor ⁠::after) or we could add additonal DOM elements but these could hit upon other issues. The workarounds could be unitiuitive, we could be adding more CSS that could break if layouts change, additional columns or rows are added. Adding additional DOM elements could work but we could hit upon accessibility issues. This new specification clears these potential issues up rather neartly as they have no impact on the layouts they’re being applied to as they’re purely visual, they allow for pattern creation as they make use of ⁠repeat() and we don’t need extra markup, we can apply this CSS to what we have already.

New Specification, New Rules, New Properties

The new gap decoration specification introduces familiar syntax, similar to how we write border properties in CSS. It brings two main sets of properties: one for column rules and one for row rules, each with their own width, style, and colour properties.

Individual Properties

For complete control, you can use individual properties:

Code languagecss
/* Column decorations */
column-rule-width: 2px;
column-rule-style: solid;
column-rule-color: #BADA55;
/* Row decorations */
row-rule-width: 1px;
row-rule-style: solid;
row-rule-color: #C0FFEE;

Shorthand Syntax

Just like with borders, you can combine these properties using shorthand:

Code languagecss
/* Combined column properties */
column-rule: 2px solid #BADA55;
/* Combined row properties */
row-rule: 1px solid #C0FFEE;

Universal Rule

If you want the same decoration style for both rows and columns, there's a new universal shorthand:

Code languagecss
/* Applied to both row and column gaps */
rule: 2px solid #BADA55;

This new specification brings consistency to how we style gaps in our layouts, whether they're grid, flexbox, or multi-column. The syntax should hopefully feel familiar to anyone who's worked with borders, making it intuitive to implement while offering powerful new styling capabilities.

Specialised Properties

The specification introduces some powerful properties that give us fine-grained control over how our gap decorations appear and interact. Let’s take a look at these in detail:

Rule Break

The rule-break property controls how our gap decorations behave when they meet at intersections or spannign items:

Code languagecss
/* Column decorations */
column-rule-break: none; /* Lines continue through intersections */
column-rule-break: intersection; /* Lines stop at intersections */
column-rule-break: spanning-item; /* Lines break at spanning items */
/* Row decorations */
row-rule-break: none;
row-rule-break: intersection;
row-rule-break: spanning-item;
/* Shorthand for both */
rule-break: intersection;

This property is particularly useful when you need precise control over how your decorations interact with the layout. For example, in a calendar interface, you might want vertical lines to break at day headers that span multiple columns.

Rule Outset

The ⁠rule-outset properties give us precise control over where decorations start and end relative to intersections:

Code languagecss
/* Controls how far decorations extend from their central position */
column-rule-outset: 50%; /* Default - meets in the middle */
column-rule-outset: 0px; /* Aligns with edges */
column-rule-outset: 5px; /* Extends beyond edges */
column-rule-outset: -5px; /* Pulls back from edges */
/* Same options for rows */
row-rule-outset: 50%;
/* Shorthand for both */
rule-outset: 5px;

The percentage values are relative to the gap width, which means they'll scale proportionally as your layout changes. This is particularly useful for responsive designs where gap sizes might vary.

Paint Order

Finally, ⁠rule-paint-order lets us control which decorations appear on top when row and column rules intersect:

Code languagecss
/* Row decorations appear on top of column decorations */
rule-paint-order: row-over-column;
/* Column decorations appear on top of row decorations */
rule-paint-order: column-over-row;

This property becomes especially important when working with different rule widths or styles. For example, if you're creating a notebook-style layout with thin vertical lines and thicker horizontal lines, you might want to ensure the horizontal lines appear on top for a more authentic look:

Code languagecss
.notebook {
display: grid;
gap: 2rem;
column-rule: 1px solid #ddd;
row-rule: 2px solid #000;
rule-paint-order: row-over-column;
}

Pattern Creation with ⁠repeat()

Just like CSS Grid's ⁠repeat() function, gap decorations can use similar syntax to create patterns. This gives us powerful ways to style our gaps without writing repetitive CSS:

Code languagecss
/* Simple repetition of the same style */
row-rule: repeat(solid); /* Every row gap has a solid line */
/* Alternating styles */
column-rule-style: repeat(solid dotted); /* Alternates between solid and dotted lines */
/* Complex patterns */
row-rule: repeat(2, 1px solid black) 3px solid red; /* Two thin black lines, then one thick red line */
/* Multiple properties can use repeat */
column-rule-width: repeat(1px 2px); /* Alternating thin and thick lines */
column-rule-color: repeat(#BADA55 #C0FFEE); /* Alternating colours */

These properties give us incredible flexibility... want your decorations to meet perfectly in the middle? Use the default 50%. Need them to stop right at the edges? Set it to 0. Want them to extend a bit further or pull back? Use positive or negative values accordingly.

In Practice

Let’s go back to that brand logo component I needed to create in 2017. There were 5 logos that needed to go from being stacked on a narrow viewport to evenly place when the viewport was large enough. The HTML would look something like this:

Code languagehtml
<ul>
<li><img src="logo-a.svg" alt="a logo of brand A"></li>
<li><img src="logo-b.svg" alt="a logo of brand B"></li>
<li><img src="logo-c.svg" alt="a logo of brand C"></li>
<li><img src="logo-d.svg" alt="a logo of brand D"></li>
<li><img src="logo-e.svg" alt="a logo of brand E"></li>
</ul>

The CSS to make sure that the gap decorations are displayed as needed looks like this:

Code languagecss
ul {
display: grid;
gap: 1rem;
justify-items: center;
row-rule-width: 1px;
row-rule-style: solid;
row-rule-color: black;
column-rule-width: 1px;
column-rule-style: solid;
column-rule-color: black;
@media (width > 600px) {
grid-template-columns: repeat(5, 1fr);
}
}

See the Pen gap rule swap (full) by Stuart Robson (@sturobson) on CodePen.

By default (on narrow screens), our grid has a single column with five rows, showing the horizontal rules between logos. Once the viewport is wider than 600px, it transforms into a five-column grid with a single row, now showing vertical rules between logos instead.

The beauty of using gap decorations here is that we don't need to worry about which rules show when... the browser handles that for us based on the layout. When there's only one column, column rules aren't visible, and when there's only one row, row rules aren't visible.

Since we're using the same width, style, and color for both our row and column rules, we can simplify our CSS even further using the ⁠rule shorthand property:

Code languagecss
ul {
display: grid;
gap: 1rem;
justify-items: center;
rule: 1px solid black;
@media (width > 600px) {
grid-template-columns: repeat(5, 1fr);
}
}

This single line of ⁠rule: 1px solid black replaces our previous six lines of individual properties, making our code more concise while maintaining the same responsive behavior. The browser still handles the visibility of rules based on the layout, we've just made it more efficient to write.

See the Pen gap rule swap (simplified) by Stuart Robson (@sturobson) on CodePen.

Advanced Gap Decorations

Building on the basic gap decorations, we can create more sophisticated visual patterns and layouts. Here are some advanced techniques that leverage the full power of CSS gap decorations:

Non-uniform Gap Patterns

Instead of applying the same decoration to every gap, we can create rhythmic patterns or highlight specific sections:

Code languagecss
.feature-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
/* Create alternating pattern for column rules */
column-rule: 1px solid black;
column-rule-width: 1px 2px; /* Alternates between 1px and 2px */
/* Apply dotted style to every third row */
row-rule: 1px solid black;
row-rule-style: solid solid dotted;
}

Visually Distinct Sections

You can create visual hierarchy by applying different gap decorations to specific grid areas:

Code languagecss
.dashboard {
display: grid;
grid-template:
"header header header" auto
"sidebar main main" 1fr
"footer footer footer" auto / 1fr 1fr 1fr;
gap: 1.5rem;
/* Base gap style */
row-rule: 1px solid #ddd;
column-rule: 1px solid #ddd;
/* Custom gap between header and content */
row-rule-width: 3px 1px;
row-rule-color: #0074d9 #ddd;
/* No vertical divider between main sections */
column-rule-width: 1px 0px 1px;
}

Decorative Gap Elements

For truly custom gap decorations, we can position decorative elements in the gap space:

Code languagecss
.gallery-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
position: relative;
/* Create space for decorations */
row-rule-outset: 1rem;
column-rule-outset: 1rem;
/* Visible rule styles */
row-rule: 1px dashed #0074d9;
column-rule: 1px dashed #0074d9;
}
/* Add decorative elements at intersections */
.gallery-grid .gallery-item {
position: relative;
}
/* Add corner decorations to create the appearance of decorated gaps */
.gallery-grid .gallery-item::after {
content: "";
position: absolute;
width: 1.5rem;
height: 1.5rem;
background:
radial-gradient(circle at center, #0074d9 0.25rem, transparent 0.25rem),
repeating-linear-gradient(45deg, #0074d9 0, #0074d9 2px, transparent 2px, transparent 8px);
border-radius: 50%;
bottom: -1.75rem;
right: -1.75rem;
z-index: 2;
}
/* Hide decorations on last column (every 3rd item) */
.gallery-grid .gallery-item:nth-child(3n)::after {
display: none;
}
/* Hide decorations on last row */
.gallery-grid .gallery-item:nth-last-child(-n+3)::after {
display: none;
}

Theme-Aware Gap Decorations

Using CSS custom properties, we can make gap decorations respond to theme changes:

Code languagecss
:root {
--gap-primary-color: #0074d9;
--gap-secondary-color: #ddd;
--gap-width-small: 1px;
--gap-width-large: 3px;
}
.dark-theme {
--gap-primary-color: #61dafb;
--gap-secondary-color: #444;
}
.content-sections {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
/* Theme-aware gap decorations */
column-rule: var(--gap-width-small) solid var(--gap-secondary-color);
row-rule: var(--gap-width-large) dotted var(--gap-primary-color);
/* Different outset based on theme */
row-rule-outset: calc(var(--gap-width-large) * 2);
}

Here’s a handy CodePen demonstrating the above examples, in action:

See the Pen CSS gap decoration 'advanced' examples by Stuart Robson (@sturobson) on CodePen.

Browser Support

Lacklustre to say the least. Currently, as I write this, styling gaps only works for Chromium-based browsers. You can see the work the Firefox team are doing on this here and where Webkit is up to here.

However, since this is a purely decorative feature (as the specification name suggests), I think we can confidently use it without worrying about browsers that don't yet support it. Non-supporting browsers will simply display layouts without the gap decorations, which doesn't impact functionality.

And it’s 2025, just as we recognized in 2012, websites don't need to look identical in every browser. Embracing the platform's natural progressive enhancement capabilities remains as valid as ever.

Wrapping Up

Gap decorations solve a problem I've been wrestling with since 2017, and now we have an elegant, declarative solution. No more pseudo-elements. No more magic numbers. Just straightforward CSS that adapts to your layout.

The familiar border-like syntax makes it approachable, while features like rule-outset and pattern creation give us room to get creative.

Do you struggle with managing CSS specificity in large projects?

I’ll create a strategy for managing and reducing specificity conflicts.

get in touch!