Parent is inline-block and child has % padding = strange behaviour - css

I've been doing CSS for a while now but couldn't figure out what's going here. Feeling really dumb :) Could you explain the behaviour?
.parent {
display:inline-block;
}
.child {
border: 2px solid red;
padding: 20px; /* this works as expected */
padding: 20%;
box-sizing: border-box; /* makes no difference */
}
<div class="parent">
<div class="child">CSSisAwesome</div>
</div>

You are facing a cyclic calculation due to the use of percentage value. The parent is an inline-block element so its width is defined by its content and that same content is using a percentage value so the content need a reference for that percentage which is the width of the parent. You have a cycle.
In such case, the browser will first ignore the padding to define the parent width and then calculate the padding BUT we don't get to calculate the parent width again because will have an infinite loop.
Check this:
.parent {
display: inline-block;
}
.child {
border: 2px solid red;
}
<div class="parent">
<div class="child">CSSisAwesome</div>
</div>
<br>
<br>
<div class="parent">
<div class="child" style="padding: 20%;">CSSisAwesome</div>
</div>
Note how in both cases, the width of the parent is the same and that width is defined by the content. The padding is added later and create an overflow.
You can find mode detail in the Specification
Sometimes the size of a percentage-sized box’s containing block depends on the intrinsic size contribution of the box itself, creating a cyclic dependency.
Related questions:
Why does percentage padding break my flex item?
CSS Grid - unnecessary word break
How percentage truly works compared to other units in different situations

As seen in this CSSTricks article, padding using percentage units is in relation to the parent container, not the content within the element. The 20% padding you're setting in your code snippet is in relation to the .parent div's dimensions, not in relation to the content within the .child div.

If you are using % as a unit, Parent should have fixed width and height

Related

Overflow scroll/auto and height: 100% [duplicate]

