Mind the Gap: From Simple Spacing to Stylish Dividers in CSS

25 min read

Mind the Gap: From Simple Spacing to Stylish Dividers in CSS

Photo by Luke Chesser on Unsplash - A fitting dashboard analogy for our journey today.

Right then, settle in. If you've been knocking about the frontend world for more than a few weeks, you've felt the pain. I'm talking about the age-old struggle of getting the spacing just right between elements in a list or a grid.

Remember the dark times? Floating everything, then clearing it. Hacking away with margins, only to have the last item in the row throw your whole layout off. You'd find yourself writing selectors that looked like a cat walked over your keyboard: :not(:last-child), :nth-child(n+1), or adding negative margins to the parent container to "pull" the layout back into alignment. It was a proper faff, a rite of passage that left us all with a few more grey hairs.

Then, along came Flexbox and Grid, and with them, a true gift from the CSS gods: the gap property. Suddenly, creating perfectly consistent gutters between items was as simple as one line of code. It was beautiful.

But what if we could do more? What if that empty space, that gap, could be more than just... space? What if it could be a styled, decorative part of our design? Well, get ready to be chuffed to bits, because that future is arriving now. Today, we're going on a deep dive, from the foundations of gap to the exciting new frontier of styling the space between.

A Quick Refresher: Why gap Was a Game-Changer

Before we jump into the shiny new stuff, it's crucial to understand the problem gap solved. Let's imagine a simple, real-world component: a set of "Key Metric" cards on a business intelligence dashboard.

Our HTML might be straightforward:

<!-- A simple container for our key metrics -->
<div class="metrics-container">
  <div class="metric-card">
    <h4>Revenue</h4>
    <p>$1.2M</p>
  </div>
  <div class="metric-card">
    <h4>New Users</h4>
    <p>4,312</p>
  </div>
  <div class="metric-card">
    <h4>Churn Rate</h4>
    <p>3.1%</p>
  </div>
  <div class="metric-card">
    <h4>Support Tickets</h4>
    <p>87</p>
  </div>
</div>

The Old Way (The Margin Mambo)

To create space between these cards, we'd typically add a margin.

/* The old, clunky way of doing things */
.metrics-container-old {
  display: flex;
  flex-wrap: wrap; /* Allow cards to wrap to the next line */
}

.metric-card-old {
  /* Add space to the right and bottom of each card */
  margin-right: 16px;
  margin-bottom: 16px;
}

This looks okay at first glance, but it creates two classic problems:

  1. The Trailing Margin: The last item in a row gets a margin-right it doesn't need, potentially pushing it out of the container or requiring a not(:last-child) selector.

  2. The Wrapping Problem: When items wrap, the ones at the end of the row still have a margin-right, which can misalign the grid.

It was messy. We spent more time fighting the layout than building the feature.

The New Way (The gap Glory)

With gap, this all goes away. The property is applied to the container, and it intelligently places space only between the items.

/* The modern, clean way with gap */
.metrics-container-new {
  display: grid;
  /* This creates a grid that automatically adds columns of at least 200px */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  
  /* AND HERE IS THE MAGIC! */
  /* This one line replaces all our margin hacks. */
  /* It creates a 16px space both between rows and columns. */
  gap: 16px; 
}

/* We don't need any special margin rules on the card itself! */
.metric-card-new {
  /* Card styles like padding, background, etc. go here */
  background-color: #f9f9f9;
  padding: 24px;
  border-radius: 8px;
}

Look at that. Clean, declarative, and it just works. No hacks, no complex selectors. gap (and its long-form versions row-gap and column-gap) tells the browser, "I want this much space between my children," and the browser sorts it out. Bliss.

The Next Evolution: Styling the Gap with rule

For years, that's where the story ended. The gap was empty space. If you wanted a divider line, you were back to using border on the child elements, or worse, adding decorative <hr> or <div> elements, which is a big no-no for semantic HTML.

But the CSS Working Group has been busy, and they've given us a new set of properties that work a lot like the venerable border properties, but for gaps. Meet rule-width, rule-style, and rule-color.

These properties are designed to draw a line—a "rule"—smack-bang in the middle of a row-gap or column-gap.

Let's build a more complex, real-world example to see this in action. We'll create a Kanban-style project management board, a staple of SaaS applications.

Our SaaS Project Board HTML

<!-- The main container for our project board -->
<main class="project-board">

  <!-- Column 1: To Do -->
  <section class="board-column">
    <header>
      <h2>To Do</h2>
      <span class="task-count">3</span>
    </header>
    <div class="task-list">
      <article class="task-card priority-high">Update user avatars</article>
      <article class="task-card">Fix login bug on Safari</article>
      <article class="task-card">Prepare release notes</article>
    </div>
  </section>

  <!-- Column 2: In Progress -->
  <section class="board-column">
    <header>
      <h2>In Progress</h2>
      <span class="task-count">1</span>
    </header>
    <div class="task-list">
      <article class="task-card">Refactor API endpoint for reporting</article>
    </div>
  </section>

  <!-- Column 3: Done -->
  <section class="board-column">
    <header>
      <h2>Done</h2>
      <span class="task-count">4</span>
    </header>
    <div class="task-list">
      <article class="task-card task-done">Deploy dark mode feature</article>
      <article class="task-card task-done">Onboard new customer</article>
      <article class="task-card task-done">Write documentation for SSO</article>
      <article class="task-card task-done">Resolve database query performance</article>
    </div>
  </section>

