Are Block-Level Boxes Enclosed in Line Boxes? - html

Is there somewhere in the W3C spec, that specifies that block-level boxes get surrounded in line boxes automatically?
Background
As we know, vertical padding and border of inline boxes do not affect line box height. Line box height, in turn, is the only feature of inline boxes that affects position of subsequent content. This, in turn, implies that padding / border on inline boxes will bleed onto surrounding content.
Quote from W3C CSS 2.1, Chapter 10:
Although margins, borders, and padding of non-replaced elements do not
enter into the line box calculation, they are still rendered around
inline boxes. This means that if the height specified by line-height
is less than the content height of contained boxes, backgrounds and
colors of padding and borders may "bleed" into adjoining line boxes.
Note how it says: "bleed into adjoining line boxes".
I did a test and noticed that it does in fact also bleed into adjoining block-level boxes.
.one {
border: 1px solid green;
}
.two {
border: 1px solid red;
}
.three {
line-height: normal;
border: 1px solid blue;
padding: 10px 0;
}
<div class="one">test</div>
<span class="two">
<span class="three">test two</span>
x
</span>
Therefore, I was wondering, does it say anywhere in the spec that block-level boxes are also surrounded by line boxes? I could only find relevant line box details under inline formatting context, within which only inline-level boxes participate.
Thank you.

I'm not actually sure if a block-level box can be said to be adjoining to a line box (or vice versa). However, in your example, there are in fact two line boxes — one that is generated by div.one when formatting its inline contents by establishing an inline formatting context, and another that is generated by the body element when formatting span.two (and its span.three child) — and the line box inside div.one can be said to be adjoining to the one containing span.two and span.three.
For the purposes of drawing inline boxes, it doesn't matter which block container is generating each line box that's adjoining to those containing those inline boxes. It doesn't even matter whether any of the block containers also establishes a block formatting context. That is why you see the inline box of span.three bleed into the line box inside div.one.
A block-level box cannot directly coexist with a line box, and by definition of "block-level" it cannot be enclosed in a line box either (to answer your title). In your example, the line box on which the spans are rendered resides in an anonymous block box that is then laid out as a sibling of the div. That anonymous block box establishes its own inline formatting context for the spans, just like the div does for its text.

Related

How to determine height of content-box of a block and inline element

