I've come across a strange behavior related to the margins of a <div> element. I've added a very simple test case to jsFiddle to show you what I mean.
Interestingly the margins are used correctly when using a border. Is this the default behavior? Does this have something to do with collapsing margins? Is it possible to somehow force the margins without any hacks?
Least ugly solution so far that I thought of:
.cssContainer {
margin:10px;
padding:0.1px;
background-color:#FF0000;
}
But I don't know whether this will work in every browser.
This is another case of adding overflow:auto to the rescue!
jsFiddle example
Just add overflow:auto to your container div and voila, harmony is restored.
.cssContainer {
margin:10px;
padding:0px;
overflow:auto;
background-color:#FF0000;
}
From specs, the reason this works is because a new block formatting context is established by applying overflow other than visible, forcing margins of .cssContainer to not collapse with its in-flow children .cssElement.
From this site
the simplest way to stop the margin collapse from occurring is to add
padding or borders to each element. If we wanted 10px margins on each
element we could simply use a 9px margin and 1px of padding to get the
result we wanted
This is due to the CSS box model. The elements with the red backgrounds will not grow in height to accommodate the top and bottom padding of its children elements, but because the border was specified in the second example and encompasses the padding, the padding will show.
The overflow: auto; property works because the padding overflows its parent element. To illustrate this, in Google Chrome Developer Tools, find and click the HTML row for the green element whose bottom padding will not show. Chrome will highlight the element on the page. Notice how the padding is included in the highlighted element. The padding actually overflows the parent element. The parent element won't show it though because there's no content in or after the padding (no 'bounds').
From the css spec regarding box models:
Adjoining vertical margins collapse, except:
...
Horizontal margins never collapse.
Two margins are adjoining if and only if:
...
both belong to vertically-adjacent box edges, i.e. form one of the following pairs:
top margin of a box and top margin of its first in-flow child
bottom margin of box and top margin of its next in-flow following sibling
bottom margin of a last in-flow child and bottom margin of its parent if the parent has 'auto' computed height
...
Note the above rules imply that:
...
Margins of elements that establish new block formatting contexts (such
as floats and elements with 'overflow' other than 'visible') do not
collapse with their in-flow children.
Unfortunately, text-overflow: auto is slightly risky because the spec says:
auto
The behavior of the 'auto' value is user agent-dependent,
but should cause a scrolling mechanism to be provided for overflowing boxes
However, all browsers that I have looked at appear to implement it in the following manner:
The box is expanded to the smaller amount between:
as much as its containing box allows
as much as is necessary to display the content without clipping
Then if clipping must occur, a scrollbar is added.
Related
As you can see in this simple example:
<div id="minheight">
<p id="margin">Paragraph with a margin</p>
</div>
<div id="sibling">Sibling div</div>
#minheight {
min-height: 100px;
background: red;
}
#sibling {
background: blue;
}
http://jsfiddle.net/peterbriers/B43th
There is a difference between Chrome (35) and Firefox (29) in how it handles the collapsing margins on a block with a min-height that is larger than the child's margin.
I tried to fully understand the specifications: http://www.w3.org/TR/CSS2/box.html#collapsing-margins , but I'm still unsure which browser handles this correctly. I would say Chrome is in the wrong, but Safari (7) does it the Chrome way too.
Which browser is correct, and how can I file a bug for the one that isn't doing it the right way?
BTW: I'm not asking any fix by adding new block formatting context (adding overflow property)...
OK, so this seems to be a very peculiar case.
If you change min-height to height, the gap disappears in Chrome. Not only does Safari behave the same as Chrome, but so does IE. Firefox's behavior is unique to itself, and its behavior does not change when you make that adjustment to your CSS. This should come as a surprise, as you would not expect min-height and height to behave any differently in your given scenario.
However, the spec has something interesting to say about min-height with respect to margin collapsing:
The following algorithm describes how the two properties influence the used value of the 'height' property:
[...]
These steps do not affect the real computed values of the above properties. The change of used 'height' has no effect on margin collapsing except as specifically required by rules for 'min-height' or 'max-height' in "Collapsing margins" (8.3.1).
Because you have not specified a fixed value for the height property on the same element that has a min-height, the computed value for height remains the default auto, even though the used value is floored to min-height.
Therefore the following text from section 8.3.1 applies, and the margins between the block box and its child should collapse as a result, irrespective of min-height:
Two margins are adjoining if and only if:
both belong to vertically-adjacent box edges, i.e. form one of the following pairs:
...
bottom margin of a last in-flow child and bottom margin of its parent if the parent has 'auto' computed height
Note that it goes on to list some scenarios in which margins may or may not collapse:
Note the above rules imply that:
...
The bottom margin of an in-flow block box with a 'height' of 'auto' and a 'min-height' of zero collapses with its last in-flow block-level child's bottom margin if the box has no bottom padding and no bottom border and the child's bottom margin does not collapse with a top margin that has clearance.
... but it does not state what happens when the block box has height: auto and a non-zero min-height.
Based on this, it would be safe to assume that the spec should be interpreted as I am doing. Therefore it looks like Firefox is not behaving quite correctly, and all other browsers are following the spec to the letter, despite what one might expect from the behavior of height and min-height.
You can file a bug for Firefox here, although it looks like the developers have already made themselves aware of this issue.
Heres the fiddle
When I set #two to inline-block it subtracts the 16 px of top/bottom margin from the <p> and adds it to the divs content box height so it becomes 52px instead of 20px .. why is this the case?
What you're seeing is one of the stranger cases of margin collapsing.
If the parent and children are block elements and there's nothing (padding, a border, etc.) separating their vertical margins, then those margins will collapse. Collapsed margins are when two neighboring margins aren't added (as you might expect), but instead the larger of the two is displayed. In the parent-child case, the collapsed margin ends up outside the parent. You can read more details under the section Parent and first/last child in the above link.
Setting the parent to inline-block, or float:left;ing it or a number of other things (refer to the link for a more complete list) will stop the margins from collapsing. This leads to the behavior we're used to: the child's margin will appear inside the parent, adding to its total height, and the parent's margin will also be displayed.
To elaborate, and expand on the existing answers..
This behavior is known as collapsing margins.
8.3.1 Collapsing margins
In CSS, the adjoining margins of two or more boxes (which might or might not be siblings) can combine to form a single margin. Margins that combine this way are said to collapse, and the resulting combined margin is called a collapsed margin.
To work around this, you need to establish a new block formatting context:
9.4.1 Block formatting contexts
Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with 'overflow' other than 'visible' (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.
In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block. The vertical distance between two sibling boxes is determined by the 'margin' properties. Vertical margins between adjacent block-level boxes in a block formatting context collapse.
Therefore a few different ways to establish a new block formatting would be to..
Change the overflow property of the parent element to something other than the default value, visible - (updated example)
Change the display of the element to inline-block - (updated example)
Float the element - (updated example)
This has already been answered and accepted, still I'd like to point out that clearfixing it would have prevented margin collapse thus normalizing its behaviour
I'd add:
.two:before,
.two:after {
content: " ";
display: table;
}
.two:after {
clear: both;
}
See this fiddle .
Here's the Nicholas Gallagher clearfix I've used.
Paragraphs have margins built in (in most browsers).
Try this:
p
{
margin: 0px;
}
This is my code :
HTML
<div class='father'>
<div class='son'>Son</div>
</div>
CSS
.father
{
background-color:blue;
}
.son
{
margin-top:50px;
background-color:red;
height:50px;
}
Where is the background-color blue of the father?
I know how to fix this problem (putting padding-top:1px to the father) but I'd like to know why of this behaviour!
For me it doesnt make sense, because on every browsers, not only IE, this is the behaviour.
This is a result of Collapsing Margins. You can read a good article by Eric Meyer on this topic where he illustrates this exact behavior. The following image is from his article.
Here's what the CSS2 specification has to say about it.
8.3.1 Collapsing margins
If the top and bottom margins of a box are adjoining, then it is
possible for margins to collapse through it. In this case, the
position of the element depends on its relationship with the other
elements whose margins are being collapsed.
If the element's margins are collapsed with its parent's top margin, the top border edge of the box is defined to be the same as
the parent's.
Otherwise, either the element's parent is not taking part in the margin collapsing, or only the parent's bottom margin is involved.
The position of the element's top border edge is the same as it
would have been if the element had a non-zero bottom border.
Note that the positions of elements that have been collapsed through
have no effect on the positions of the other elements with whose
margins they are being collapsed; the top border edge position is only
required for laying out descendants of these elements.
Source: http://www.w3.org/TR/CSS2/box.html#collapsing-margins
This is because the div is a block-level element. Certain block-level elements don't contain any padding by default. Divs are one such element. Block-level elements will take up the entire height and width of the container while respecting any padding it may contain.
W3C Visual formatting model
The parent element's height is set to its content's height by default. Once you set a height on the parent, that's no longer the default. Checking up on it, I believe that the padding adds to the height. So, the height is originally determined by the content unless otherwise stated in the CSS. Then, in most cases (IE 6 may be the exception), the padding is added to the height.
Good thing about SO, it helps us be much more detailed in our responses. :)
In CSS, block level elements naturally fills the elements content area, so your "son" div is filling your "father" div completely. Of course, you can bypass this by adding margin/padding/height to your parent div.
You're setting blue explicitly:
.father {
background-color:blue;
}
It's overridden (thanks to the "C" in CSS a.k.a cascading) but the style remains on your parent element (here, appropriately named "father").
If your .father box receives any height at all (check in the Firebug/Chrome dev tools inspector) than the blue is going to show through. I am guessing this is what you're seeing in IE (that or perhaps there's a flash of content before your child element style comes in). I don't think the IE debug tools show bounding boxes but you can test the element for it's height using JavaScript.
Moving outwards from the content, you have padding, border, and margin (as you probably know). Background covers only padding, but not margin.
HTML and CSS boggle my mind sometimes.
A DIV with a border shows its background color for the full height of the element and its contents. Why is it that without a border, a DIV will assume (reverse inherit?) its child's margins?
As an example, here is a JSFiddle illustrating the behavior with and without borders.
http://jsfiddle.net/ahNUX
http://jsfiddle.net/ahNUX/1/
Does anyone care to explain how this is a "feature" and not some kind of bug?
Update: adding 1px of padding to the parent is a quick fix.
Sure. In CSS, by default, adjacent top and bottom margins overlap each other. This was a sensible workaround before the adjacent sibling selector (+) was thought of/became well-supported, as it meant that if you wrote h2 {margin-top: 3em;}, you’d have 3ems worth of space above your h2s even if there was a paragraph before it with a bottom margin of 1em.
In your second example, because the <div> doesn’t have any top or bottom padding or borders, its top and bottom margins are adjacent to the <h1>'s default top and bottom margins. Even though the <div>’s margins don’t have any height, they’re still treated as if they exist, and so the <h1>’s margins have to overlap them. As the <div>s margins are by definition outside of the background-color area of the <div>, the <h1>’s margins have to be positioned outside too.
In your first example, because the <div> has a border, its margins are no longer adjacent to the <h1>’s margins, so no overlapping occurs. You can get the same effect by adding top and bottom padding to the <div>: http://jsfiddle.net/ahNUX/7/
(I’m not sure what you mean about the <div> “reverse-inheriting” its child’s padding though. In your examples, neither the <div> nor the <h1> have any padding. The space inside the <div> in your first example is created by the <h1>’s top and bottom margin.)
This is a result of collapsing margins. See http://www.w3.org/TR/CSS2/box.html#collapsing-margins
In a nutshell the top and bottom margin on the <h1> element in your example may have its margin collapse (or overlap) with another element that's above or below it, which would result in a conflict with the background color, so it's not shown. On the other hand, if you have a border, the rules change.
The spec explains it pretty well, albeit in a dry and somewhat technical tone.
I have a problem with the margin-top in a nested div -- when I apply margin-top to the nested div, the margin is applied to the parent div instead of the nested div.
Any ideas?
I get the solution with overflow:auto in the parent div.
Margins will collapse by design. Add 1px of padding as well and it should work fine.
This is how margins work.. the margin is the space between the next element with a margin / padding / similar. It is not necessarily defined as its parent element. Consult the spec.
Here are some things you can do as a workaround
Use Padding Instead
This just means instead of using margin-top: 10px; you use padding-top: 10px;. This is not suitable for every occasion.
Weird Hack I Discovered
I doubt I discovered this initially, but the other day I solved the problem like this. I had a <div id="header" /> that I wanted to give a top margin too, and its top margin was pushing the parent (body element) down as well. So I did this on the body element...
body {
padding-top: 1px;
margin-top: -1px;
}
This made my margin work. You can also try using a border, like border: 1px solid #ccc.
It would also be wise to leave a note in the CSS comments to explain why you have that peculiar pair of rules. I just added /* this is to stop collapsing margins */.
Further Reading
Check out these other questions on Stack Overflow
The reason overflow:auto changes the parent div to allow the nested margin-top is that it creates a new formatting context. Any div that's positioned absolute, fixed, floated or with overflow other than visible creates a new formatting context. The parent div then becomes the root of this new formatting context, and collapsing margins don't apply to root elements.
More in depth:
Formatting contexts can be either inline or block, and only the block formatting contexts collapse their margins. These formatting contexts form the flow of the document.
A block formatting context is simply all the block level elements (e.g. divs, p, table) laid out one after another vertically within a containing block until the end of the document/container or until a new formatting context is established.
In the case of nesting, the margin-top of the child collapses with the margin-top of the parent since both divs are part of a block formatting context. By setting overflow to auto, the parent div becomes the container of the new formatting context, and the child the first block element within it. Thus the two margins are no longer "adjoining" since the parent div is now the root.
Hope that helps.
Box model:
http://www.w3.org/TR/CSS2/box.html#collapsing-margins
Visual formatting model:
http://www.w3.org/TR/CSS2/visuren.html#normal-flow
"Collapsing margins" is your problem. Here you could understand what is and why it's still alive: http://www.sitepoint.com/web-foundations/collapsing-margins/
I read across the web to look for a decent solution, and finally I found this article: http://www.seifi.org/css/understanding-taming-collapsing-margins-in-css.html
In short you have a bunch of methods to resolve your problem:
1) border in parent div (could be transparent)
2) padding in parent div
3) overflow: auto
4) float: left
You should follow the link because it explains in detail all the solutions.
You Can also use position property for inner div to fix this. like:
position:fixed;