</main>

Styling the Board with Styled Gaps

First, let's lay out the main columns. We'll use Flexbox for the project-board and give it a column-gap to space out the columns. This is where we'll introduce our first styled rule.

.project-board {
  display: flex;
  
  /* We want a 24px space between our columns */
  column-gap: 24px;

  /* --- THE NEW HOTNESS --- */
  /* Let's draw a decorative line in that gap! */

  /* Set the width of the divider line */
  column-rule-width: 1px; 
  
  /* Set the style of the line (solid, dashed, dotted, etc.) */
  column-rule-style: dashed; 
  
  /* And set its color. Let's use a modern color function! */
  /* oklch is great because it's perceptually uniform. */
  /* This color is a light, low-chroma (not very colourful) grey. */
  column-rule-color: oklch(85% 0.01 240);
}

By 'eck, look at that! With three simple properties on the parent container, we now have a perfectly centered, dashed grey line between our project columns. We didn't have to touch the board-column elements at all. No borders, no pseudo-elements. The layout logic is neatly separated from the item styling.

Just like with borders, there's a shorthand property, column-rule.

/* The shorthand version is cleaner */
.project-board {
  display: flex;
  column-gap: 24px;
  
  /* shorthand: <width> <style> <color> */
  column-rule: 1px dashed oklch(85% 0.01 240);
}

Now, let's go a level deeper. We want to style the space between the task cards inside each column. We'll use Flexbox again for the task-list, but this time it's a vertical list. So we'll use row-gap and, you guessed it, row-rule.

.task-list {
  display: flex;
  flex-direction: column;
  
  /* Create a 12px space BETWEEN each task card */
  row-gap: 12px;

  /* --- AND AGAIN! --- */
  /* Let's add a subtle, solid line between the tasks */
  row-rule: 1px solid oklch(92% 0.01 240);
}

.task-card {
  /* Basic card styling */
  background-color: white;
  padding: 16px;
  border-radius: 6px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.05);
  /* other styles... */
}

And there we have it. A fully styled Kanban board where the dividers are part of the layout system itself, not tacked on with styling hacks. The project-board has dashed vertical dividers, and each task-list has solid horizontal dividers. It’s clean, maintainable, and semantically pure.

A Word of Caution: Browser Support

Now for the important bit of housekeeping. As of me writing this, these rule properties are highly experimental. They are currently available in Chrome Canary and Edge Dev behind a feature flag (#enable-experimental-web-platform-features).

So, you can't ship this in a production SaaS app tomorrow. But that's not the point. The point is to understand where CSS is going. This is on the standards track and will, in time, become a part of our daily toolkit. Playing with it now means you'll be ready to use it effectively the moment it lands.

For now, you'd use this with @supports for progressive enhancement:

.task-list {
  display: flex;
  flex-direction: column;
  row-gap: 12px;
}

/* Fallback for browsers that don't support row-rule */
.task-card:not(:last-child) {
  border-bottom: 1px solid oklch(92% 0.01 240);
}

/* The shiny new way for browsers that DO support it */
@supports (row-rule-style: solid) {
  .task-list {
    row-rule: 1px solid oklch(92% 0.01 240);
  }
  
  /* We must undo our fallback! */
  .task-card:not(:last-child) {
    border-bottom: none;
  }
}

Why This is More Than Just Syntactic Sugar

As a senior developer, what gets me excited isn't just a new, shiny toy. It's about what that toy represents for how we build UIs.

  1. True Separation of Concerns: A divider between layout items is a property of the layout, not the items themselves. These rule properties respect that. Your component's CSS (.task-card) doesn't need to know or care about how it's being laid out by its parent.

  2. Cleaner, More Semantic HTML: No more div class="divider" or empty hr tags. This reduces DOM clutter and keeps your HTML focused on content and structure, which is a massive win for accessibility and maintainability.

  3. Robustness: These rules exist within the gap. They don't affect the box model of the child elements at all. This means no more weird calculations or box-sizing gotchas that you'd get when using borders to simulate a divider.

We've come a long way from wrestling with floats and margins. CSS is evolving to be a more intuitive and powerful language for describing layout and design. The ability to style the gap is a small but profound step on that journey. It shows a commitment to solving real-world developer problems at the language level.

So go on, have a play with it in a supporting browser. Build a dashboard, a grid of user profiles, or a pricing table. See how it feels to treat the space between as a first-class design element. It might not be ready for prime time just yet, but it's a brilliant glimpse into a cleaner, more logical future for CSS layout.

And I'm proper excited about that.


Further Reading & Inspiration

This article was written after studying the work of others in the community. A massive thank you to the original authors for their fantastic insights. I highly recommend giving them a read:

0%
0 min