43

I'd like to show a horizontal series of a unknown number of playing cards. To do this, they will have to overlap if there are too many. I'm having trouble convincing a flex box to overlap the cards without shrinking them. The example below shrinks the cards. I tried flex-shrink: 0, but then max-width wasn't respected.

.cards {
  display: flex;
  max-width: 300px;
}

.card {
  width: 50px;
  height: 90px;
  border: 1px solid black;
  border-radius: 3px;
  background-color: rgba(255, 0, 0, 0.4);
}
<div class='cards'>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
</div>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
Michael Arrison
  • 1,464
  • 2
  • 13
  • 22
  • 2
    Why use flex if you want them to overlap, isn't the point of flex that it fills out the space? – Pete May 11 '17 at 15:01
  • Can you just use negative margin for the overlap? – StefanBob May 11 '17 at 15:03
  • A negative margin would work for a given number of cards. But it would have to change for each card and for each number of total cards. I'm looking for something more flexible. – Michael Arrison May 11 '17 at 15:05
  • Can you be more specific with what you're trying to achieve? What's "too many"? How do you want them to overlap exactly. What do you mean in reply to @StefanBob with *"it would have to change for each card and for each number of total cards. I'm looking for something more flexible."*? it would have to change how? And why? What's the logic there? What do you mean "more flexible"? – Michael Coker May 11 '17 at 15:09
  • @MichaelCoker I'm trying to get the cards to fan in a way similar to this picture: http://shpgames.com/zero-mod/fan.jpg (but without the curve). The cards should never resize - they should always remain 50 x 90px. So, if the max-width of the container is 300px wide, you can fit up to 6 cards with no overlap. Once a seventh is added, there will be a slight overlap. And if there were 50 cards, you would only see 6 pixels of each due to the overlap. So I'm hoping the flex container could take care of that calculation. – Michael Arrison May 11 '17 at 15:15

9 Answers9

53

Here's how I'd do this using flexbox.

.cards {
  display: flex;
  align-content: center;
  max-width: 35em;
}

.cardWrapper {
  overflow: hidden;
}

.cardWrapper:last-child, .cardWrapper:hover {
    overflow: visible;
}

.card {
    width: 10em;
    min-width: 10em;
    height: 6em;
    border-radius: 0.5em;
    border: solid #666 1px;
    background-color: #ccc;
    padding: 0.25em;
  
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
<div class="cards">
  <div class="cardWrapper">
    <div class="card">card 1 blah blah blah</div>
  </div>
  <div class="cardWrapper">
    <div class="card">card 2 blah blah blah</div>
  </div>
  <div class="cardWrapper">
    <div class="card">card 3 blah blah blah</div>
  </div>
  <div class="cardWrapper">
    <div class="card">card 4 blah blah blah</div>
  </div>
  <div class="cardWrapper">
    <div class="card">card 5 blah blah blah</div>
  </div>
</div>

Note that technically speaking, the cards aren't overlapping, they're just being clipped. But they look like they're overlapping. The trick is to wrap each card in another element with overflow: hidden.

The wrapping elements are shrunk to fit the available space, and as much of the cards as is possible is displayed in that space.

I include the :hover rule just to show how you could fully display a card from the middle of the "stack", but in a real project I'd probably do this for selected cards instead of hovered ones.

FTWinston
  • 1,019
  • 10
  • 16
  • 1
    You can eliminate `overflow: hidden` if you set the size on the wrapper and position the card to take it out of the flow, then the cards actually would overlap visually. – robartsd Oct 23 '22 at 21:29
  • 1
    `min-width` on the wrappers instead of the hidden overflow will also let it visually stack, just add a bit of margin/padding on the right of `.cards` if needed – Anthony Z Jan 30 '23 at 22:47
34

You can make elements in a flex layout overlap using transform: translateX(-10px), but that won't address the layout you're trying to get. I don't think you can do that in flexbox. But you could easily do this with JS.

var parentEl = document.getElementById("cards");

function sortCards() {
  var cards = document.getElementsByClassName("card"),
      cw = parentEl.clientWidth,
      sw = parentEl.scrollWidth,
      diff = sw - cw,
      offset = diff / (cards.length - 1);

  for (var i = 1; i < cards.length; i++) {
    cards[i].style.transform = "translateX(-" + offset * i + "px)";
  }
}

sortCards();

var b = document.getElementById("button");
b.addEventListener("click", function() {
  var div = document.createElement("div");
  div.classList.add("card");
  parentEl.appendChild(div);
  sortCards();
});
.cards {
  display: flex;
  max-width: 300px;
}

.card {
  height: 90px;
  border: 1px solid black;
  border-radius: 3px;
  background-color: rgba(255, 0, 0, 0.4);
  flex: 0 0 50px;
  background: red;
  transition: transform .25s;
}
<div><button id="button">addcards</button></div>
<div class='cards' id="cards">
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
</div>
Michael Coker
  • 52,626
  • 5
  • 64
  • 64
  • 1
    Although not flex, this is a great solution. It fulfills all the requirements of a variable number of cards in a given space. If anyone still has a flex solution, I'd love to hear it, but as of now, this is a good-enough accepted answer. – Michael Arrison May 11 '17 at 15:42
  • @MichaelArrison someone else answered saying you can make the elements overlap with `margin` - you can do that with `transform: translateX()` too, as I did here, but that wouldn't pull off your layout, so I didn't use it. You'll need some math (I think) to do that. But I updated my answer to use flex and transform since you had that in your original question. – Michael Coker May 11 '17 at 15:50
  • 1
    Why doesn't the `for` cycle start at `i=1` in your solution? – BoltKey Dec 22 '19 at 21:08
  • @BoltKey 'cause I mostly write CSS and that's just how I default to a JS loop :) Updated the answer with your suggestion, thanks! – Michael Coker Dec 27 '19 at 16:03
