SeinopSys
SeinopSys

Reputation: 8937

How to create "collapsed" borders around flex items and their container?

I have the following layout:

#limited-width {
  width: 100%;
  max-width: 200px;
  margin: 0 auto;
  font-size: 18px;
}
ul {
  display: flex;
  flex-flow: row wrap;
  list-style: none;
  padding: 0;
  margin: 20px;
}
ul > li {
  display: block;
  text-align: center;
  flex: 1 0 auto;
  max-width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 4px 7px;
  border: 2px solid rgba(0,0,0,.3);
  background-color: rgba(0,0,0,.03);
}
<div id="limited-width">
  <ul>
    <li>Apple</li>
    <li>Orange</li>
    <li>Pineapple</li>
    <li>Banana</li>
    <li>Tomato</li>
    <li>Pear</li>
    <li>Lemon</li>
  </ul>
</div>

As you can see, the list items inside the ul have a border with the width of 2px, but because of this, the border between elements doubles. I'm looking for a way to make the borders the same width between elements, while also keeping the border on the outside the same (similar to how border-collapse works on tables) using flexbox. Is this possible, and if so, how?

Upvotes: 37

Views: 56523

Answers (8)

t33st33r
t33st33r

Reputation: 307

I hope this comment could be helpful but my will is to make a response to @jon-onstott

Your answer helped me to solve a similar case. I have noticed you used inset shadows (I wonder if that's why you don't need the rest of corners (top left, top right and bottom left) to be fixed but I am pretty sure you get a better result if all your shadows are outset.

Another point I had to solve (for my case) is that the color I was using for the shadows were using transparency (not 100% opaque), so sibling boxes were messing up the color. I fixed it using inset clip-path with negative values on those sides were the shadow needs to be shown.

I don't show any code because it solves a case that is not the one mentioned in the question* , but I still think this can help anyone who ends here by mistake (at first) searching for a solution for some similar problem.

*: My case is a well-formed bunch of boxes with determined columns and rows 'size. That's why I know what boxes need to show the shadow. There's no sense on trying to solve my problem in a flexbox because the flexbox's shape depends on the available space to contain it.

Upvotes: 0

Rumments
Rumments

Reputation: 9

This was my solution:

window.addEventListener('load', function() {

    var containsFlex = document.getElementsByClassName('containsFlex');
    var i = containsEdit.length;
    while (i--) containsEdit[i].style.borderWidth = "1px 0 0 0"; 

}

The issue is that the programming code for FLEX comes after the code in css that sets the width and so any attempt to set these items through CSS fails. however with the code above (or a variant/s), one can create an acceptable result.

[I am at work so I cant do a full post. apologies to all]

Upvotes: 0

Charles Zhao
Charles Zhao

Reputation: 186

I just ran into this issue and this is my solution. My case is I need to render every 4 items in a row.

.container {
  display: flex;
  flex-wrap: wrap;
}

.item {
  flex: 0 0 25%;
  border-right: 1px solid black;
}

.item:nth-child(4n) {
  border-right: none;
}

.item:nth-child(n+5) {
  border-top: 1px solid black;
}

This way, the border-right only shows on the first 1-3 items in the same row. And starting from the 5th item (2nd row), they all have a border-top.

It produces a similar result as Ivan's answer. But I think this approach is much simpler.

Upvotes: 0

speciale
speciale

Reputation: 7

With :last-of-type you can "collapse" the last border. Maybe adding a box-sizing:border-box;

Upvotes: -2

wotboy
wotboy

Reputation: 19

Here's another idea I had that will keep borders "collapsed" when flex boxes wrap. It uses background color for top and left and borders for right and bottom. It might be hard to make it work over a background image.

.container {
  display: flex;
  flex-flow: row wrap;
  border-style: solid;
  border-width: 0 2px 2px 0;
  border-color: black;
  background-color: black;
}

.container>div {
  flex: 1 0 auto;
  margin: 2px 0 0 2px;
  background-color: white;
}
<div class="container">
  <div>Eh?</div>
  <div>Bee.</div>
  <div>This div contains a whole bunch of stuff.</div>
  <div>This div contains a whole bunch of stuff.</div>
  <div>This div contains a whole bunch of stuff.</div>
  <div>Sea!</div>
  <div>This div contains a whole bunch of stuff.</div>
  <div>This div contains a whole bunch of stuff.</div>
  <div>This div contains a whole bunch of stuff.</div>
</div>

Upvotes: 0

Jon Onstott
Jon Onstott

Reputation: 13727

In my case, the borders need to be 1px which makes it more difficult. I found a solution at https://codepen.io/Hawkun/pen/rsIEp/ which uses shadows to simulate borders, which actually works well.

Here is the code in action. It doesn't use flexbox but if you apply the shadow to your flex content, you're good to go.

body {
  font-family: sans-serif;
  background-color: #eee;
  padding: 20px;
}

.info {
  color: darkred;
  font-weight: bolder;
}

.container {
  background-color: white;
  float: left; /* Makes the container height the same as its children. */
  padding: 10px;
  margin-bottom: 40px;
}

.container div {
  padding: 20px;
  float: left;
  background-color: #def;
  
  
  /* And here comed the trick: */

  box-shadow: 
1px 0 0 0 #888, 
0 1px 0 0 #888, 
1px 1px 0 0 #888,   /* Just to fix the corner */
1px 0 0 0 #888 inset, 
0 1px 0 0 #888 inset;

}

#container1 {
  width: 100%;
}

#container2 {
  width: 50%;
}