I thought 'what determines height of content-box' and thought line-height might.
It seems to be the case you can determine it for block elements with line-height.
<p style="background-color: grey; font-size: 16px; line-height: 2em;">Is line-height same as height of p tag?</p>
<div style="background-color: lightsalmon; font-size: 16px; line-height: 2em;">Is line-height same as height of div tag?</div>
<span style="background-color: aquamarine; font-size: 16px; line-height: 2em;">Is line-height same as height of span tag?</span>
Inline elements seems a different story. Would someone be able to elaborate?
For block elements its quite easy but you need to distinguish between two different cases. When we have an IFC (inline formatting context) or BFC (block formatting context). From the specification you can read
If 'height' is 'auto', the height depends on whether the element has any block-level children and whether it has padding or borders:
The element's height is the distance from its top content edge to the first applicable of the following:
the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin does not collapse with the element's bottom margin
the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
zero, otherwise
Only children in the normal flow are taken into account (i.e., floating boxes and absolutely positioned boxes are ignored, and relatively positioned boxes are considered without their offset)
If we consider an IFC then the line boxes will define our height exactly like your examples where in the div and p you have one line box defined by the line-height.
If we consider a BFC then the (2) and (3) apply. Here, you can see it as a recursive definition because having a BFC means we have other blocks inside and those blocks will follow the same rules (either BFC or IFC and so on). In addition, we consider the margin collapsing rules to get the final height.
If the block is empty then it's zero.
Of course, this consider the case of height:auto. If you explicitely set a height then it's trivial.
For inline elements you can refer to this part of the specification:
The 'height' property does not apply. The height of the content area should be based on the font, but this specification does not specify how. A UA may, e.g., use the em-box or the maximum ascender and descender of the font. (The latter would ensure that glyphs with parts above or below the em-box still fall within the content area, but leads to differently sized boxes for different fonts; the former would ensure authors can control background styling relative to the 'line-height', but leads to glyphs painting outside their content area.)
Here is a bit tricky but the rule is simple: you cannot control or set the height of the content area. Only the font properties you will be using will define the final height.
You should also note that the content area is different from the line box.
The vertical padding, border and margin of an inline, non-replaced box start at the top and bottom of the content area, and has nothing to do with the 'line-height'. But only the 'line-height' is used when calculating the height of the line box.
<span style="background-color: aquamarine; font-size: 16px; line-height: 2em;">Is line-height same as height of span tag?</span>
<br>
<span style="background-color: aquamarine; font-size: 16px; line-height: 4em;">Is line-height same as height of span tag?</span>
<br>
<span style="background-color: aquamarine; font-size: 16px; line-height: 8em;">Is line-height same as height of span tag?</span>
If you increase the line-height you will clearly notice that the background won't cover a bigger area but only the line box will be bigger
Some related question to get more details and understand the difference between line box and content area:
Can specific text character change the line height?
Box Model for Inline Elements
Why is there space between line boxes, not due to half leading?
So line-height will indeed define the height of block element and not inline element since the block element height will depend on the height of the line boxes and the height of line boxes are defined with line-height1
Here is an example to show a block element having its height defined by the line box and the content area of the inline element inside will oveflow because they play no role in defining the line boxes:
p {
margin:50px;
font-size:35px;
border:1px solid red;
line-height:0;
}
span {
background:green;
line-height:10px;
}
<p>
<span>some text here and there</span>
</p>
<p>
<span style="font-family:cursive">some text here and there</span>
</p>
In both examples, we have a height equal to 12px for the p (10px of line-height + 2px of border) and our inline element is having a different height in each case because the font is not the same.
1: We should note that in practise it can be more complex and the value of line-height alone isn't enough.
If we read the specficiation we can see that:
On a block container element whose content is composed of inline-level elements, line-height specifies the minimal height of line boxes within the element.
Then
On a non-replaced inline element, line-height specifies the height that is used in the calculation of the line box height.
That's why in the last example I made the line-height of the block element to be 0 so only the line-height of the inline elements inside will define the final height of the linebox
Even the vertical alignment play a role here because if we have multiple elements not aligned the same way and with different line-height, finding the result will be more complex:
p {
margin:50px;
font-size:35px;
border:1px solid red;
line-height:0;
}
span {
background:green;
line-height:1;
}
<p>
<span>AB</span> <span >AB</span>
</p>
<p>
<span>AB</span> <span style="vertical-align:super">AB</span>
</p>
To find the height of a linebox you need to consider 3 factors:
The line-height of the block container (the IFC)
The line-height of the inline elements inside
the vertical alignment of inline elements
Let's not forget that line-height is inherited so if we don't set it explicitely we need to either consider the value of the parent element or the default one.

Box Model for Inline Elements

W3C's CSS2.1 specification, chapter 8.6 The box model for inline elements in bidirectional context, states:
For each line box, UAs must take the inline boxes generated for each
element and render the margins, borders and padding in visual order
(not logical order).
When the element's 'direction' property is 'ltr', the left-most
generated box of the first line box in which the element appears has
the left margin, left border and left padding, and the right-most
generated box of the last line box in which the element appears has
the right padding, right border and right margin.
Questions
It says "left-most generated box", which indicates that the inline-level element creates more than one inline-level box. Is that because at each line break it creates a new anonymous inline-level box?
Why does the padding/border of inline boxes behave as below? Insight into why it shows up the way it does, with basis in the CSS specification, would be greatly appreciated.
.test {
margin: 0 10px;
padding: 20px;
border: 5px solid blue;
}
<span class="test">test2test2test2test2test2test2test2 test2test2test2test2test2test2test2test2test2test2 test2test2test2test2test2test2test2test2test2test2test2test2test2test2test2test2test2</span>
For the second question you may refer to this part of the specification:
The 'height' property does not apply. The height of the content area
should be based on the font, but this specification does not specify
how. A UA may, e.g., use the em-box or the maximum ascender and
descender of the font. (The latter would ensure that glyphs with parts
above or below the em-box still fall within the content area, but
leads to differently sized boxes for different fonts; the former would
ensure authors can control background styling relative to the
'line-height', but leads to glyphs painting outside their content
area.)
The vertical padding, border and margin of an inline, non-replaced box
start at the top and bottom of the content area, and has nothing to do
with the 'line-height'. But only the 'line-height' is used when
calculating the height of the line box.
.test {
margin: 0 10px;
padding: 20px;
border: 5px solid blue;
}
div {
border:1px solid red;
margin:50px 0;
}
<div>
<span class="test">test2test2test2test2test2test2test2 test</span>
</div>
<div>
<span class="test">test2test2test2test2test2test2test2 test st2test2test2test2 test st2test2test2test2 test st2test2test2test2 test</span>
</div>
<div>
<span class="test" style="line-height:50px;">test2test2test2test2test2test2test2 test2test2</span>
</div>
<div>
<span class="test" style="line-height:50px;">test2test2test2test2test2test2test2 test2test2 test2test2test2test2test2test2test2 test2test2</span>
</div>
To answer the first question:
It says "left-most generated box", which indicates that the inline-level element creates more than one inline-level box. Is that because at each line break it creates a new anonymous inline-level box?
Yes, but that's only one reason. An inline-level element can create zero, one or many inline-level boxes. So an inline element with no content or horizontal padding, border, or margin will create zero inline-level boxes. A different way in which an element can create multiple inline-level boxes is if the element contains child elements.
So if we have <span>foo <b>bar</b> baz</span>, then even if that all sits on one line, the span will create one inline-level box for foo, which will get the left margin, left border and left padding, and a separate inline-level box for baz, which will get the right margin, right border and right padding.
The b element creates the inline-level box for bar, which may have its own margins, borders, and paddings.
See Temani's answer for your second question.

