Copied SVG to clipboard
Something went wrong
Copied code to clipboard
Something went wrong

Default

User image

Default

Name

  • Osmo Discount
    -25%
The Vault/

Tab System with Autoplay Option

Tab System with Autoplay Option

Documentation

Webflow

Code

Setup: External Scripts

External Scripts in Webflow

Make sure to always put the External Scripts before the Javascript step of the resource.

In this video you learn where to put these in your Webflow project? Or how to include a paid GSAP Club plugin in your project?

HTML

Copy
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.7/dist/gsap.min.js"></script>

Step 1: Copy structure to Webflow

Copy structure to Webflow

In the video below we described how you can copy + paste the structure of this resource to your Webflow project.

Copy to Webflow

Webflow structure is not required for this resource.

Step 1: Add HTML

HTML

Copy
<div data-tabs-autoplay-duration="5000" data-tabs="wrapper" data-tabs-autoplay="true" class="tab-layout__wrap">
    <div class="tab-layout__col">
      <div class="tab-content__wrap">
        <div class="tab-content__inner">
          <div class="tab-content__top">
            <h1 class="tab-heading">Explore the perks of being a member</h1>
          </div>
          <div role="tablist" class="tab-content__bottom">
            <a role="tab" data-tabs="content-item" href="#" class="tab-content__item w-inline-block">
              <div class="tab-content__item-main">
                <div class="content-item__nr">
                  <div>01</div>
                </div>
                <h2 class="content-item__heading">Explore the vault</h2>
              </div>
              <div data-tabs="item-details" class="tab-content__item-detail">
                <div class="tab-description__spacer"></div>
                <p class="tab-description">The Vault is where everything lives. Organized into clear categories , it’s designed to make browsing easy. Whether you’re looking for a specific slider, animation,or utility, our quick-find search has you covered. </p>
                <div class="tab-description__spacer"></div>
              </div>
              <div class="tab-content__item-bottom">
                <div data-tabs="item-progress" class="tab-progress"></div>
              </div>
            </a>
            <a role="tab" data-tabs="content-item" href="#" class="tab-content__item w-inline-block">
              <div class="tab-content__item-main">
                <div class="content-item__nr">
                  <div>02</div>
                </div>
                <h2 class="content-item__heading">Learn from videos</h2>
              </div>
              <div data-tabs="item-details" class="tab-content__item-detail">
                <div class="tab-description__spacer"></div>
                <p class="tab-description">We also include videos that explain the concept, go deeper on the subject, or maybe might spark some new ideas for the resources that you&#x27;re using.</p>
                <div class="tab-description__spacer"></div>
              </div>
              <div class="tab-content__item-bottom">
                <div data-tabs="item-progress" class="tab-progress"></div>
              </div>
            </a>
            <a role="tab" data-tabs="content-item" href="#" class="tab-content__item w-inline-block">
              <div class="tab-content__item-main">
                <div class="content-item__nr">
                  <div>03</div>
                </div>
                <h2 class="content-item__heading">Implement Osmo Basics</h2>
              </div>
              <div data-tabs="item-details" class="tab-content__item-detail">
                <div class="tab-description__spacer"></div>
                <p class="tab-description">These are the foundations you’ll rely on for every award-worthy project. Master the basics, and the flashy stuff will actually have something solid to stand on.</p>
                <div class="tab-description__spacer"></div>
              </div>
              <div class="tab-content__item-bottom">
                <div data-tabs="item-progress" class="tab-progress"></div>
              </div>
            </a>
          </div>
        </div>
      </div>
    </div>
    <div class="tab-layout__col">
      <div aria-live="polite" role="region" class="tab-visual__wrap">
        <div id="tab1" data-tabs="visual-item" role="tabpanel" class="tab-visual__item active">
          <div class="tab-visual__inner"><img src="https://cdn.prod.website-files.com/679013b2e01832b21eba1b5b/679016ba86e862ccd6750213_tab-asset-vault.avif" loading="lazy"  alt="" class="tab-image"></div>
        </div>
        <div id="tab2" data-tabs="visual-item" role="tabpanel" class="tab-visual__item">
          <div class="tab-visual__inner"><img src="https://cdn.prod.website-files.com/679013b2e01832b21eba1b5b/679016ba8f0f937c2b5b1d0f_tab-asset-videos.avif" loading="lazy" alt="" class="tab-image"></div>
        </div>
        <div id="tab3" data-tabs="visual-item" role="tabpanel" class="tab-visual__item">
          <div class="tab-visual__inner"><img src="https://cdn.prod.website-files.com/679013b2e01832b21eba1b5b/679016bab4910a3b7a9e0e64_tab-asset-basics.avif" loading="lazy" alt="" class="tab-image"></div>
        </div>
      </div>
    </div>
