Prevent a child element from overflowing its parent in flexbox [duplicate] - html

This question already has answers here:
Why don't flex items shrink past content size?
(5 answers)
Closed 5 years ago.
I'm working on a web app that shows a large grid of cards, the height of which is inherently variable.
In the interests of aesthetics, we were using jQuery's .matchHeight() to equalise the height of cards within each row.
The performance of that didn't scale well, so today I've been migrating to a flex-box based solution which is so much faster.
However, I've lost a behaviour - the content of the card header should be truncated with an ellipsis if it won't fit.
Goals:
3 columns
Column widths vary to fill parent
Constant spacing between columns
Heights equalised within a row
How do I arrange for the container size to be respected and the text-overflow: ellipsis; and white-space: nowrap; to be honoured?
(No jQuery tag as we're moving away from that)
My solution in it's current form, which achieves all of my goals apart from the truncation:
https://codepen.io/anon/pen/QvqZYY
#container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start; /* Bias cards to stack from left edge */
align-items: stretch; /* Within a row, all cards the same height */
border: thin solid gray;
}
.card-wrapper {
width: 33.33%;
display: flex;
background: #e0e0ff;
}
.card {
flex-grow: 1;
margin: 7px;
display: flex;
flex-direction: column;
border: thin solid gray;
background: #e0ffff;
}
.card div {
border: thin solid gray;
}
.card div:nth-child(1) {
white-space: nowrap;
text-overflow: ellipsis;
}
.card div:nth-child(2) {
flex-grow: 2;
}
<div id="container">
<div class="card-wrapper">
<div class="card">
<div>Title</div>
<div>Multiline<br/>Body</div>
<div>Footer</div>
</div>
</div>
<div class="card-wrapper"><div class="card"><div>Really long rambling title that pushes beyond the bounds of the container, unless your screen is really, really wide</div><div>Body</div><div>Footer</div></div></div>
<div class="card-wrapper"><div class="card"><div>Title</div><div>Body</div><div>Footer</div></div></div>
<div class="card-wrapper"><div class="card"><div>Title</div><div>Body</div><div>Footer</div></div></div>
<div class="card-wrapper"><div class="card"><div>Title</div><div>Body</div><div>Footer</div></div></div>
</div>

An initial setting on flex items is min-width: auto. This means that a flex item, by default, cannot be smaller than the size of its content.
Therefore, text-overflow: ellipsis cannot work because a flex item will simply expand, rather than permit an overflow. (Scroll bars will not render either, for the same reason.)
To override this behavior, use min-width: 0 or overflow: hidden. More details.
#container {
display: flex;
flex-wrap: wrap;
border: thin solid gray;
}
.card-wrapper {
width: 33.33%;
display: flex;
background: #e0e0ff;
}
.card {
flex-grow: 1;
margin: 7px;
display: flex;
flex-direction: column;
border: thin solid gray;
background: #e0ffff;
overflow: hidden; /* NEW */
}
.card div {
border: thin solid gray;
}
.card div:nth-child(1) {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden; /* NEW */
}
.card div:nth-child(2) {
flex-grow: 2;
}
<div id="container">
<div class="card-wrapper">
<div class="card">
<div>Title</div>
<div>Multiline<br/>Body</div>
<div>Footer</div>
</div>
</div>
<div class="card-wrapper">
<div class="card">
<div>Really long rambling title that pushes beyond the bounds of the container, unless your screen is really, really wide</div>
<div>Body</div>
<div>Footer</div>
</div>
</div>
<div class="card-wrapper">
<div class="card">
<div>Title</div>
<div>Body</div>
<div>Footer</div>
</div>
</div>
<div class="card-wrapper">
<div class="card">
<div>Title</div>
<div>Body</div>
<div>Footer</div>
</div>
</div>
<div class="card-wrapper">
<div class="card">
<div>Title</div>
<div>Body</div>
<div>Footer</div>
</div>
</div>
</div>

Related

Flexbox children shrink up to a certain point (but don't expand if they don't need to)

