I'm building a carousel inside a flex container which has flex-direction: column
. I thought this would be trivial, and it probably is. I've built carousels by hand before but not in this exact circumstance.
My problem is that the content is a carousel, a scrolling list of items that is in a parent with overflow: hidden
, but its size is not being constrained by its flex item parent so it is occupying the full width of all its child items. Normally a parent like that has its size constrained in some way, causing it to be smaller than its content, a very common design pattern.
Consider this snippet, with two columns in the outer layout, a Left panel (yellow) wants to be 30% width and Right panel (green) wants to be 60% width. Inside the left panel, I want a horizontal carousel, which contains many items, only a few of which will be visible because there isn't space to display them all (I would add proper scrolling mechanics later).
.container {
display: flex;
}
.left-panel {
flex: 0 0 33%;
display: flex;
flex-direction: column;
background-color: yellow;
}
.right-panel {
flex: 1 0 auto;
flex-basis: 66%;
background-color: green;
}
.items {
flex: 1;
overflow-x: hidden;
}
.items-wrapper {
display: flex;
flex-wrap: nowrap;
gap: 1rem;
}
.item {
width: 8rem;
height: 8rem;
background-color: red;
}
<div class="container">
<div class="left-panel">
<div class="title">Left Panel, this panel is supposed to be only 30% the width of the parent, but is being pushed wider by the children below.</div>
<p>I want the .items div to be the same width as the 33% wide .left-panel, and I'll deal with the scroll/overflow once that is working.</p>
<div class="items">
<div class="items-wrapper">
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
</div>
</div>
</div>
<div class="right-panel">
Right Panel Here
</div>
</div>
See equivalent codepen https://codepen.io/neekfenwick/pen/QWRQrVL
I've told the left panel to be flex: 0 0 33%
which I thought would force it to be 33% the width of its parent (the document), since flex-grow
is 0 and flex-basis
is 33%, and any content inside would be sized accordingly.
I understand that the content is physically larger than this requested 33%. How can I make the .items
element respect the size requested by its parent?
Perhaps what's tripping me up is that the .left-panel
has flex-direction: column
which means the main axis is vertical, so the effect of flex-basis
, flex-grow
etc becomes confusing, but the flex axis of the outer .container
is the default row
so I can't narrow down what's going wrong.
I've found advice at Overflow and contain flex-child of a flex-child advising flex-basis: 0
which doesn't help.
This is not helped by having a bit of a stinker of a cold right now. Help would be hugely appreciated :)
For clarification, here is an example that achieves what I'm trying to do but without using Flexbox. Here, the left and right panels are divs with display: inline-block
and fixed widths. See how the .items
carousel is constrained within the width of .left-panel
, so you cannot see the items that overflow its width.
https://codepen.io/neekfenwick/pen/JjqpZQz
.container {
/* Container has no flex layout, children have fixed width */
}
.left-panel {
display: inline-block;
vertical-align: top;
width: 33%;
ffflex-direction: column;
background-color: yellow;
}
.right-panel {
display: inline-block;
vertical-align: top;
width: 66%;
background-color: green;
}
.items {
flex: 1;
overflow-x: hidden;
}
.items-wrapper {
display: flex;
flex-wrap: nowrap;
gap: 1rem;
}
.item {
flex: 0 0 8rem;
width: 8rem;
height: 8rem;
background-color: red;
}
<div class="container">
<div class="left-panel">
<div class="title">An example based on <a href="https://codepen.io/neekfenwick/pen/QWRQrVL">This Codepen https://codepen.io/neekfenwick/pen/QWRQrVL</a> but without using Flexbox, Left Panel, this panel is set to 33% the width of the parent.</div>
<p>The .items div is therefore the same width as the 33% wide .left-panel.</p>
<div class="items">
<div class="items-wrapper">
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
</div>
</div>
</div>
<div class="right-panel">
Right Panel Here
</div>
</div>
For the record, the error seems to have been the use of width
on the .item
CSS rule. A better solution is to use flex-basis
.
Here is an example solution, showing two layouts, one with many items in the list and one with just 3. I have sized the item scroller using CSS variables, and calc()
to calculate the width required for 3 to be visible, as that is the kind of layout I was aiming for and is more useful in a real-world application.
:root {
--item-width: 8rem;
--item-gap: 1rem;
}
.container {
display: flex;
margin-bottom: 1rem;
}
.left-panel {
flex: 0 0 calc(var(--item-width) * 3 + (var(--item-gap)*2));
display: flex;
flex-direction: column;
background-color: yellow;
}
.right-panel {
flex: 1 0 66%;
background-color: green;
}
.items {
overflow-x: scroll;
}
.items-wrapper {
display: flex;
flex-wrap: nowrap;
gap: var(--item-gap);
}
.item {
flex: 0 0 var(--item-width);
height: var(--item-width);
background-color: red;
}
<div class="container">
<div class="left-panel">
<div class="title">This panel has many items.</div>
<div class="items">
<div class="items-wrapper">
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
</div>
</div>
</div>
<div class="right-panel">
Right Panel Here
</div>
</div>
<div class="container">
<div class="left-panel">
<div class="title">This panel has 3 items.</div>
<div class="items">
<div class="items-wrapper">
<div class="item">Item</div>
<div class="item">Item</div>
<div class="item">Item</div>
</div>
</div>
</div>
<div class="right-panel">
Right Panel Here
</div>
</div>
See codepen at https://codepen.io/neekfenwick/pen/OJYKEKo
This solution does still run into problems when the items are smaller, for example when --item-width: 5rem
. I don't have a solution for that yet.
In my real world application I have switched to using CSS grid
to control the container, and only using flexbox for the items scroller. This has also allowed me to overlap grid cells with greater control than I found with flexbox.