</div>

HTML structure is not required for this resource.

Step 2: Add CSS

CSS

Copy
.tab-layout__wrap {
  z-index: 1;
  grid-row-gap: 3em;
  flex-flow: wrap;
  padding-left: 1em;
  padding-right: 1em;
  display: flex;
  position: relative;
}

.tab-layout__col {
  width: 50%;
  padding-left: .5em;
  padding-right: .5em;
}

.tab-content__inner {
  grid-column-gap: 3em;
  grid-row-gap: 3em;
  flex-flow: column;
  justify-content: space-between;
  align-items: flex-start;
  min-height: 100%;
  padding-top: 1em;
  padding-bottom: 0;
  padding-right: 2.5em;
  display: flex;
}

.tab-content__top {
  grid-column-gap: 2em;
  grid-row-gap: 2em;
  flex-flow: column;
  justify-content: flex-start;
  align-items: flex-start;
  display: flex;
}

.tab-heading {
  margin-top: 0;
  margin-bottom: 0;
  font-size: 3.5em;
  font-weight: 500;
  line-height: 1;
}

.tab-visual__wrap {
  aspect-ratio: 1.6;
  height: 50em;
  position: relative;
}

.tab-visual__item {
  visibility: hidden;
  justify-content: flex-start;
  align-items: center;
  width: 100%;
  height: 100%;
  display: flex;
  position: absolute;
}

.tab-visual__item.active {
  visibility: visible;
}

.tab-visual__inner {
  border: 1px solid #0003;
  border-radius: .5em;
  width: 100%;
  height: 100%;
  padding: .5em;
  overflow: hidden;
}

.tab-image {
  object-fit: cover;
  object-position: 0% 50%;
  border-radius: .25em;
  width: 100%;
  height: 100%;
  position: relative;
}

.tab-content__wrap {
  width: 100%;
  max-width: 36em;
  height: 100%;
  margin-left: auto;
  margin-right: 0;
}

.tab-content__bottom {
  flex-flow: column;
  justify-content: space-between;
  align-items: stretch;
  width: 100%;
  max-width: 30em;
  margin-top: 0;
  margin-bottom: 0;
  padding-left: 0;
  display: flex;
}

.tab-content__item {
  color: #131313;
  width: 100%;
  padding-top: 2em;
  padding-bottom: 2em;
  text-decoration: none;
  transition: opacity .25s;
  position: relative;
}

.tab-content__item-main {
  grid-column-gap: 2em;
  grid-row-gap: 2em;
  justify-content: flex-start;
  align-items: flex-start;
  width: 100%;
  display: flex;
}

.content-item__nr {
  color: #fff;
  background-color: #131313;
  border: 1px solid #131313;
  border-radius: 100em;
  justify-content: center;
  align-items: center;
  width: 2.5em;
  height: 2.5em;
  margin-top: .2em;
  font-family: RM Mono, Arial, sans-serif;
  font-size: .75em;
  font-weight: 400;
  transition: transform .4s cubic-bezier(.625, .05, 0, 1);
  display: flex;
}

.content-item__heading {
  margin-top: 0;
  margin-bottom: 0;
  font-size: 2em;
  font-weight: 500;
  line-height: 1;
}

.tab-content__item-detail {
  width: 100%;
  height: 0;
  padding-left: 4em;
  overflow: hidden;
}

.tab-description {
  margin-bottom: 0;
  font-size: 1em;
}