I'm a having a bit of an issue here. I have a flexbox container with children of different sizes. Based on quantity and their content children might overflow the parent.
What I want is the children to shrink so they try to fit in the parent container. I did that by adding shrink and overflow properties to the children. So far so good.
.container > div {
background-color: orange;
padding: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 1;
}
I end up with something like this:
Now I want them to shrink but up to a certain point (lets say 80px). I don't care if they end up overflowing the container but I don't want to render any smaller than 80px.
Of course, I added min-width: 80px to the children... but here is my problem. I want the children to shrink up to 80px but I don't want any of those that were smaller than 80px already (like Child1, Child4 and Child5) I don't want them to be enlarged by the min-width property (or, I want them to shrink further up to min-content)
In other words. I don't want this:
I would love to have something like this:
I tried doing something like min-width: min(min-content, 80px) but of course, didn't work.
Here is an small codepen with the issue: https://codepen.io/claudiofpen/pen/QWELVJO
.container {
width: 300px;
border: 1px solid black;
display: flex;
flex-direction: row;
padding: 5px;
}
.container > div {
background-color: orange;
padding: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 1;
min-width: min-content;
}
.container > div:not(:last-child) {
margin-right: 5px;
}
/* I don't want the following css classes, I cannot
tell in before hand which children are going to have
a larger content */
.container > div:nth-child(2),
.container > div:nth-child(3) {
min-width: 80px;
}
<div class="container">
<div>Child 1</div>
<div>Longer Child 2</div>
<div>Longer Child 3</div>
<div>Child 4</div>
<div>Child 5</div>
</div>
Temani Afif's solution solves the problem of ensuring that a text element will not shrink below the specified width unless its intrinsic width is already below that width (in which case it uses its intrinsic width as the rendered width). But it does not work unless the sum of the specified widths of all the child elements exceeds the container's width.
So I tried giving each outer elements a flex-grow parameter, so that they would grow above their specified width, if the container had room. But I also give the outer elements a maximum width set to their intrinsic maximum content width, so they would never grow beyond the actual size of the text. Thus I added the following styles to the wrapping div.
flex: 1 1 auto;
max-width: max-content;
With that tweak I believe it solves the entire problem. The elements expand fully if there is room in the container. As we add more elements the longer elements start to shrink. But they never shrink below their specified width, so the container overflows once all inserted elements have shrunk down to that width. But elements that started with a shorter width never flex at all.
I have added an example below.
.container {
width: 340px;
border: 1px solid black;
display: flex;
flex-direction: row;
padding: 5px;
}
.container>div {
background-color: orange;
padding: 5px;
flex: 1 1 auto;
width: 80px;
max-width: max-content;
}
.container>div>div {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 100%;
}
.container>div:not(:last-child) {
margin-right: 5px;
}
<h5>When the items fit they expand to their intrinsic length</h5>
<div class="container">
<div>
<div>Medium length</div>
</div>
<div>
<div>Tiny</div>
</div>
<div>
<div>Longer text element</div>
</div>
</div>
<h5>When the container limit is reached the longer elements start shrinking</h5>
<div class="container">
<div>
<div>Medium length</div>
</div>
<div>
<div>Tiny</div>
</div>
<div>
<div>Longer text element</div>
</div>
<div>
<div>Filler</div>
</div>
</div>
<h5>Adding more elements...</h5>
<div class="container">
<div>
<div>Medium length</div>
</div>
<div>
<div>Tiny</div>
</div>
<div>
<div>Longer text element</div>
</div>
<div>
<div>Filler</div>
</div>
<div>
<div>Filler</div>
</div>
</div>
<h5>When there is no room it overflows<br> The tiny element stays at its intrinsic width, but the bigger elements stop shrinking at the specified width</h5>
<div class="container">
<div>
<div>Medium length</div>
</div>
<div>
<div>Tiny</div>
</div>
<div>
<div>Longer text element</div>
</div>
<div>
<div>Filler</div>
</div>
<div>
<div>Filler</div>
</div>
<div>
<div>Filler</div>
</div>
</div>
With an extra wrapper you can do it:
.container {
width: 300px;
border: 1px solid black;
display: flex;
flex-direction: row;
padding: 5px;
}
.container > div {
background-color: orange;
padding: 5px;
flex-shrink: 1;
width: 80px;
}
.container > div > div {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 100%;
}
.container > div:not(:last-child) {
margin-right: 5px;
}
<div class="container">
<div><div>Ch 1</div></div>
<div><div>Longer Child 2</div></div>
<div><div>Longer Child 3</div></div>
<div><div>Child 4</div></div>
<div><div>Child 5</div></div>
</div>
Grid Solution
.container {
width: 300px;
border: 1px solid black;
display: grid;
grid-template-columns: max-content 80px 80px repeat(2,max-content);
padding: 5px;
}
You can use javascript to make the grid-template-column dynamic.
Here is the jquery (javascript) solution using flex
.container {
width: max-content;
border: 1px solid black;
display: flex;
padding: 5px;
}
$(".container > div").each(function(){
($(this).width() < 50) ?
$(this).css('width','max-content') :
$(this).css('flex','0 0 80px');
})
This is more dynamic than the grid solution. The only thing is that you will need to have a desired number in $(this).width() < 50 instead of fifty based on your content.

