Collapsing borders using CSS Grid
Asked Answered
R

5

72

I'm having fun getting my head around the new CSS Grid spec, but I'm running into trouble with borders.

Is it possible to collapse borders in a CSS Grid, or is there any way to style the gutter?

As you can see in the snippet below, the 10px borders stack (20px total) in-between blocks.

I understand this issue isn't unique to CSS Grids, but I'm hoping it'll allow for new solutions for creating a uniform 10px border between all boxes and on the outer edges.

My actual use-case is a calendar I'm making to practice working with Grids and React components. You can see the issue I'm running into here:

CSS Grid Calendar.

Since every month is different, I'll have a lot of different edge-cases to consider.

.container {
  display: grid;
  grid-template-columns: 120px 120px;
  box-sizing: border-box;
}

.block {
  width: 100px;
  height: 100px;
  background-color: lightgrey;
  border: 10px solid palegreen;
}

.first {
  grid-column: 2 / span 1;
}
<div class='container'>
  <div class='block first'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
</div>
Ridgepole answered 28/4, 2017 at 18:28 Comment(4)
Just FYI, here's a CSS Grid calendar somebody built: https://mcmap.net/q/93190/-prevent-content-from-expanding-grid-items/3597276Hbeam
Also, a common solution to the problem is to fill the empty cells with faded cells for the days from the previous and next month.Hbeam
Thanks @Michael_B ! My plan is to make a super customizable calendar front-end so I can print out month calendars that look just how I like them. For now though, it's mostly about the learning exercise. Looking forward to reviewing that question and its answers; it looks like it'll be helpful.Ridgepole
It's remarkable that these modern ways to draw a grid still has shortcomings as fundamental as this. Compare this to table layouting from the dawn of HTML, with which one can just use border-collapse: collapse.Telegraph
R
59

You may use grid-gap and box-shadow:

.container {
  display: grid;
  grid-template-columns: 100px 100px;
  box-sizing: border-box;
  grid-gap:10px;
}

.block {
  width: 100px;
  height: 100px;
  background-color: lightgrey;
 box-shadow:0 0 0 10px palegreen;
}

.first {
  grid-column: 2 / span 1;
}
<div class='container'>
  <div class='block first'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
</div>

Or combine row and columns template setting:

.container {
  display: grid;
  grid-template-columns: 110px 110px;
  grid-template-rows:110px;
  box-sizing: border-box;
  
}

.block {
  width: 100px;
  height: 100px;
  background-color: lightgrey;
 border:solid 10px palegreen;
}

.first {
  grid-column: 2 / span 1;
}
<div class='container'>
  <div class='block first'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
</div>

Note that columns and rows of 120px will show both sides borders when box is set to 100px...

If fr value is used for columns, then do not set width on boxes (rows would follow same restriction).

.container {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-template-rows: 110px;
  /*whatever else */
  box-sizing: border-box;
}

.block {
  margin: 0 -10px 0 0;/* fixed width value missing */
  height: 100px;
  background-color: lightgrey;
  border: solid 10px palegreen;
}

.first {
  grid-column: 2 / span 1;
}
<div class='container'>
  <div class='block first'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
  <div class='block'>4</div>
  <div class='block'>5</div>
  <div class='block'>6</div>
  <div class='block'>7</div>
</div>
Rage answered 28/4, 2017 at 18:39 Comment(5)
Awesome! I'll be trying both of those for sure.Ridgepole
Do you think these approaches could still work if I've defined my grid template with frs?Ridgepole
@TimFoley If you do, do not set width on children, only col number in the template edit added a snippet exampleRage
@TimFoley an exemple with 2 grids side by side and 1fr for rows and columns : codepen.io/gc-nomade/pen/QvpBaORage
Just tested it out, and I think you've solved it! Thanks a tonRidgepole
D
37

I just found a simple way to achieve this, using css outline instead of border.

The outline property draws a line outside the element, so, having 1px gap collapses both lines.

.container {
  display: grid;
  grid-template-columns: 100px 100px 100px;
  gap: 1px; /* you can use gap instead of grid-gap */
}

.block {
  width: 100px;
  height: 100px;
  background-color: lightgrey;
  outline: 1px solid darkgreen; /* Use outline instead of border */
}

