2

I'm having issues with a layout like this:

.wrapper {
  clear: both;
  background-color: #ccc;
}

.wrapper+.wrapper {
  margin-top: 50px;
}

.side,
.main {
  height: 100px;
  padding: 10px;
  box-sizing: border-box;
  margin-top: 20px;
}

.box {
  padding: 10px;
}

.top {
  background: yellow;
}

.side {
  width: 100px;
  float: left;
  background: lightblue;
}

.main {
  margin-left: 100px;
  background: lightgreen;
}
<div class="wrapper">
  <div class="top"></div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>

<div class="wrapper">
  <div class="top">
    <div class="box">top</div>
  </div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>

The .main and .side elements need to be aligned. As you can see in the above snippet, everything is fine unless the .top element has no height in which case the margin-top rule causes them to be skewed. All of the following "fix" the issue but each has a drawback:

  • adding border to .wrapper (I might be able to live with a transparent border but I really don't like this since it feels like a dirty hack and I'd rather not add a border. For some reason the border needs to have a width of at least 1px or this doesn't work)

.wrapper {
  clear: both;
  background-color: #ccc;
  border: 1px solid #000;
}

.wrapper+.wrapper {
  margin-top: 50px;
}

.side,
.main {
  height: 100px;
  padding: 10px;
  box-sizing: border-box;
  margin-top: 20px;
}

.box {
  padding: 10px;
}

.top {
  background: yellow;
}

.side {
  width: 100px;
  float: left;
  background: lightblue;
}

.main {
  margin-left: 100px;
  background: lightgreen;
}
<div class="wrapper">
  <div class="top"></div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>
  • adding overflow: hidden to .wrapper (this hides parts of some elements and causes others to fall in the wrong place)
  • adding overflow: auto to .wrapper (this adds scroll bars in some scenarios)

Those last two are not apparent in my snippet but in the real world application they cause problems as mentioned here.

I have a strong suspicion the issue is related to Why doesn't the height of a container element increase if it contains floated elements? and CSS container doesn't stretch to accommodate floats but I've tried many of those suggestions and none seem to quite solve the issue - perhaps because one of my divs is floated and the other is not.

Since this is part of a large application, I don't want to drastically change the layout, just have some css that will keep .main and .side aligned regardless of the content before those elements.

Eaten by a Grue
  • 19,208
  • 10
  • 77
  • 99
  • why not using flexbox for this? or even inline-block will do the job – Temani Afif Dec 13 '19 at 22:27
  • @TemaniAfif - please see my response to symlink's answer below. I appreciate your suggestion and I've used flex and love it — but unfortunately it's not an option here. As I mentioned in my question, I'd like to solve the problem *without changing the layout*. – Eaten by a Grue Dec 13 '19 at 22:29
  • by the way the issue is related to margin collapsing where the parent margin collapse with the *first* inflow child (which is the second one in your case since the first one is out of the flow) – Temani Afif Dec 13 '19 at 22:29
  • I had a feeling about that - I'm just confused about how to address it in the context of the layout as it is. – Eaten by a Grue Dec 13 '19 at 22:32
  • and you want the margin-top in your first case to uncollapse? you want to always see grey? – Temani Afif Dec 13 '19 at 22:35
  • @TemaniAfif - yes, exactly – Eaten by a Grue Dec 13 '19 at 22:36

2 Answers2

1

You can do this much more elegantly with grid. Here is the grid code:

.wrapper {
    display: grid;
    grid-template-areas:
        "top top"
        "side main";
    grid-template-columns: 100px 1fr;
}

.top{grid-area:top}
.side{grid-area:side}
.main{grid-area:main}

Notice how many other elements I was able to comment out and still keep the desired layout.

.wrapper {
  /*clear: both;*/
  background-color: #ccc;
}

.wrapper+.wrapper {
  margin-top: 50px;
}

.side,
.main {
  height: 100px;
  padding: 10px;
  box-sizing: border-box;
  /*margin-top: 20px;*/
}

.box {
  padding: 10px;
}

.top {
  background: yellow;
}

.side {
  /*width: 100px;
  float: left;*/
  background: lightblue;
}

.main {
  /*margin-left: 100px;*/
  background: lightgreen;
}

.wrapper {
    display: grid;
    grid-template-areas:
        "top top"
        "side main";
    grid-template-columns: 100px 1fr;
}

.top{grid-area:top}
.side{grid-area:side}
.main{grid-area:main}
<div class="wrapper">
  <div class="top"></div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>

<div class="wrapper">
  <div class="top">
    <div class="box">top</div>
  </div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>
symlink
  • 10,968
  • 6
  • 26
  • 48
  • Thanks and I'm aware of `grid` and have used it in other applications (and love it!). However, as I stated in my question, changing the layout here is not realistic. This is a huge sprawling application and there are lots of complexities that would be beyond the scope of this question to explain. Suffice to say, the basic float/not-float layout method needs to stay. – Eaten by a Grue Dec 13 '19 at 22:28
  • btw - nice demo of a basic grid layout :-) +1 for that and let's hope for an end to all floats in 2020. – Eaten by a Grue Dec 13 '19 at 22:33