How to make dynamic width in flex box row with two columns? [duplicate]

This question already has answers here:
Fill the remaining height or width in a flex container
(2 answers)
Closed 2 years ago.
I would like the second column out of the two, to shrink when needed so that the first column can take more space. How would I go about to do this with flex box? I would like to achieve dynamic width behavior and not enter any specific pixel values.
.container {
border: 1px solid black;
width: 300px;
}
.container .column {
border: 1px solid magenta;
}
/* Second column */
.container .column+column {}
/* Flex grid */
.flex {
display: flex;
}
.row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.column {
display: flex;
flex-direction: column;
flex-basis: 100%;
flex: 1;
}
<div class="flex container">
<div class="row">
<div class="column">
Some very long descriptional text and some extra words here and there so to say.
</div>
<div class="column">
123£
</div>
</div>
</div>
A fiddle to fiddle around width:
https://jsfiddle.net/TheJesper/5wexr384/7/
This happens automatically with flex, so here is the minimum css you need to make it work:
.container {
border: 1px solid black;
width: 300px;
}
.flex {
display: flex;
width: 100%;
}
.column {
border: 1px solid magenta;
}
Then change your HTML:
<div class="container">
<div class="flex">
<div class="column">
// ...
</div>
<div class="column">
// ...
</div>
</div>
</div>
I always find this guide very useful:
https://css-tricks.com/snippets/css/a-guide-to-flexbox/

How to use `flex: grow` on floating content?