.first {
  grid-column: 2 / span 1;
}
<div class='container'>
  <div class='block first'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
  <div class='block'>4</div>
  <div class='block'>5</div>
  <div class='block'>6</div>
</div>

As TylerH commented, outline does not take up space and can overlap, that is why you need to use the gap for it, if you want a 5px line, you should write 5px for both properties, the outline and the gap.

.container {
  display: grid;
  grid-template-columns: 100px 100px 100px;
  gap: 5px;
}

.block {
  width: 100px;
  height: 100px;
  background-color: lightgrey;
  outline: 5px solid darkgreen; /* The same width as the gap */
}
Dre answered 3/3, 2021 at 14:18 Comment(5)
While outline draws a line outside the element, it's important to note it does not take up space, so it doesn't cause changes to the layout. This also allows a weird 'feature' where you can have two elements side by side and their outlines can appear to 'overlap'.Treasurer
I found this answer to be the simplest and most universal solution. Thank you.Struve
i thought this was the solution for me until i learned there's no outline-top, outline-left, etc :(Selfregulated
no need to set size for .block. also no need to set sizes for .container. this works too: grid-template-columns: repeat(3, 1fr)Microhenry
You'll particularly notice outline stacking when using transparency for the outline, making the outer and inner outlines appear differentAmaras
H
13

Consider controlling all sizing and spacing at the grid container level, not at the grid item level. Remove the borders and sizing applied to the items.

.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); /* 1 */ /* 2 */
  grid-auto-rows: 100px; /* 3 */
  grid-gap: 5px; /* 4 */
  padding: 5px;
  background-color: tomato;
}

.block {
  background-color: lightgrey;
}

/* for demo only */
.block:nth-child(-n + 2) {
  visibility: hidden;
}
<div class='container'>
  <div class='block'>0</div>
  <div class='block'>0</div>
  <div class='block'>1</div>
  <div class='block'>2</div>
  <div class='block'>3</div>
  <div class='block'>4</div>
  <div class='block'>5</div>
  <div class='block'>6</div>
  <div class='block'>7</div>
  <div class='block'>8</div>
  <div class='block'>9</div>
  <div class='block'>10</div>
  <div class='block'>11</div>
  <div class='block'>12</div>
  <div class='block'>13</div>
  <div class='block'>14</div>
  <div class='block'>15</div>
  <div class='block'>16</div>
  <div class='block'>17</div>
  <div class='block'>18</div>
  <div class='block'>19</div>
  <div class='block'>20</div>
  <div class='block'>21</div>
  <div class='block'>22</div>
  <div class='block'>23</div>
  <div class='block'>24</div>
  <div class='block'>25</div>
  <div class='block'>26</div>
  <div class='block'>27</div>
  <div class='block'>28</div>
  <div class='block'>29</div>
  <div class='block'>30</div>
  <div class='block'>31</div>  
</div>

jsFiddle demo

Notes:

  1. auto-fit: Fill in as many columns as can fit on the row. Overflow columns will wrap.
  2. minmax(): Each column will be a minimum width of 120px and maximum width of whatever free space is available. The fr unit is comparable to flex layout's flex-grow property.
  3. grid-auto-rows: Automatically created rows (implicit rows) will be 100px in height.
  4. grid-gap: 5px gutters all around. Shorthand for grid-column-gap and grid-row-gap.
Hbeam answered 28/4, 2017 at 18:49 Comment(3)
Nice! I'm hoping to have some more control over what happens with the unfilled space. I'll definitely keep this in mind for other applications.Ridgepole
I'm referring to the green background-color showing in the upper left and lower right.Ridgepole
Even though this does not let you control the border-style (which is a pity), this is probably the best solution for grid layouts.Shakitashako
E
1

I was looking for a pure CSS way to collapse the borders of a grid, but since I could find none, I made a little prototype.

CSS Grid Collapsed Borders Rounded Corners

HTML