Why don't containers expand to fit their inline elements

I simply have a container like a div or a button which contains an inline element with padding - the container doesn't grow to fit the inline element.
button, div {
background: blue;
}
span {
background: orange;
/*display: inline-block; */ /* toggle to see container element grow */
padding: 4em;
}
hr {
margin:100px;
}
<div>
<span class="btn__content">
I'm div inline content
</span>
</div>
<hr>
<button class="btn" type="button">
<span>
I'm button inline content
</span>
</button>
Why does this happen?
NB: I'm not looking for a fix - (for instance, I already know that I could display:flex on the container and that would make the container expand to the content)
Rather, I'm looking for an explanation from a credible source like the spec which explains this behavior of inline elements.
The reason is explained in the Visual Formatting model documentation (emphasis mine):
In an inline formatting context, boxes are laid out horizontally, one
after the other, beginning at the top of a containing block.
Horizontal margins, borders, and padding are respected between these
boxes. The boxes may be aligned vertically in different ways: their
bottoms or tops may be aligned, or the baselines of text within them
may be aligned. The rectangular area that contains the boxes that form
a line is called a line box.
The width of a line box is determined by a containing block and the
presence of floats. The height of a line box is determined by the
rules given in the section on line height calculations.
The line height is then calculated as:
The height of a line box is determined as follows:
The height of each inline-level box in the line box is calculated. For
replaced elements, inline-block elements, and inline-table elements,
this is the height of their margin box; for inline boxes, this is
their 'line-height'. (See "Calculating heights and margins" and the
height of inline boxes in "Leading and half-leading".)
The
inline-level boxes are aligned vertically according to their
'vertical-align' property. In case they are aligned 'top' or 'bottom',
they must be aligned so as to minimize the line box height. If such
boxes are tall enough, there are multiple solutions and CSS 2.1 does
not define the position of the line box's baseline.
The line box height is the distance between
the uppermost box top and the lowermost box bottom. Empty inline elements
generate empty inline boxes, but these boxes still have margins,
padding, borders and a line height, and thus influence these
calculations just like elements with content.
And as MDN notes about the box model:
Note that, for non-replaced inline elements, the amount of space taken
up (the contribution to the height of the line) is determined by the
line-height property, even though the border and padding appear
visually around the content.
and reiterated by the W3 for inline non-replaced elements:
The 'height' property doesn't apply, but the height of the box is
given by the 'line-height' property.
https://www.w3.org/TR/REC-CSS2/visudet.html#q15
Where the line height is "the height that is used to calculate line box height." So while padding or borders might give the look of height to an inline element, only the line-height property will affect parent nodes. As you can see in the example below, the line height on the spans is affecting the height of the parent div.
button,
div {
background: blue;
}
span {
background: orange;
line-height: 10em;
}
hr {
margin: 100px;
}
<div>
<span class="btn__content">
I'm div inline content
</span>
</div>
<hr>
<button class="btn" type="button">
<span>
I'm button inline content
</span>
</button>
See also Inline elements and line-height
I believe it's because of these two sections:
9.4.2 Inline formatting contexts
The height of a line box is determined by the rules given in the section on line height calculations.
10.8 Line height calculations: the 'line-height' and 'vertical-align' properties
The height of each inline-level box in the line box is calculated. For replaced elements, inline-block elements, and inline-table elements, this is the height of their margin box; for inline boxes, this is their 'line-height'.
Setting a line-height does change the height of the containing box
button, div {
background: blue;
}
span {
background: orange;
/*display: inline-block; */ /* toggle to see container element grow */
padding: 4em;
line-height: 8em;
}
hr {
margin:100px;
}
<div>
<span class="btn__content">
I'm div inline content
</span>
</div>
<hr>
<button class="btn" type="button">
<span>
I'm button inline content
</span>
</button>