I have a header with 2 rows of 2 Foundation columns of content, as below:
<div class="wrapper">
<div class="header row">
<div class="large-6 columns">
HEADER
</div>
<div class="large-6 columns">
menu
</div>
</div>
<div class="row">
<div class="large-5 none show-for-medium columns info">
Some information to the left
</div>
<div class="large-7 columns">
<div class="image-container">
<div class="image">
image to the right
</div>
</div>
</div>
</div>
</div>
The .header height is dynamic and not set. I want the .image element to take up 100% of the remaining vertical space.
eg:
To that affect I have tried using flex and flex-grow, eg:
.wrapper {
display: flex;
flex-direction: column;
min-height: 100vh;
width: 100%;
}
.image-container {
flex-grow: 1;
}
but had no luck, see fiddle: https://jsfiddle.net/9kkb2bxu/46/
Would anyone know how I could negate the dynamic height of the header from the 100vh of the image container?
.wrapper {
display: flex;
flex-direction: column;
min-height: 100vh;
width: 100%;
border: 1px solid black;
background-color: #ccc;
}
.row {
width: 100%;
}
.header {
background-color: green;
}
.info {
background-color: yellow;
border: 1px solid #ccc;
}
.image-container {
border: 1px solid black;
display: flex;
}
.image {
background-color: red;
flex-grow: 1;
width: 100%;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.1/css/foundation.min.css" rel="stylesheet"/>
<div class="wrapper">
<div class="header row">
<div class="large-6 columns">
<h1>
HEADER
</h1>
</div>
<div class="large-6 columns">
<h1>
menu
</h1>
</div>
</div>
<div class="row">
<div class="large-5 none show-for-medium columns info">
Some information to the left
</div>
<div class="large-7 columns">
<div class="image-container">
<div class="image">
image to the right
</div>
</div>
</div>
</div>
</div>
Set the second row to take up the rest of the remaining height with flex: 1 and make sure you nest that flex with display: flex:
.row.target-row {
flex: 1;
display: flex;
}
Set the .image-container to 100% height of its column parent.
.image-container {
height: 100%;
}
By default both columns will expand. Stop the left column from expanding with:
.large-5 {
align-self: flex-start;
}
(flex-start reference: https://stackoverflow.com/a/40156422/2930477)
Complete Example
.wrapper {
display: flex;
flex-direction: column;
min-height: 100vh;
width: 100%;
border: 1px solid black;
background-color: #ccc;
}
.row {
width: 100%;
}
.header {
background-color: green;
}
.info {
background-color: yellow;
border: 1px solid #ccc;
}
.image-container {
height: 100%;
background-color: red;
}
.large-5 {
align-self: flex-start;
}
.row.target-row {
flex: 1;
display: flex;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.1/css/foundation.min.css" rel="stylesheet" />
<div class="wrapper">
<div class="header row">
<div class="large-6 columns">
<h1>
HEADER
</h1>
</div>
<div class="large-6 columns">
<h1>
menu
</h1>
</div>
</div>
<div class="row target-row">
<div class="large-5 none show-for-medium columns info">
Some information to the left
</div>
<div class="large-7 columns">
<div class="image-container">
<div class="image">
image to the right
</div>
</div>
</div>
</div>
</div>
flex-grow only applies to flex children.
.image-container isn't a direct child of a display: flex element, so that property has no effect.
Plus, it affects the flex axis, which is not what you want.
Instead, you need to put those two elements in their own flex row, and use align-items (on the parent) and align-self (on either child) so that the first one aligns (on the cross axis) to flex-start (stick to top) and the second one to stretch.
You'll also want that flex row (parent) to have flex-grow: 1 so that it stretches along the vertical flex axis of its parent (.wrapper) to fill the rest of the page (otherwise, the grandchild will have nothing to stretch to).
For more information, read a good flex tutorial.
div.wrapper > div:not(.header).row {
flex: 1; /* 1 */
display: flex; /* 1 */
}
div.large-7.columns {
display: flex; /* 2 */
}
div.image-container { /* 3 */
flex: 1;
}
div.large-5.show-for-medium { /* 4 */
align-self: flex-start;
}
jsFiddle
Notes:
flex container and items consume all remaining height of respective parents
give children full height (via align-items: stretch initial setting)
flex item consumes all available width
yellow box does not need to expand to full height; now set to content height

Flexbox expands outside container instead of wrapping

I have 3 flexboxes each inside the other. The outermost flexbox(.flex-1) holds the title and the next flexbox(.flex-2), which holds the button and the innermost flexbox(.flex-3), which holds a few .items. Ideally, the items will flow down and then when there is no space left (based on the size of the outermost box) should wrap to the right. This works if a height is explicitly set on .flex-3, but I am not able to do that because the title varies in height.
Instead, it overflows and items are shown below the border of .flex-1, as well as flex-2 expanding to fit them and overflowing.
.title{width: 100%}
.flex-1{ /* Title and flex-2 */
display: flex;
flex-direction: column;
width: 700px;
height: 700px;
border: 4px black solid;
}
.flex-2{ /* flex-3 and button */
display: flex;
flex-direction: row;
border: 3px #333 solid;
align-items: flex-start;
}
.flex-3{ /* contains items */
display: flex;
flex-direction: column;
flex-wrap: wrap;
border: 2px #888 solid;
flex-grow: 1;
}
.item{
display: block;
width: 200px;
height: 150px;
border:1px #DDD solid;
}
<div class='flex-1'>
<h3 class='title'>THIS IS A TITLE</h3>
<div class='flex-2'>
<div class='flex-3'>
<div class='item'></div>
<div class='item'></div>
<div class='item'></div>
<div class='item'></div>
<div class='item'></div>
<div class='item'></div>
</div>
<button>BTN</button>
</div>
</div>
The problem seems to be caused by the CSS property align-items on .flex-2.
Try removing this.

Set same height in flex-box for dynamic content

In this case
need to keep same height for all three columns (this is working already according to current code, ".col" get same height using flex)
need to set max height of yellow div (dynamic content) for all yellow
div
need to be implement using CSS only (not JavaScript)
use flex (not tables)
here is test code jsfiddle
Current Design
Need Design
I have dynamic content inside the "bottom-content" area. what I need to do, set all "bottom-content" area same height (set max height for 3 yellow areas), and all "col" should be same height too.
HTML code
<div class="container">
<div class="col">
<div class="top-content">top content here</div>
<div class="bottom-content">bottom content here</div>
</div>
<div class="col">
<div class="top-content">top content here</div>
<div class="bottom-content">bottom content here</div>
</div>
<div class="col">
<div class="top-content">top content here</div>
<div class="bottom-content">bottom content here</div>
</div>
</div>
CSS code
.container {
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
flex-flow: row wrap;
overflow: hidden;
}
.container .col {
flex: 1;
padding: 20px;
}
.container .col:nth-child(1) {
background: #eee;
}
.container .col:nth-child(2) {
background: #ccc;
}
.container .col:nth-child(3) {
background: #eee;
}
.top-content {
background: #888;
border-bottom: 1px solid #333;
padding: 5px;
}
.bottom-content {
background: yellow;
padding: 5px;
}