<div class="container">
  <div id="grid" class="grid">
    <div class="element">1</div>
    <div class="element">2</div>
    <div class="element">3</div>
    <div class="element">4</div>
    <div class="element">5</div>
    <div class="element">6</div>
    <div class="element">7</div>
    <div class="element">8</div>
    <div class="element">9</div>
    <div class="element">10</div>
    <div class="element">11</div>
  </div>
</div>

CSS

.container {
  max-width: 720px;
  margin: 0 auto;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));  
}

.element {
  text-align: center;
  padding: 20px;
  background: #f4f4f4;
  border-bottom: 1px solid black;
  border-right: 1px solid black;
}

.border-top {
  border-top: 1px solid black;
}

.border-left {
  border-left: 1px solid black;
}

.border-top-left-rounded {
  border-top-left-radius: 8px;
}

.border-top-right-rounded {
  border-top-right-radius: 8px;
}

.border-bottom-left-rounded {
  border-bottom-left-radius: 8px;
}

.border-bottom-right-rounded {
  border-bottom-right-radius: 8px;
}

JS

function dynamicRoundedCorners() {
  // get
  const grid = document.getElementById("grid");
  const elements = grid.children;
  const gridStyle = getComputedStyle(grid);

  // reset
  for (element of elements) {
    element.classList = "";
    element.classList.add("element");
  }

  // analyze
  const elementsPerRowCount = gridStyle.gridTemplateColumns
    .split(" ")
    .filter((element) => Number(element.replace("px", ""))).length;
  const rowCount = Math.ceil(elements.length / elementsPerRowCount);
  const rowsFirstAndLastElements = [];
  let firstAndLastElementIndex = 0;

  for (let i = 1; i <= rowCount; i++) {
    const rowFirstAndLastElements = [firstAndLastElementIndex];

    if (i === rowCount && rowCount > 1) {
      rowFirstAndLastElements.push(
        firstAndLastElementIndex + (elements.length % elementsPerRowCount) - 1
      );
    } else {
      rowFirstAndLastElements.push(
        firstAndLastElementIndex + elementsPerRowCount - 1
      );
    }
    rowsFirstAndLastElements.push(rowFirstAndLastElements);
    firstAndLastElementIndex += elementsPerRowCount;
  }

  // apply
  // -> add border-top on the first row
  for (let i = 0; i <= rowsFirstAndLastElements[0][1]; i++) {
    elements[i].classList.add("border-top");
  }

  // -> add border-left on every first element of a row
  for (let i = 0; i < rowCount; i++) {
    elements[rowsFirstAndLastElements[i][0]].classList.add("border-left");
  }

  // -> add top-left rounded corner on first element of first row
  elements[0].classList.add("border-top-left-rounded");
  // -> add top-right rounder corner on last element of first row
  elements[rowsFirstAndLastElements[0][1]].classList.add(
    "border-top-right-rounded"
  );
  // -> add bottom-left rounded corner on first element of last row
  elements[rowsFirstAndLastElements[rowCount - 1][0]].classList.add(
    "border-bottom-left-rounded"
  );
  // -> add bottom-right rounder corner on last element of last row
  elements[elements.length - 1].classList.add("border-bottom-right-rounded");
  // -> if elements.length % elementsPerRowCount != 0, add bottom-right rounder corner on last element of second to last row
  if (elements.length % elementsPerRowCount !== 0) {
    elements[
      rowsFirstAndLastElements[rowsFirstAndLastElements.length - 2][1]
    ].classList.add("border-bottom-right-rounded");
  }
}

// call
dynamicRoundedCorners();
window.addEventListener("resize", dynamicRoundedCorners);

Here is the link: https://codepen.io/RilDev/pen/gOmjNrQ

Etoile answered 30/6, 2021 at 8:57 Comment(0)
T
0

Another approach you could take if you were ok with the gap border color being the same as the day cells that don't fall on the current month is to wrap a div around the entire grid container and set its background-color to the color you want your borders to be and give it 1px of padding with a grid-gap of 1px. With this approach you're able to achieve a uniformly bordered grid without the complexity of using box-shadow, which feels like a hack to me.

Thalia answered 20/12, 2017 at 21:21 Comment(1)
Isn't that the same as @Michael_B's answer? Except that he uses 5px instead of 1px borders.Lex

© 2022 - 2024 — McMap. All rights reserved.