Why does x-overflow:hidden cause extra space below?

I have two spans inside each other. On the inner span I have overflow-x:hidden. This causes extra space below the inner span. Why?
<span style="" class="yavbc-color-tip"><span style="">Some text</span></span>
Fiddle: http://jsfiddle.net/U92ue/
Note: I have only tested in latest Chrome.
Visual formatting model - 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.
More specifically, a new block formatting context is established when the overflow property is changed. By default, an element's vertical-align property value is baseline. You can simply change this to something like top in order to fix this.
Updated Example
span.yavbc-color-tip span {
display: inline-block;
padding: 3px;
border-radius: 8px;
border: none;
background-color:#005e8e;
color:#7cd3ff;
overflow-x: hidden; /* This gives extra space under this span. Why? */
vertical-align:top;
}
Notice this doesn't happen when the element's display isn't changed to inline-block? It doesn't occur with inline elements - example demonstrating this.
Despite the above quote in the accepted answer, this behavior has nothing to do with Block Formatting Context and with "block" part of the inline-block value at all. It's all about the "inline" part of it.
All inline-* elements participate in the Inline Formatting Context. That means, they are placed inside the so called "line boxes" along with text and other inline-level elements. These elements and text are aligned with each other, so the height of each line box is calculated from the top of the highest element to the bottom of the lowest one.
By default, inline-level elements are aligned with the baseline of their fonts (see first line in the example below). Even if parent element has no actual text, the position of the baseline and the minimal height of the line box are calculated as if it had text (the spec calls this "imaginary" text the "strut" of the element). That's why the line box always has some space above the baseline (for the font ascenders and diacritics) and below it (for the font descenders) — see the second line of the example.
The tricky part for the inline-block elements is that overflow property changes what is considered the baseline for these elements (end of section 10.8.1 of the spec):
The baseline of an 'inline-block' is the baseline of its last line box in the normal flow, unless it has either no in-flow line boxes or if its 'overflow' property has a computed value other than 'visible', in which case the baseline is the bottom margin edge.
So, while technically the space below the baseline is always reserved, with default overflow: visible, the inline-block element is placed so that its text's baseline is aligned with the parent baseline, and its bottom part is moved below it. In many cases this makes this inline-block the lowest element in the line, so the space reserved for font descenders is not visible. However, changing the overflow value makes the whole element render above the baseline (like an <img> element), making all this space visible.
p {
margin: .5em;
font: 32px/1.5 serif;
color: #fff;
background: #888;
}
span {
display: inline-block;
font: 12px/1 sans-serif;
background: #fff;
color: #000;
padding: 2px;
border: 1px solid green;
}
.ovh {
overflow: hidden;
border-color: red;
}
<p>Text with image <img src="http://via.placeholder.com/30x15"> and <span>two</span> <span>inline-block</span>s</p>
<p><img src="http://via.placeholder.com/30x15"> <span>only</span> <span>inline-blocks</span></p>
<p><img src="http://via.placeholder.com/30x15"> <span>regular</span>, the <span class="ovh">overflowed</span> trick</p>
In general, inline formatting is tricky. You can find a good explanation of some of its gotchas and surprises in this article: http://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align.
I'd say that a good rule of thumb would be not using display: inline-* for its side effects, if you aren't really going to place the element inside the text. In the OPs example, the best solution would be to use display: block for the inner span, that doesn't involve any "magic" like line box calculation.

Baseline shifts for block-level element when no content inside