How can I prevent a child div with scrollbars and flex:1 from exceeding the height of its parent flexbox in Firefox? It works correctly in Chrome.
CodePen link (if you prefer it to Stack Overflow snippets):
https://codepen.io/garyapps/pen/ZMNVJg
Details:
I have a flex container of fixed height. It has a flex-direction:column setting, and it contains multiple childen divs which will get vertically stacked. One of the child divs is given a flex:1 property, whereas others are given fixed heights.
My expectation is that the child div with the flex:1 property will expand to fill the remaining vertical space. This works as expected.
I have also given the child div an overflow-y:scroll property, so that if the content within it exceeds its height, scrollbars appear. This works fine in Chrome. In Firefox however, this child's height increases, exceeding its parent div.
In Chrome:
In Firefox:
As you see in the screenshot, I have two occurrences of this issue, once in the panel on the left, and the other in the panel on the right. In Chrome, the height of the child div remains constant, scrollbars appear, and the height of the parent does not get exceeded. In Firefox scrollbars do not appear, and the height of the child div increases and exceeds its parent.
I have looked at a few other similar questions on SO, and in many cases setting a min-height:0 property solved the problem in Firefox. However I have tried adding min-height:0 to parents, children, both parents and children, and had no luck.
Please run the code snippet below in both Chrome and Firefox to see the difference in the two browsers.
I would appreciate any advice on how to prevent child div from growing.
(Note that Bootstrap 4 is being used. The code snippet references the bootstrap 4 .css file CDN)
Code Snippet:
.body-content {
height: 300px;
max-height:100%;
border: 3px dashed purple;
display:flex;
flex-direction: column;
}
#messagescontainerrow {
flex: 1;
border: 5px double black;
}
#leftdiv {
display:flex;
flex-direction:column;
border: 1px solid green;
}
#messagetools {
height: 50px;
background-color: cornsilk;
}
#messagelist {
flex:1;
overflow-y: scroll;
background-color:whitesmoke;
}
#rightdiv {
display:flex;
flex-direction:column;
border: 1px solid blue;
}
#messagesenderspane {
width: 100%;
height: 50px;
background-color: lemonchiffon
}
#messagecontents {
flex: 1;
overflow-y: scroll;
width: 100%;
border: 1px solid blue;
background-color: aliceblue;
}
#messagesend {
width: 100%;
height: 70px;
background-color: whitesmoke;
}
<html>
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container body-content">
<div class="row" id="messagescontainerrow">
<div class="col-5" id="leftdiv">
<div id="messagetools">
<input type="button" id="newbutton" value="My Button" />
</div>
<div id="messagelist">
<h4>Chat 1</h4>
<h4>Chat 2</h4>
<h4>Chat 3</h4>
<h4>Chat 4</h4>
<h4>Chat 5</h4>
<h4>Chat 6</h4>
<h4>Chat 7</h4>
<h4>Chat 8</h4>
</div>
</div>
<div class="col-7" id="rightdiv">
<div id="messagesenderspane">
Chat 3
</div>
<div id="messagecontents">
<h4>line 1</h4>
<h4>line 2</h4>
<h4>line 3</h4>
<h4>line 4</h4>
<h4>line 5</h4>
<h4>line 6</h4>
<h4>line 7</h4>
</div>
<div id="messagesend">
<textarea id="sendbox"></textarea>
<input type="button" id="sendbutton" value="Send" />
</div>
</div>
</div>
</div>
</body>
</html>
Short Answer
Instead of flex: 1, use flex: 1 1 1px.
Make these two adjustments in your code:
#messagelist {
/* flex:1; */
flex: 1 1 1px; /* new */
}
#messagecontents {
/* flex:1; */
flex: 1 1 1px; /* new */
}
revised codepen
Explanation
In most cases, as you have noted, adding min-height: 0 to flex items in a column-direction container is enough to correct the problem.
In this case, however, there's an additional obstacle: flex-basis.
You're applying the following rule to flex items #messagelist and #messagecontents: flex: 1.
This is a shorthand rule that breaks down to:
flex-grow: 1
flex-shrink: 1
flex-basis: 0
(source: https://www.w3.org/TR/css-flexbox-1/#flex-common)
2019 UPDATE: Since the posting of this answer in 2018, it appears that Chrome's behavior has changed and is now uniform with Firefox and Edge. Please keep that in mind as you read the rest of this answer.
In Chrome, flex-basis: 0 is enough to trigger an overflow, which generates the scrollbars. (2019 update: This may no longer be the case.)
In Firefox and Edge, however, a zero flex-basis is insufficient. This is probably the more correct behavior in terms of standards compliance as MDN states:
In order for overflow to have an effect, the block-level container must have either a set height (height or max-height) or white-space set to nowrap.
Well, flex-basis: 0 meets none of those conditions, so an overflow condition should not occur. Chrome has probably engaged in an intervention (as they often do).
An intervention is when a user agent decides to deviate slightly from a standardized behavior in order to provide a greatly enhanced user experience.
To meet the "standardized behavior", which would enable an overflow to occur in Firefox and Edge, give flex-basis a fixed height (even if it's just 1px).
I am marking Michael_B's answer as the correct one, since it is a valid solution along with an explanation. In addition here is another solution I came up with, which does not require modifying the flex-basis:
#messagescontainerrow {
flex: 1;
min-height: 0; /* ADDED THIS. */
border: 5px double black;
}
#leftdiv {
display:flex;
flex-direction:column;
max-height: 100%; /* ADDED THIS */
border: 1px solid green;
}
#rightdiv {
display:flex;
flex-direction:column;
max-height: 100%; /* ADDED THIS */
border: 1px solid blue;
}
Explanation:
As per the current Flexbox specification https://www.w3.org/TR/css-flexbox-1/#min-size-auto
In general, the automatic minimum size of a flex item is the smaller
of its content size and its specified size. However, if the box has an
aspect ratio and no specified size, its automatic minimum size is the
smaller of its content size and its transferred size. If the box has
neither a specified size nor an aspect ratio, its automatic minimum
size is the content size.
So, by default #messagescontainerrow was taking on a minimum height based on its contents, rather than respecting the height of its parent flexbox. This behavior can be overridden by setting min-height:0.
By making this change one sees what is displayed in the following image; note that #messagescontainerrow - the one with the double line border - is now the same height as its parent - the one with the purple dashed border.
(Note that the more recent draft specification, found here - https://drafts.csswg.org/css-flexbox/#min-size-auto - says "for scroll containers the automatic minimum size is zero". So in future we might not need to do this).
What remains now is the issue of its children, #leftdiv and #rightdiv, overflowing its borders. As Michael_B pointed out, overflow requires a height or max-height property to be present. So the next step is to add max-height: 100% to both #leftdiv and #rightdiv, so that the overflow-y:scroll property of their children gets triggered.
Here is the result:

Prevent flex item from exceeding parent height and make scroll bar work

How can I prevent a child div with scrollbars and flex:1 from exceeding the height of its parent flexbox in Firefox? It works correctly in Chrome.
CodePen link (if you prefer it to Stack Overflow snippets):
https://codepen.io/garyapps/pen/ZMNVJg
Details:
I have a flex container of fixed height. It has a flex-direction:column setting, and it contains multiple childen divs which will get vertically stacked. One of the child divs is given a flex:1 property, whereas others are given fixed heights.
My expectation is that the child div with the flex:1 property will expand to fill the remaining vertical space. This works as expected.
I have also given the child div an overflow-y:scroll property, so that if the content within it exceeds its height, scrollbars appear. This works fine in Chrome. In Firefox however, this child's height increases, exceeding its parent div.
In Chrome:
In Firefox:
As you see in the screenshot, I have two occurrences of this issue, once in the panel on the left, and the other in the panel on the right. In Chrome, the height of the child div remains constant, scrollbars appear, and the height of the parent does not get exceeded. In Firefox scrollbars do not appear, and the height of the child div increases and exceeds its parent.
I have looked at a few other similar questions on SO, and in many cases setting a min-height:0 property solved the problem in Firefox. However I have tried adding min-height:0 to parents, children, both parents and children, and had no luck.
Please run the code snippet below in both Chrome and Firefox to see the difference in the two browsers.
I would appreciate any advice on how to prevent child div from growing.
(Note that Bootstrap 4 is being used. The code snippet references the bootstrap 4 .css file CDN)
Code Snippet:
.body-content {
height: 300px;
max-height:100%;
border: 3px dashed purple;
display:flex;
flex-direction: column;
}
#messagescontainerrow {
flex: 1;
border: 5px double black;
}
#leftdiv {
display:flex;
flex-direction:column;
border: 1px solid green;
}
#messagetools {
height: 50px;
background-color: cornsilk;
}
#messagelist {
flex:1;
overflow-y: scroll;
background-color:whitesmoke;
}
#rightdiv {
display:flex;
flex-direction:column;
border: 1px solid blue;
}
#messagesenderspane {
width: 100%;
height: 50px;
background-color: lemonchiffon
}
#messagecontents {
flex: 1;
overflow-y: scroll;
width: 100%;
border: 1px solid blue;
background-color: aliceblue;
}
#messagesend {
width: 100%;
height: 70px;
background-color: whitesmoke;
}
<html>
<head>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container body-content">
<div class="row" id="messagescontainerrow">
<div class="col-5" id="leftdiv">
<div id="messagetools">
<input type="button" id="newbutton" value="My Button" />
</div>
<div id="messagelist">
<h4>Chat 1</h4>
<h4>Chat 2</h4>
<h4>Chat 3</h4>
<h4>Chat 4</h4>
<h4>Chat 5</h4>
<h4>Chat 6</h4>
<h4>Chat 7</h4>
<h4>Chat 8</h4>
</div>
</div>
<div class="col-7" id="rightdiv">
<div id="messagesenderspane">
Chat 3
</div>
<div id="messagecontents">
<h4>line 1</h4>
<h4>line 2</h4>
<h4>line 3</h4>
<h4>line 4</h4>
<h4>line 5</h4>
<h4>line 6</h4>
<h4>line 7</h4>
</div>
<div id="messagesend">
<textarea id="sendbox"></textarea>
<input type="button" id="sendbutton" value="Send" />
</div>
</div>
</div>
</div>
</body>
</html>
Short Answer
Instead of flex: 1, use flex: 1 1 1px.
Make these two adjustments in your code:
#messagelist {
/* flex:1; */
flex: 1 1 1px; /* new */
}
#messagecontents {
/* flex:1; */
flex: 1 1 1px; /* new */
}
revised codepen
Explanation
In most cases, as you have noted, adding min-height: 0 to flex items in a column-direction container is enough to correct the problem.
In this case, however, there's an additional obstacle: flex-basis.
You're applying the following rule to flex items #messagelist and #messagecontents: flex: 1.
This is a shorthand rule that breaks down to:
flex-grow: 1
flex-shrink: 1
flex-basis: 0
(source: https://www.w3.org/TR/css-flexbox-1/#flex-common)
2019 UPDATE: Since the posting of this answer in 2018, it appears that Chrome's behavior has changed and is now uniform with Firefox and Edge. Please keep that in mind as you read the rest of this answer.
In Chrome, flex-basis: 0 is enough to trigger an overflow, which generates the scrollbars. (2019 update: This may no longer be the case.)
In Firefox and Edge, however, a zero flex-basis is insufficient. This is probably the more correct behavior in terms of standards compliance as MDN states:
In order for overflow to have an effect, the block-level container must have either a set height (height or max-height) or white-space set to nowrap.
Well, flex-basis: 0 meets none of those conditions, so an overflow condition should not occur. Chrome has probably engaged in an intervention (as they often do).
An intervention is when a user agent decides to deviate slightly from a standardized behavior in order to provide a greatly enhanced user experience.
To meet the "standardized behavior", which would enable an overflow to occur in Firefox and Edge, give flex-basis a fixed height (even if it's just 1px).
I am marking Michael_B's answer as the correct one, since it is a valid solution along with an explanation. In addition here is another solution I came up with, which does not require modifying the flex-basis:
#messagescontainerrow {
flex: 1;
min-height: 0; /* ADDED THIS. */
border: 5px double black;
}
#leftdiv {
display:flex;
flex-direction:column;
max-height: 100%; /* ADDED THIS */
border: 1px solid green;
}
#rightdiv {
display:flex;
flex-direction:column;
max-height: 100%; /* ADDED THIS */
border: 1px solid blue;
}
Explanation:
As per the current Flexbox specification https://www.w3.org/TR/css-flexbox-1/#min-size-auto
In general, the automatic minimum size of a flex item is the smaller
of its content size and its specified size. However, if the box has an
aspect ratio and no specified size, its automatic minimum size is the
smaller of its content size and its transferred size. If the box has
neither a specified size nor an aspect ratio, its automatic minimum
size is the content size.
So, by default #messagescontainerrow was taking on a minimum height based on its contents, rather than respecting the height of its parent flexbox. This behavior can be overridden by setting min-height:0.
By making this change one sees what is displayed in the following image; note that #messagescontainerrow - the one with the double line border - is now the same height as its parent - the one with the purple dashed border.
(Note that the more recent draft specification, found here - https://drafts.csswg.org/css-flexbox/#min-size-auto - says "for scroll containers the automatic minimum size is zero". So in future we might not need to do this).
What remains now is the issue of its children, #leftdiv and #rightdiv, overflowing its borders. As Michael_B pointed out, overflow requires a height or max-height property to be present. So the next step is to add max-height: 100% to both #leftdiv and #rightdiv, so that the overflow-y:scroll property of their children gets triggered.
Here is the result:

How does float property blockify the element?

According to Cascading Style Sheets Level 2 Revision 2 (CSS 2.2) Specification,
when an element is given a float property other than none, it implicitly sets display to block, but the way i see it, its behaving like an inline-block element as it doesn't take 100% of it's parent's width.
an example:
These two blue bloxes are floated to the left so they implicitly set to display:block but they are not taking the whole width of the wrapper div (red-colored rectangle).
HTML Code
<div class="wrapper cf">
<div class="box"></div>
<div class="box"></div>
</div>
CSS Code
.wrapper {
background-color: red;
padding: 10px;
}
.box {
margin: 10px;
height: 100px;
width: 100px;
background: lightblue;
float: left;
}
.cf:after {
content: "";
display: block;
clear: both;
}
“There is a simple solution that fixes many of the IE float bugs. All floats become a block box; the standard says that the display property is to be ignored for floats, unless it’s specified as none. If we set display:inline for a floating element, some of the IE/Win bugs disappears as if by magic. IE/Win doesn’t make the element into an inline box, but many of the bugs are fixed.”
[Float Layouts]
As that suggests, the primary reason the block is added is for fixing issues that came up with floats in IE. Although the display:block is implicitly defined, display values aren't technically applied to floated elements except for if it is set to none.
If you want to learn more about floats, this is a pretty good article: CSS Float Theory: Things You Should Know

Percentage % padding that isn't relative to the parent

I have this piece of HTML
<div class="box">
<div class="box-header">
<h4 class="box-header-title">CONTENT</h4>
</div>
<!-- rest of box -->
</div>
And I have this CSS:
.box {
display: inline-block;
min-width: 360px;
position: relative;
}
.box-header {
color: #fff;
}
.box-header-title {
padding: 0 2%;
}
What I want: That the .box.header-title adjust it's padding according to the current width of .box-header-title instead of .box
Right now, if .box is 500px computed value, the padding will be computed at 10px. I want it to be computed at the current width of the element, instead of the parent box.
According to the CSS specification, percentage values for padding are computed based on the element's containing block (parent element). So what you are seeing is the expected behavior.
Reference: http://www.w3.org/TR/CSS2/box.html#padding-properties
To get the result that you want, you would need to use some JavaScript or jQuery method to compute the desired value.
Why dont you try,
position:absolute;
and give, appropriate, top and left values.

Possible to auto height child div? (not 100% of parent)

Is it possible to simply auto-height a child div to the remaining height not being used by other component of it's parent? For the below example, the .body would only be like 20px high, because it's only using that much for the inner html. Is it possible for the .body to automatically consume the unused height of the .parent? e.g. .parent 200px - .head 30px - .foot 30px = .body 120px?
The sample below will display the .parent yellow box much taller than the used space. If you set .body to "height: 100%", it'll use the parent's height and not respect the .head or .foot elements.
<html>
<head>
<style type="text/css">
.parent {
width: 200px;
height: 200px;
background-color: yellow;
}
.head { height: 30px; background-color: blue; }
.body { background-color: #999; }
.foot { height: 30px; background-color: green; }
</style>
</head>
<body>
<div class="parent">
<div class="head">I'm the head</div>
<div class="body">I'm the body</div>
<div class="foot">I'm the foot</div>
</div>
</body>
</html>
This is only an example. In my project the .parent height can only be reasonably set in the .parent element. Plus the .parent height is essentially dynamically set by the back-end code. The three inner div organization is because the body is collapsible and I have rounded corners for the head and foot.
Any suggestions are well appreciated!
This can easily be achieved with negative margins!
Set .body to 100% height
Assuming that the height of .head and .foot is known, you can add a negative top + bottom margin equal to the respective heights of .head and .foot.
Because of the source ordering, the .body will "cover" .head. To counter this, add position: relative to .head.
The inner content of the body need to be shifted down a bit. You cannot add padding to .body directly. Better, add another dive inside .body with padding top + bottom set to desired height.
Demo here
Variant of the above example:
Set .body to 100% height
Assuming that the height of .head and .foot is known, you can add a negative bottom margin equal to the sum of heights of .head and .foot.
Since .body will attempt to flow outside the parent, add overflow: hidden to the parent.
Demo here
There are currently two ways to achieve this. Both are somewhat unsatisfactory.
The first is to calculate the remaining height using DOM information via JS.
The second is called CSS3 flexbox and works perfectly, but is an immature specification with currently very little support.
Unfortunately this can't be done using CSS 2.1 and that's one of the reasons why CSS sucks so badly.

Resources