.tab-description__spacer {
  padding-top: 1em;
}

.tab-content__item-bottom {
  background-color: #0003;
  width: 100%;
  height: 1px;
  transition: background-color .2s;
  position: absolute;
  inset: auto 0% 0%;
}

.tab-progress {
  transform-origin: 0%;
  transform-style: preserve-3d;
  background-color: #ff4c24;
  width: 100%;
  height: 1px;
  transform: scale3d(0, 1, 1);
}

@media screen and (max-width: 991px) {
  .tab-layout__col {
    width: 100%;
    padding-left: 0;
    padding-right: 0;
  }
  
  .tab-content__inner {
    justify-content: space-between;
    align-items: stretch;
    padding: 0;
  }

  .tab-content__top {
    grid-column-gap: 1.5em;
    grid-row-gap: 1.5em;
  }

  .tab-visual__wrap {
    height: auto;
    padding-left: 0;
    padding-right: 0;
  }

  .tab-visual__item {
    overflow: hidden;
  }

  .tab-content__wrap {
    max-width: none;
    margin-left: 0;
  }
}

@media screen and (max-width: 767px) {
  .tab-layout__wrap {
    grid-row-gap: 2em;
  }

  .tab-heading {
    font-size: 2.8em;
  }

  .tab-visual__item {
    border-radius: .25em;
  }

  .tab-content__bottom {
    max-width: none;
  }

  .tab-content__item-main {
    grid-column-gap: 1.5em;
    grid-row-gap: 1.5em;
  }

  .content-item__nr {
    margin-top: -.2em;
  }

  .content-item__heading {
    font-size: 1.5em;
  }
}

@media screen and (max-width: 479px) {
  .tab-heading {
    font-size: 3em;
  }

  .tab-visual__inner {
    border-style: none;
    border-radius: .25em;
    padding: 0;
  }

  .tab-image {
    aspect-ratio: auto;
  }

  .tab-content__item {
    padding-top: 1.5em;
    padding-bottom: 1.5em;
  }

  .tab-content__item-main {
    grid-column-gap: 1em;
    grid-row-gap: 1em;
  }

  .content-item__nr {
    flex: none;
  }

  .content-item__heading {
    font-size: 1.5em;
  }

  .tab-content__item-detail {
    padding-left: 3em;
  }
}

Step 2: Add custom Javascript

Custom Javascript in Webflow

In this video, Ilja gives you some guidance about using JavaScript in Webflow:

Step 2: Add Javascript

Step 3: Add Javascript

Javascript