I'm working through a CSS problem at http://www.codecademy.com/courses/web-beginner-en-jNuXw/0/1?curriculum_id=50579fb998b470000202dc8b (actually, just helping a friend learn HTML/CSS) and came across a curious issue. If you erase the content in any of the <p> tags within a <div>, the div shifts upward. For example, delete the word 'Mom' without deleting the <p>. As best as I can figure out, this is because the element is set to vertical-align: baseline and for some reason the baseline is changing. I just can't figure out exactly why it's changing or what is causing it to change.
To be clear, I'm not asking for help to get the div's to align. That's simply a matter of setting their vertical-align to 'top'. I'm just trying to understand how the document flow is calculated. The specific question is: why does the empty div shift upwards?
DEMO: jsFiddle
UPDATE: Here is a new jsFiddle - http://jsfiddle.net/tonicboy/2DtTw/3/ which removes a lot of rules to boil the problem down to a simplified use case. From this, we can see that when a <p> tag has text in it, the baseline of the parent <div> is set at the baseline of the text. When you remove the text, the baseline of the parent <div> is set to the bottom of the <div>. Why is that?
HTML:
<div class="friend" id="best_friend"><p>Arthur</p></div>
<div class="friend"><p>Batmanuel</p></div>
<div class="friend"><p>Captain Liberty</p></div>
<div class="friend"><p>The City</p></div>
<div class="friend"><p>Justice</p></div>
<div class="family"><p></p></div>
<div class="family"><p>Dad</p></div>
<div class="family"><p>Bro</p></div>
<div class="family"><p>Sis</p></div>
<div class="family"><p>Rex</p></div>
<div class="enemy"><p>Baron Violent</p></div>
<div class="enemy"><p>The Breadmaster</p></div>
<div class="enemy"><p>The Deadly Nose</p></div>
<div class="enemy"><p>Dinosaur Neil</p></div>
<div class="enemy" id="archnemesis"><p>Chairface</p></div>
CSS:
div {
position: relative;
display: inline-block;
height: 100px;
width: 100px;
border-radius: 100%;
border: 2px solid black;
margin-left: 5px;
margin-top: 5px;
text-align: center;
}
div p {
position: relative;
margin-top: 40px;
font-size: 12px;
}
.friend {
border: 2px dashed green;
}
.family {
border: 2px dashed blue;
}
.enemy {
border: 2px dashed red;
}
#best_friend {
border: 4px solid #00C957;
}
#archnemesis {
border: 4px solid #cc0000;
}
I think I've mostly figured out the reason, after digging through W3C specs. Here are three key items from the spec which may explain this behavior:
"Line boxes that contain no text, no preserved white space, no inline elements with non-zero margins, padding, or borders, and no other in-flow content (such as images, inline blocks or inline tables), and do not end with a preserved newline must be treated as zero-height line boxes for the purposes of determining the positions of any elements inside of them, and must be treated as not existing for any other purpose."
When you delete the text, the <p> element is no longer in-flow.
"The baseline of an 'inline-block' is the baseline of its last line box in the normal flow, unless it has either no in-flow line boxes or if its 'overflow' property has a computed value other than 'visible', in which case the baseline is the bottom margin edge."
Because there are no in-flow elements within the parent div, the baseline becomes the bottom margin.
Because the div's are set to display: inline-block, their default vertical alignment is 'baseline'
Because the other div's have in-flow elements (the <p> tags), their baseline is set to the text baseline.
And that is why the empty box's bottom margin aligns with the baseline of the <p> tags in the other div's.
The baseline of the element is shifting because the text inside the <p> determs the baseline height:
In an inline formatting context, boxes are laid out horizontally, one
after the other, beginning at the top of a containing block.
Horizontal margins, borders, and padding are respected between these
boxes. The boxes may be aligned vertically in different ways: their
bottoms or tops may be aligned, or the baselines of text within them
may be aligned.
source: http://www.w3.org/TR/CSS2/visuren.html#block-formatting
The height of each inline-level box in the line box is calculated. For
replaced elements, inline-block elements, and inline-table elements,
this is the height of their margin box; for inline boxes, this is
their 'line-height'.
source: http://www.w3.org/TR/CSS2/visudet.html#line-height
CSS assumes that every font has font metrics that specify a
characteristic height above the baseline and a depth below it. In this
section we use A to mean that height (for a given font at a given
size) and D the depth. We also define AD = A + D, the distance from
the top to the bottom.
source: http://www.w3.org/TR/CSS2/visudet.html#inline-box-height
So with this block being a inline-block and baseline is calculted based on the line-height which is calcuted by different font types. Because this <p> has no font/text the baseline will not be positioned.
place all the line-height: 0; and you will see that the one with no text/font doesn't react like the other does:
jsFiddle
So why are the other two elemets shifting that have text in them?
Well it's because the text excist of two lines of text. The margin of the text is bigger and uses more space, thus the baseline is pushed further