Multi-Line Exclusion Tabs

Exclusion tabs inspired by Paco using WAAPI and Clip-path. I extend it to support wrapped content with flex-wrap on the parent container.

You can use a span element and move that based on the active item, but CSS is pretty powerful right now and there's so much we can do with it. The trick is to use clip-path to create a mask that reveals only the active tab.


How it works

To animate the clip-path across the horizontal axis, you have to measure how much you have to clip on the left and how much you have to clip on the right.

We add a ref to the activeTabElement and use that to get it's offsets.

const { offsetLeft, offsetWidth } = activeTabElement;
const clipLeft = offsetLeft;
const clipRight = offsetLeft + offsetWidth;

Once you've gotten that, you slot the values into the clip-path. We are basically saying clip everything from the left of the element and everything after the right of the element.

container.style.clipPath = `inset(0 ${Number(100 - (clipRight / container.offsetWidth) * 100).toFixed()}% 0 ${Number((clipLeft / container.offsetWidth) * 100).toFixed()}% round 17px)`;

Emil has a great introduction to the feature.

To make this work for flex-wrap containers, we need to figure out the top and bottom positions as well. With that we can by what percentage we have to clip.

In this case:

const { offsetLeft, offsetWidth, offsetTop, offsetHeight } = activeTabElement
const clipLeft = offsetLeft
const clipRight = offsetLeft + offsetWidth
const clipBottom = offsetTop + offsetHeight
const clipTop = offsetTop

With that, our clip-path looks like this:

const clipBottomValue = Number(
  100 - (clipBottom / container.offsetHeight) * 100).toFixed()

const clipTopValue = Number(
  (clipTop / container.offsetHeight) * 100).toFixed()

const clipRightValue = Number(
  100 - (clipRight / container.offsetWidth) * 100).toFixed()

container.style.clipPath = `inset(${clipTopValue}% ${clipRightValue}% ${clipBottomValue}% ${Number(
  (clipLeft / container.offsetWidth) * 100).toFixed()}% round 17px)`

It wasn't intuitive at first but getting a pen and paper and drawing out a 4/4 rectangle helped simplify it.

Thanks for Emil for the initial prototype.

You can view the full code here.