sonofevil
sonofevil

Reputation: 53

CSS: Wrap several elements at once to next line once width is exceeded

This is a pretty specific issue and I wouldn't be surprised if it's impossible to solve. Let's say I have the following html, which I cannot change at all:

<box>
 <tab class="A" id="A1">...</tab>
 <tab class="A" id="A2">...</tab>
 <tab class="A" id="A3">...</tab>
 ...
 <tab class="B" id="B1">...</tab>
 <tab class="B" id="B2">...</tab>
 <tab class="B" id="B3">...</tab>
 ...
</box>

The amount of A and B tabs varies. Using only CSS, I would like to achieve the following behavior:

1: The tabs have to be arranged in rows, and wrap to the next row once they exceed the width of the box (which is only constrained by screen width). (This is the easy part. I already know how to do it with flex, but I'm not committed to one specific method.)

|A1 A2 A3 B1|
|B2 B3      |

2: But what I would like to see is for the B elements all wrap to the next line at once, as if they were one element. I also found a way to do this by inserting an invisible line break element with ::before and assigning it an "order" value that puts it between A3 and B1.

|A1 A2 A3   |
|B1 B2 B3   |

3: However, this means there are always a minimum of 2 lines, for each type of element. The behavior I'm trying to achieve is that if all elements do fit inside the width of the box, they should all remain on the same line, and only wrap in the manner described in 2 once the width is exceeded. Like this:

|A1 A2 B1 B2|

New tab is added:

|A1 A2      |
|B1 B2 B3   |

Upvotes: 0

Views: 809

Answers (2)

sonofevil
sonofevil

Reputation: 53

I found a solution. Assuming that the width of the box fits exactly 4 tabs (This should be mathematically generalizable to other cases of course. My actual case is much more complicated, with the box width fitting roughly 10 B-tabs, and B-tabs being 4 times as wide as A-tabs. I'm simplifying here for comprehensibility.):

The box is styled as a flex-box:

box {
    display: flex;
    flex-wrap: wrap;
    overflow-y: auto;
}

This inserts the line break with order value 1 (by default this is sorted after elements without the order property):

box::after {
  display: flow-root list-item;
  order: 1;
  content: "";
  flex-basis: -moz-available;
  height: 0px;
}

This tells every B element immediately after an A element to be sorted after the line break iff A and B elements together exceed the line (this will require at minimum as many comma-separated selectors as the number of B-tabs that can be fit into the width of the box):

.A:nth-child(n+4) + .B:nth-last-of-type(n+1),
.A:nth-child(n+3) + .B:nth-last-of-type(n+2),
.A:nth-child(n+2) + .B:nth-last-of-type(n+3),
.A:nth-child(n+1) + .B:nth-last-of-type(n+4)
{ order: 2; }

This tells the subsequent elements to do the same:

.A:nth-child(n+4) + .B:nth-last-of-type(n+1) ~ *,
.A:nth-child(n+3) + .B:nth-last-of-type(n+2) ~ *,
.A:nth-child(n+2) + .B:nth-last-of-type(n+3) ~ *,
.A:nth-child(n+1) + .B:nth-last-of-type(n+4) ~ *
{ order: 2; }

Theoretically it should be possible to combine these two blocks via nesting:

.A:nth-child(n+4) + .B:nth-last-of-type(n+1),
.A:nth-child(n+3) + .B:nth-last-of-type(n+2),
.A:nth-child(n+2) + .B:nth-last-of-type(n+3),
.A:nth-child(n+1) + .B:nth-last-of-type(n+4)
{ order: 2; 
   ~ * { order: 2; }
}

However, in my case that didn't work. I'm trying to style my browser UI, and apparently it doesn't accept nesting.

If anyone has a suggestion for how to further streamline this, it is more than welcome.

Upvotes: 0

Joshua Cooper
Joshua Cooper

Reputation: 26

Without reformatting the HTML, implementing a few Javascript conditional if-statements might make it easier since you are still able to target the className directly.

If you are able to reformat the HTML in some way, it might be easier to control with creating 2 new HTML div elements in the box element (1 surrounding each different class type) in order to control easier with flex-box.

Upvotes: 1

Related Questions