New overflow and positioning features coming to CSS are making it easier to build common UI patterns with minimal JavaScript. This post explores how to build scrollable tabs using features from the overflow module and anchor positioning draft specifications

Carousel? Nah, scrollable tabs!

Recent examples of new experimental scroll features in the CSS overflow module use the Carousel pattern as a demonstration. This post by Adam Argyle provides an in-depth explanation of the features with a gallery of inspiring examples. Similar carousels also featured in some talks at the latest Google IO event and got me thinking about other areas where we can use these new tools.

The tabs pattern is a staple in any design systems and appears in most applications. Without a native HTML implementation, gracefully dealing with a list of tabs that exceed the width of the parent container requires a good amount of JavaScript to achieve. With these new CSS features, we still don't have a native solution but we can at least ditch a good amount of JavaScript.

Using the Scrollable Tabs Material Design component as an reference, we'll attempt to replicate the functionality with minimal JavaScript and explore some new and experimental CSS features that make it possible.

Removing the horizontal scroll bar

To start, we will create a basic tabs structure using flex box to align them with a horizontal overflow. When the list of tab buttons exceeds the available space they overflow and show a horizontal scroll bar.

We don't want to show the scroll bar so set the scrollbar-width property to none to remove it.

<style>
  .tablist {
    display: flex;
    gap: 0.125rem;
    overflow-x: auto;
    scrollbar-width: none;
  }
</style>

<div class="tablist" role="tablist">
  <button role="tab">Tab one</button>
  <button role="tab">Tab two</button>
  <button role="tab">Tab three</button>
  <button role="tab">Tab four</button>
  <button role="tab">Tab five</button>
  <button role="tab">Tab six</button>
  <button role="tab">Tab seven</button>
  <button role="tab">Tab eight</button>
  <button role="tab">Tab nine</button>
  <button role="tab">Tab ten</button>
</div>

Adding buttons to control scrolling

Removing the scroll bar works okay on touch devices but for users with a mouse we need to add next and previous buttons.

The scroll-button psuedo-elements handle this for us. To add the buttons at the start and end of the tab list we specify the inline directions. Providing a content property for the buttons, with some alternative text, enables them.

.tablist::scroll-button(inline-start) {
  content: "<" / "Previous";
}

.tablist::scroll-button(inline-end) {
  content: ">" / "Next";
}

To add common styles for all scroll buttons we use a universal * selector and style button states in the usual way.

.tablist::scroll-button(*) {
  color: rbg(0 0 0 / 75%);
}

.tablist::scroll-button(*):hover {
  color: rbg(0 0 0 / 95%);
}

Positioning the scroll buttons with anchor positioning

To position the scroll buttons to the start and end of the tab list we use anchor positioning. This allows us to align the buttons to the containing box of the tab list, outside of the content overflow.

To achieve this we wrap the tab list in a relative positioned container element and anchor the buttons at the start and end with absolute positioning. The anchor also helps us to center align the buttons with the tabs.

.tablist-wrapper {
  position: relative;
}

.tablist {
  anchor-name: --tab-list;

  &::scroll-button(*) {
    align-self: anchor-center;
    position: absolute;
    position-anchor: --tab-list;
  }

  &::scroll-button(inline-start) {
    left: anchor(start);
  }

  &::scroll-button(inline-end) {
    right: anchor(end);
  }
}

Animating the scroll behaviour

Clicking the scroll buttons jumps the tab list to the next position instantly. To animate the scroll we set the scroll-behavior property with a value of smooth to add a smooth transition to the next set of tabs.

.tablist {
  scroll-behavior: smooth;
}

Putting it together, with a sprinkling of JavaScript

When we put this all together we have a working set of scrollable tabs in less than 20 lines of CSS! We need a small amount of JavaScript to handle tab selection for the demo and to enable keyboard navigation. We create the animated indicator, like what we see in Material Design, with pure CSS using a little bit more anchor positioning.

Looks like your browser doesn't support these CSS features and the example below may not work as expected. Try viewing this page in the latest version of Chrome instead.

You can view the full code for the example above on CodePen.