33

You can do it using only css by using grid instead of flex.

.hand{
  width: 50%;
  margin-left: auto;
  margin-right: auto;
  justify-content: center;
  display: grid;
  grid-template-columns: repeat(auto-fit,  minmax(10px, max-content)) ;
}
.card{
  width: 3em;
  height: 2.4em;
  padding: 3px;
  margin: 2px;
  background-color: lightgreen;
  border-style: solid;
  transform: rotate(3deg);  /*makes it easier to see the overlap*/
}
<div class="hand"> 
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
  <div class="card"> card </div>
</div>
Max Garber
  • 331
  • 3
  • 4
  • This solution is quite elegant; however, it overflows the `.hand` container. Is there a way to accomplish this such that the cards are evenly spaced and overlapping in a css grid, but without the overflow? – grahamcracker1234 Jan 11 '23 at 06:06
9

A flex container is designed to align items along the X and Y axes.

You're asking about alignment along the Z axis.

Flexbox is not designed for z-axis alignment (overlapping).

Any overlapping would have to come from negative margins, absolute positioning, CSS Grid Layout, JavaScript or something else. The z-index property may also need to play a role.

Here's a basic example using CSS Grid:

.cards {
  display: grid;
  grid-template-columns: repeat(30, 10px);
  grid-template-rows: 90px;
  max-width: 300px;
}

.card {
  grid-row-start: 1;
  background-color: lightgreen; 
  border: 1px solid black;
}

.card:nth-child(1) { grid-column: 1 / 6; }
.card:nth-child(2) { grid-column: 4 / 9; }
.card:nth-child(3) { grid-column: 7 / 12; }
.card:nth-child(4) { grid-column: 10 / 15; }
.card:nth-child(5) { grid-column: 13 / 18; }
.card:nth-child(6) { grid-column: 16 / 21; }
.card:nth-child(7) { grid-column: 19 / 24; }
.card:nth-child(8) { grid-column: 22 / 27; }
.card:nth-child(9) { grid-column: 25 / 30; }
<div class='cards'>
  <div class='card'>1</div>
  <div class='card'>2</div>
  <div class='card'>3</div>
  <div class='card'>4</div>
  <div class='card'>5</div>
  <div class='card'>6</div>
  <div class='card'>7</div>
  <div class='card'>8</div>
  <div class='card'>9</div>
</div>

Cards are made to overlap using line-based placement. In this case, the grid-column property is used to make column tracks overlap.

