Matt Wilson
Matt Wilson

Reputation: 8319

CSS/Flexbox: Only show as many items as will fit in container

My goal is to build a breadcrumb style component which:

  1. shows a horizontal list of items
  2. has a maximum width for each item
  3. omits items from the start of the list if there is insufficient width to show them
  4. prioritises the last item(s) in the list
  5. all layout achieved with CSS only (no JS resize watchers etc)

Flexbox seems like a good place to start, but when it comes to requirements 3&4 I am not sure what the best approach is.

Here's my thought process so far:

a) To start, I could create a flexbox with items like so, each 100px wide:

https://codepen.io/mattwilson1024/pen/LLvMzB

b) Now let's imagine we only have 300px for the container. I could have all items shrinkable (flex: 0 1 100px). This would be fine for a small number of items, but if I have a lot of items they'd all get too small.

https://codepen.io/mattwilson1024/pen/pwBqdN

What I really want is for it to only show as many items as will fit into the container. In this case that would be items 7-9.

c) Enabling flex-wrap: wrap means each row only shows as many items as can fit. This is closer to what I'm after, except I'd only want the bottom row.

https://codepen.io/mattwilson1024/pen/YQMdEB

d) Media queries might help. For example I could use a media query to hide all but the last 3 items on a small screen, and all but the last 6 items on a larger screen etc. However, I would then need to know exactly where the component is going to be used and tweak the numbers accordingly. I'd rather find a solution that is based on the size of the container rather than the size of the viewport.

Upvotes: 4

Views: 3701

Answers (1)

Asons
Asons

Reputation: 87251

You need to make 3 adjustments (in your last sample) to fulfill your requirements:

  • On the flex container items

    • remove flex-wrap: wrap
    • add justify-content: flex-end
  • On the flex items item last child

    • add margin-right: auto

It works like this, where the flex-end will right align the items making the last items the visible ones.

The margin-right: auto will make the items left aligned when there are less items to fill the container.

Update codepen, where I also gave flex items a max-width and changed flex to flex: 0 0 auto, so they adjust to content and get ellipsis when they exceed the max width.


In the below Stack snippet I changed the flex-shrink from 1 to 0 in flex: 0 0 100px so it clearly shows how it behaves:

.items {
  width: 300px;
  display: flex;
  justify-content: flex-end;
}

.item {
  flex: 0 0 100px;
  
  /* Truncate text with ellipsis */
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.item:last-child {
  margin-right: auto;
}


/* Styling - not relevant to the question, just to make it look good */

.item {
  border-right: solid 1px black;
  box-sizing: border-box;
}

.item:nth-child(1) {
  background-color: #e3f2fd;
  color: black;
}

.item:nth-child(2) {
  background-color: #bbdefb;
  color: black;
}

.item:nth-child(3) {
  background-color: #90caf9;
  color: black;
}

.item:nth-child(4) {
  background-color: #64b5f6;
  color: black;
}

.item:nth-child(5) {
  background-color: #42a5f5;
  color: white;
}

.item:nth-child(6) {
  background-color: #2196f3;
  color: white;
}

.item:nth-child(7) {
  background-color: #1e88e5;
  color: white;
}

.item:nth-child(8) {
  background-color: #1976d2;
  color: white;
}

.item:nth-child(9) {
  background-color: #1565c0;
  color: white;
}

body {
  font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
  font-size: 14px;
}

body > div:nth-child(2) {
  font-family: sans-serif;
  font-size: 12px;
}
<div class="items">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
  <div class="item">Item 3</div>
  <div class="item">Item 4</div>
  <div class="item">Item 5</div>
  <div class="item">Item 6</div>
  <div class="item">Item 7</div>
  <div class="item">Item 8</div>
  <div class="item">Item 9</div>
</div>

<div><br>If less to fit the container, they align left<br><br></div>

<div class="items">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
</div>


Updated

Based on a comment, where an item can't be chopped of, one can use row-reverse, flex-end and order to make them start at the bottom right corner.

With this they can wrap upwards and by adding overflow: hidden only the one's that fit within the containers width will be visible

.items {
  width: 300px;
  height: 15px;
  display: flex;
  flex-direction: row-reverse;
  flex-wrap: wrap;
  justify-content: flex-end;
  overflow: hidden;
}
.item {
  flex: 0 0 auto;
  max-width: 100px;  
  padding: 0 10px;

  /* Truncate text with ellipsis */
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}


/* Styling - not relevant to the question, just to make it look good */
.item { border-right: solid 1px black; box-sizing: border-box; }
.item:nth-child(1) { background-color: #e3f2fd; color: black; order: 10; }
.item:nth-child(2) { background-color: #bbdefb; color: black; order: 9; }
.item:nth-child(3) { background-color: #90caf9; color: black; order: 8; }
.item:nth-child(4) { background-color: #64b5f6; color: black; order: 7; }
.item:nth-child(5) { background-color: #42a5f5; color: white; order: 6; }
.item:nth-child(6) { background-color: #2196f3; color: white; order: 5; }
.item:nth-child(7) { background-color: #1e88e5; color: white; order: 4; }
.item:nth-child(8) { background-color: #1976d2; color: white; order: 3; }
.item:nth-child(9) { background-color: #1565c0; color: white; order: 2; }
body {
  font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
  font-size: 14px;
}
<div class="items">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
  <div class="item">Item 3</div>
  <div class="item">Item 4</div>
  <div class="item">Item 5</div>
  <div class="item">Item 6</div>
  <div class="item">Item 7</div>
  <div class="item">Item 8</div>
  <div class="item">Item 9</div>
</div>

<div><br>If less to fit the container, they align left<br><br></div>

<div class="items">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
</div>

Upvotes: 6

Related Questions