#container2 div {
  width: 70%;
}
<p>The first container:</p>

<div id="container1" class="container">
  <div>Hello, this is the first floated div</div>
  <div>And this is the second</div>
  <div>And finally the third one</div>
</div>

<p>The second container:</p>

<div id="container2" class="container">
  <div>Hello, this is the first floated div</div>
  <div>And this is the second</div>
  <div>And finally the third one</div>
</div>

Upvotes: 8

Ivan
Ivan

Reputation: 321

I had same question, but I made this(see demo below). I add to each block negative 'margin-left' and negative 'margin-top' equal to the width of the border. Then I add the same but positive 'padding-left' and 'padding-top' to the container, to compensate for the offset. Woo-a-la! Now we get “collapsed” borders around flex items and their container.

.catalog-list {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  padding-top: 1px;
  padding-left: 1px;
  box-sizing: border-box;
  max-width: 800px;
  margin: auto;
  box-shadow: inset 0 0 0 1px #8c8c8c;
}

.catalog-item {
  width: calc(25% + 1px);
  margin-top: -1px;
  margin-left: -1px;
  padding: 20px;
  border: 1px solid #8c8c8c;
  box-sizing: border-box;
  transition: all 0.2s;
  box-sizing: border-box;
}

.catalog-item:hover {
  border-color: transparent;
  box-shadow: 0 0 15px -2px #8c8c8c;
}
<div class="catalog-list">
  <div class="catalog-item"></div>
  <div class="catalog-item"></div>
  <div class="catalog-item"></div>
  <div class="catalog-item"></div>
  <div class="catalog-item"></div>
  <div class="catalog-item"></div>
</div>

Upvotes: 20

SeinopSys
SeinopSys

Reputation: 8937

There are two primary ways to achieve this. Under each method you will find a working demo that you can expand to see how it behaves. Hovering over elements will give them a red border to make choosing the approach that works best for you easier.

Parent-child border alignment

You need to define the border like this:

ul, ul > li {
  border-style: solid;
  border-color: rgba(0,0,0,.3);
}
ul      { border-width: 2px  0   0  2px }
ul > li { border-width:  0  2px 2px  0  }

The key here is in the border-width property:

  • On the container, the values for the top and left are set to the desired size while the right and bottom are set to 0
  • On the items, the values for the right and bottom are set to the desired size while the top and left are set to 0

By doing this, the borders will add up in a way that they form a nicely collapsed, consistent border around the elements and the container.

:hover { border-color: red }
#limited-width {
  width: 100%;
  max-width: 200px;
  margin: 0 auto;
  font-size: 18px;
}
ul, ul > li {
  border-style: solid;
  border-color: rgba(0,0,0,.3);
}
ul {
  display: flex;
  flex-flow: row wrap;
  list-style: none;
  padding: 0;
  margin: 20px;
  border-width: 2px 0 0 2px;
}
ul > li {
  display: block;
  text-align: center;
  flex: 1 0 auto;
  max-width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 4px 7px;
  border-width: 0 2px 2px 0;
  background-color: rgba(0,0,0,.03);
}
<div id="limited-width">
  <ul>
    <li>Apple</li>
    <li>Orange</li>
    <li>Pineapple</li>
    <li>Banana</li>
    <li>Tomato</li>
    <li>Pear</li>
    <li>Lemon</li>
  </ul>
</div>

Halving borders

In case you want to have distinct borders for each element for any purpose, this is a compromise that might suit your needs. Given a desired border-width of 2px the CSS is as follows:

ul, ul > li {
  border: 1px solid rgba(0,0,0,.3);
}

This method sets half of the desired border width on both the parent and its children, making the final border 2px thick. Be wary of using this method with fractional pixels (e.g. 1.5px) as you can run into issues.

When using border-color-changing rules the half-width will be apparent, but if you want nicer looking borders this is a much better approach than the first.

:hover { border-color: red }
#limited-width {
  width: 100%;
  max-width: 200px;
  margin: 0 auto;
  font-size: 18px;
}
ul, ul > li {
  border: 1px solid rgba(0,0,0,.3);
}
ul {
  display: flex;
  flex-flow: row wrap;
  list-style: none;
  padding: 0;
  margin: 20px;
}
ul > li {
  display: block;
  text-align: center;
  flex: 1 0 auto;
  max-width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 4px 7px;
  background-color: rgba(0,0,0,.03);
}
<div id="limited-width">
  <ul>
    <li>Apple</li>
    <li>Orange</li>
    <li>Pineapple</li>
    <li>Banana</li>
    <li>Tomato</li>
    <li>Pear</li>
    <li>Lemon</li>
  </ul>
</div>

Upvotes: 62

Related Questions