Still, some scripting would be necessary if the number of cards varies dynamically, as the overlap amount would need to vary in order for all cards to fit precisely in the fixed-width container.

Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • Although one card should be on top of the next, that is not the important issue here. The important issue is having them overlap on the x axis. Flex containers have many options for alignment along the x axis. But I'm just not sure if it has any for overlapping. – Michael Arrison May 11 '17 at 15:30
  • I think with use of jquery you can put a formula on the margin-left from the 2nd card onwards that will dynamically calculate it and overlap the cards. If you want i can make you an example. – Nasir T May 11 '17 at 15:34
  • 1
    @NasirT brings up a good point, although it can be simplified to pure css. A rule like .card:nth-child(n+2) { margin-left: -30px; } would be quite effective if the number of cards is known ahead of time. – Michael Arrison May 11 '17 at 15:43
9

Late to the party, but this was my fix for a similar situation:

There's a suggestion mentioning negative margins and it's true that when no other adaptions are done, cards will always overlap with: margin-left: -30px; (Overlap width chosen somewhat arbitrary. You could choose the card's width as a maximum.)

What I've changed here is adding justify-content: space-evenly; to the mix. This spreads out the cards over the available space in the flex container and only lets the cards overlap when it gets too crowded. This could be a solution if you're fine with the cards spreading out like that when not overlapping.

NB. Since the negative margin pulls the cards to the left, I also hacked in a padding-left with the same width as the negative margin into the flex container.

NB2. I changed the opacity of the original example because otherwise the cards become see-through.

    .cards {
      display: flex;
      max-width: 300px;
      padding-left: 30px;
      justify-content: space-evenly;
    }

    .card {
      width: 50px;
      height: 90px;
      border: 1px solid black;
      border-radius: 8px;
      background-color: rgb(255, 100, 100);
      margin-left: -30px;
    }
    <div class='cards'>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
      <div class='card'></div>
    </div>
  • Though simple, it reduces the width of the .card elems when the width of the .cards container is reduced. (tested in FF and Chrome) – erenon Apr 19 '22 at 07:24
2

I came up with a generic, CSS-based solution. However, with a few caveats:

  1. The last card overflows the .cards container.
  2. A child element of .card is needed to produce the overlapping effect.

.cards {
  display: flex;
  max-width: 300px;
}

.card {
  position: relative;
  flex-basis: 50px;
  height: 90px;
}

.card::before {
  content: '';
  display: block;
  position: absolute;
  width: 50px;
  height: 100%;
  border: 1px solid black;
  border-radius: 3px;
  background-color: rgba(255, 0, 0, 0.4);
  box-sizing: border-box;
}
<div class='cards'>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
</div>
Nate Whittaker
  • 1,866
  • 15
  • 14
0

You can do it using margin property and apply to the items you desire by manual or JS.

.cards {
  display: flex;
  max-width: 300px;
}

.overl {
margin-left:-10px;
}

.card {
  width: 50px;
  height: 90px;
  border: 1px solid black;
  border-radius: 3px;
  background-color: rgba(255, 0, 0, 0.4);
}
<div class='cards'>
  <div class='card'></div>
  <div class='card overl'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
  <div class='card'></div>
</div>
Sam
  • 1,459
  • 1
  • 18
  • 32
0

div.card-area
{
    height: 16vh; /* whatever */
    display: flex;
}
div.card-area > div
{
    flex: 1 0 0;
    overflow-x: hidden;
}
div.card-area > div:last-of-type
{
    flex: 0 0 auto;
}
div.card-area img
{
    height: 100%;
}
<div class="card-area N">
    <div><img src="img/cards/AH.png"></div>
    <div><img src="img/cards/KH.png"></div>
    <div><img src="img/cards/QH.png"></div>
    <div><img src="img/cards/JH.png"></div>
    <div><img src="img/cards/10H.png"></div>
    <div><img src="img/cards/9H.png"></div>
    <div><img src="img/cards/8H.png"></div>
    <div><img src="img/cards/7H.png"></div>
    <div><img src="img/cards/6H.png"></div>
    <div><img src="img/cards/5H.png"></div>
    <div><img src="img/cards/4H.png"></div>
    <div><img src="img/cards/3H.png"></div>
    <div><img src="img/cards/2H.png"></div>
</div>
Cuse70
  • 271
  • 3
  • 5
0

This overlaps a right child over a left child, excluding the first.

.overlapped {
  > * + * {
    postion: absolute;
    margin-left: -7px;
  }
}

Your container should also have flex and flex-direction row.

zurcacielos
  • 1,495
  • 2
  • 9
  • 7