You Old Fool
You Old Fool

Reputation: 22941

Unexpected result from margin-top next to a floated element

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:

.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>

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.

Upvotes: 2

Views: 49

Answers (2)

Temani Afif
Temani Afif

Reputation: 272789

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

Upvotes: 1

symlink
symlink

Reputation: 12209

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>

Upvotes: 1

Related Questions