Copy
function initTabSystem() {
  const wrappers = document.querySelectorAll('[data-tabs="wrapper"]');
  
  wrappers.forEach((wrapper) => {
    const contentItems = wrapper.querySelectorAll('[data-tabs="content-item"]');
    const visualItems = wrapper.querySelectorAll('[data-tabs="visual-item"]');
    
    const autoplay = wrapper.dataset.tabsAutoplay === "true";
    const autoplayDuration = parseInt(wrapper.dataset.tabsAutoplayDuration) || 5000;
    
    let activeContent = null; // keep track of active item/link
    let activeVisual = null;
    let isAnimating = false;
    let progressBarTween = null; // to stop/start the progress bar

    function startProgressBar(index) {
      if (progressBarTween) progressBarTween.kill();
      const bar = contentItems[index].querySelector('[data-tabs="item-progress"]');
      if (!bar) return;
      
      // In this function, you can basically do anything you want, that should happen as a tab is active
      // Maybe you have a circle filling, some other element growing, you name it.
      gsap.set(bar, { scaleX: 0, transformOrigin: "left center" });
      progressBarTween = gsap.to(bar, {
        scaleX: 1,
        duration: autoplayDuration / 1000,
        ease: "power1.inOut",
        onComplete: () => {
          if (!isAnimating) {
            const nextIndex = (index + 1) % contentItems.length;
            switchTab(nextIndex); // once bar is full, set next to active – this is important
          }
        },
      });
    }

    function switchTab(index) {
      if (isAnimating || contentItems[index] === activeContent) return;
      
      isAnimating = true;
      if (progressBarTween) progressBarTween.kill(); // Stop any running progress bar here
      
      const outgoingContent = activeContent;
      const outgoingVisual = activeVisual;
      const outgoingBar = outgoingContent?.querySelector('[data-tabs="item-progress"]');
      
      const incomingContent = contentItems[index];
      const incomingVisual = visualItems[index];
      const incomingBar = incomingContent.querySelector('[data-tabs="item-progress"]');
      
      outgoingContent?.classList.remove("active");
      outgoingVisual?.classList.remove("active");
      incomingContent.classList.add("active");
      incomingVisual.classList.add("active");
      
      const tl = gsap.timeline({
        defaults: { duration: 0.65, ease: "power3" },
        onComplete: () => {
          activeContent = incomingContent;
          activeVisual = incomingVisual;
          isAnimating = false;
          if (autoplay) startProgressBar(index); // Start autoplay bar here
        },
      });
      
      // Wrap 'outgoing' in a check to prevent warnings on first run of the function
      // Of course, during first run (on page load), there's no 'outgoing' tab yet!
      if (outgoingContent) {
        outgoingContent.classList.remove("active");
        outgoingVisual?.classList.remove("active");
        tl.set(outgoingBar, { transformOrigin: "right center" })
          .to(outgoingBar, { scaleX: 0, duration: 0.3 }, 0)
          .to(outgoingVisual, { autoAlpha: 0, xPercent: 3 }, 0)
          .to(outgoingContent.querySelector('[data-tabs="item-details"]'), { height: 0 }, 0);
      }

      incomingContent.classList.add("active");
      incomingVisual.classList.add("active");
      tl.fromTo(incomingVisual, { autoAlpha: 0, xPercent: 3 }, { autoAlpha: 1, xPercent: 0 }, 0.3)
        .fromTo( incomingContent.querySelector('[data-tabs="item-details"]'),{ height: 0 },{ height: "auto" },0)
        .set(incomingBar, { scaleX: 0, transformOrigin: "left center" }, 0);
    }

    // on page load, set first to active
    // idea: you could wrap this in a scrollTrigger
    // so it will only start once a user reaches this section
    switchTab(0);
    
    // switch tabs on click
    contentItems.forEach((item, i) =>
      item.addEventListener("click", () => {
        if (item === activeContent) return; // ignore click if current one is already active
        switchTab(i);
      })
    );
    
  });
}

document.addEventListener("DOMContentLoaded", ()=> {
  initTabSystem();
});

Step 3: Add custom CSS

Step 2: Add custom CSS

Custom CSS in Webflow

Curious about where to put custom CSS in Webflow? Ilja explains it in the below video:

CSS

Copy
/* Fade the links that are not active */
.tab-content__bottom:has( .tab-content__item.active) .tab-content__item:not(.active){
	opacity: 0.5;
}

@media (hover:hover) and (pointer:fine){

.tab-content__item:not(.active):hover .tab-content__item-bottom{ background-color: rgba(0, 0, 0, 0.75)}
.tab-content__item:not(.active):hover .content-item__nr{ transform: translate(25%, 0px) }

}

Implementation

Autoplay

There's an attribute of data-tabs-autoplay on the wrapper that can be set to 'true' or simply removed. Control the duration of the autoplay in milliseconds using the data-tabs-autoplay-duration attribute. In the JS, there's a function called startProgressBar(), it's in here where you can define whatever animation you want to show the progress of the autoplay duration. In our example, we simply scale a div on the x-axis. In the onComplete() call from the GSAP tween, is where we call the switch to the next tab.

Animation

Control all of the animations during switch in the switchTab() function. We're using a GSAP timeline in our example. To prevent null warnings on the first ever switch, we've wrapped the 'outgoing' tweens in an if statement.

Resource Details

Accordion
Advanced
Autoplay
Basic
Information
Layout
Script
Sections
Setup

Original source

Ilja van Eck

Creator Credits

We always strive to credit creators as accurately as possible. While similar concepts might appear online, we aim to provide proper and respectful attribution.