1

You can make the main element to be inline-block and use calc to set the width. This shouldn't affect your layout a lot and you will get the correct output:

.main {
  width:calc(100% - 100px);
  display:inline-block;
  background: lightgreen;
}

Full code:

.wrapper {
  background-color: #ccc;
  clear: both;
}
.wrapper+.wrapper {
  margin-top: 50px;
}

.side,
.main {
  height: 100px;
  padding: 10px;
  box-sizing: border-box;
  margin-top: 20px;
}

.box {
  padding: 10px;
}

.top {
  background: yellow;
}

.side {
  width: 100px;
  float: left;
  background: lightblue;
}

.main {
  width:calc(100% - 100px);
  display:inline-block;
  background: lightgreen;
}
<div class="wrapper">
  <div class="top"></div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>

<div class="wrapper">
  <div class="top">
    <div class="box">top</div>
  </div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>

Another hacky idea is to make sure your top element is never empty:

.top:empty {
  font-size:0;
}
.top:empty::before {
  content: "\80"; /* a random character */
}

Full code

.wrapper {
  background-color: #ccc;
  clear: both;
}

.wrapper+.wrapper {
  margin-top: 50px;
}

.side,
.main {
  height: 100px;
  padding: 10px;
  box-sizing: border-box;
  margin-top: 20px;
}

.box {
  padding: 10px;
}

.top {
  background: yellow;
}

.side {
  width: 100px;
  float: left;
  background: lightblue;
}

.main {
  margin-left: 100px;
  background: lightgreen;
}

.top:empty {
  font-size:0;
}
.top:empty::before {
  content: "\80"; /* a random character */
}
<div class="wrapper">
  <div class="top"></div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>

<div class="wrapper">
  <div class="top">
    <div class="box">top</div>
  </div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>

You can also consider the same trick but using a pseudo element on the main wrapper:

.wrapper::before {
  content: "\80"; /* a random character */
  display:block;
  font-size:0;
}

Full code

.wrapper {
  background-color: #ccc;
  clear: both;
}

.wrapper+.wrapper {
  margin-top: 50px;
}

.side,
.main {
  height: 100px;
  padding: 10px;
  box-sizing: border-box;
  margin-top: 20px;
}

.box {
  padding: 10px;
}

.top {
  background: yellow;
}

.side {
  width: 100px;
  float: left;
  background: lightblue;
}

.main {
  margin-left: 100px;
  background: lightgreen;
}

.wrapper::before {
  content: "\80"; /* a random character */
  display:block;
  font-size:0;
}
<div class="wrapper">
  <div class="top"></div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>

<div class="wrapper">
  <div class="top">
    <div class="box">top</div>
  </div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>

You can also make the wrapper inline-block with a width equal to 100% and it will behave almost the same as a block element:

.wrapper {
  background-color: #ccc;
  display:inline-block;
  width:100%;
  vertical-align:top; /* avoid some unwanted white space issue*/
}

.wrapper+.wrapper {
  margin-top: 50px;
}

.side,
.main {
  height: 100px;
  padding: 10px;
  box-sizing: border-box;
  margin-top: 20px;
}

.box {
  padding: 10px;
}

.top {
  background: yellow;
}

.side {
  width: 100px;
  float: left;
  background: lightblue;
}

.main {
  margin-left: 100px;
  background: lightgreen;
}
<div class="wrapper">
  <div class="top"></div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>

<div class="wrapper">
  <div class="top">
    <div class="box">top</div>
  </div>
  <div class="side">side</div>
  <div class="main">main</div>
</div>

For the explanation, you are facing a margin collpasing issue like described in the specification:

Two margins are adjoining if and only if:

  • both belong to in-flow block-level boxes that participate in the same block formatting context
  • no line boxes, no clearance, no padding and no border separate them (Note that certain zero-height line boxes (see 9.4.2) are ignored for this purpose.)
  • both belong to vertically-adjacent box edges, i.e. form one of:
    • top margin of a box and top margin of its first in-flow child
Temani Afif
  • 211,628
  • 17
  • 234
  • 311
  • Thanks - I actually did try this and yes it works. I was trying to avoid doing this since in the rea life app there are a ton of responsive rules that will need to be rewritten to accommodate. Atm though, it does seem like possibly the best solution and requires the least amount of revisions as far as I can see. – Eaten by a Grue Dec 13 '19 at 22:42
  • actually `border: 1px solid transparent;` on the wrapper is the least intrusive fix but it seems odd and I'm not sure *why it works*, hence my reluctance to use it – Eaten by a Grue Dec 13 '19 at 22:44
  • @billynoah because it disable the margin collapsing ... if there is anything between parent and child margin they won't collapse (padding or border or another element) – Temani Afif Dec 13 '19 at 22:46
  • 1
    @billynoah added another idea ;) will probably add more as there is a lot of tricks for this – Temani Afif Dec 13 '19 at 22:48
  • that latest idea actually made me chuckle, but thanks, that's a new one for me – Eaten by a Grue Dec 13 '19 at 22:57
  • @billynoah now you have 4 methods ;) – Temani Afif Dec 13 '19 at 23:00