This isn't the full picture, but margin-right
(or margin-left
) gets ignored in specific scenarios. This is intended block functionality in CSS. From the specs:
'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
If all of the above have a computed value other than 'auto', the values are said to be "over-constrained" and one of the used values will have to be different from its computed value. If the 'direction' property of the containing block has the value 'ltr', the specified value of 'margin-right' is ignored and the value is calculated so as to make the equality true. If the value of 'direction' is 'rtl', this happens to 'margin-left' instead.
In my opinion, this isn't very intuitive behavior and I prefer to avoid margins for block layout where possible (for more reasons than just this). So, you could add a wrapper <div>
around your <p>
tags and use padding
instead. This also enables you to use a border between items instead of adding <hr>
to your content. I think the proper semantics could go either way, depending on your real-world usage of this. To quote the MDN docs:
The HTML <hr>
element represents a thematic break between paragraph-level elements: for example, a change of scene in a story, or a shift of topic within a section.
Historically, this has been presented as a horizontal rule or line. While it may still be displayed as a horizontal rule in visual browsers, this element is now defined in semantic terms, rather than presentational terms, so if you wish to draw a horizontal line, you should do so using appropriate CSS.
But, while that fixes a few concerns, that doesn't fix everything. The calculated width of the <p>
elements is still less than min-content
. So, we can force this with min-width: min-content;
, or by using a different display
value (probably on expander
). This changes which algorithms are used for calculating widths under the hood.
Last note before the full example code: max-width
and max-height
are a decent trick but are really only useful for this kind of thing if you're trying to avoid modifying styles from JS (think :hover
or adding and removing an .open
class from JS, instead of setting width and height, directly)
setTimeout(() => {
const expander = document.querySelector('.js-expander');
expander.style.width = expander.scrollWidth + "px";
expander.style.height = expander.scrollHeight + "px";
}, 1000);
body {
background-color: black;
}
.expander {
display: grid;
width: 0px;
height: 0px;
overflow: hidden;
border: 1px solid red;
transition:
width 1s,
height 1s;
}
.expander-item {
padding: 40px;
font-family: monospace;
border-bottom: 1px solid blue;
}
.expander-item:last-child { border-bottom: 0px; }
.expander-item > * {
color: yellow;
background-color: green;
}
.expander-item > :first-child { margin-top: 0; }
.expander-item > :last-child { margin-bottom: 0; }
<div class="expander js-expander">
<div class="expander-item">
<p>Hello, world!</p>
</div>
<div class="expander-item">
<p>Goodbye, world!</p>
</div>
</div>
UPDATE: Based on comments, I thought I might include how <hr/>
might be added back in:
setTimeout(() => {
const expander = document.querySelector('.js-expander');
expander.style.width = expander.scrollWidth + "px";
expander.style.height = expander.scrollHeight + "px";
}, 1000);
body {
background-color: black;
}
.expander {
display: grid;
width: 0px;
height: 0px;
overflow: hidden;
border: 1px solid red;
transition:
width 1s,
height 1s;
}
.expander-group {
padding: 40px;
font-family: monospace;
}
.expander-group > * {
color: yellow;
background-color: green;
}
.expander-group > :first-child { margin-top: 0; }
.expander-group > :last-child { margin-bottom: 0; }
.expander > hr {
margin: 0;
padding: 0;
border-top: 1px solid blue;
border-right: none;
border-bottom: none;
border-left: none;
}
<div class="expander js-expander">
<div class="expander-group">
<p>Hello, world 1!</p>
<p>Hello, world 2!</p>
</div>
<hr>
<div class="expander-group">
<p>Goodbye, world!</p>
</div>
</div>