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

Default

User image

Default

Name

  • Osmo Discount
    -25%
Osmo Basics/

Basic Filter Setup

Basic Filter Setup

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

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 role="group" data-filter-group="" class="filter-group">
  <div class="filter-buttons">
    <button data-filter-target="all" data-filter-status="active" aria-pressed="true" aria-controls="filter-list" class="filter-btn">All</button>
    <button data-filter-target="red-fruit" data-filter-status="not-active" aria-pressed="false" aria-controls="filter-list" class="filter-btn">Red</button>
    <button data-filter-target="yellow-fruit" data-filter-status="not-active" aria-pressed="false" aria-controls="filter-list" class="filter-btn">Yellow</button>
    <button data-filter-target="other" data-filter-status="not-active" aria-pressed="false" aria-controls="filter-list" class="filter-btn">Other</button>
  </div>
  <div aria-live="polite" role="list" class="filter-list">
    <div role="listitem" data-filter-name="red-fruit" data-filter-status="active" class="filter-list__item">
      <div class="demo-card">
        <div class="demo-card__top">
          <div class="demo-card__visual">
            <div class="demo-card__visual-before"></div><span class="demo-card__emoji">🍉</span>
          </div>
        </div>
        <div class="demo-card__bottom">
          <h3 class="demo-card__h3">Watermelon</h3>
        </div>
      </div>
    </div>
    <div role="listitem" data-filter-name="yellow-fruit" data-filter-status="active" class="filter-list__item">
      <div class="demo-card">
        <div class="demo-card__top">
          <div class="demo-card__visual">
            <div class="demo-card__visual-before"></div><span class="demo-card__emoji">🍌</span>
          </div>
        </div>
        <div class="demo-card__bottom">
          <h3 class="demo-card__h3">Banana</h3>
        </div>
      </div>
    </div>
    <div role="listitem" data-filter-name="red-fruit" data-filter-status="active" class="filter-list__item">
      <div class="demo-card">
        <div class="demo-card__top">
          <div class="demo-card__visual">
            <div class="demo-card__visual-before"></div><span class="demo-card__emoji">🍒</span>
          </div>
        </div>
        <div class="demo-card__bottom">
          <h3 class="demo-card__h3">Cherry</h3>
        </div>
      </div>
    </div>
    <div role="listitem" data-filter-name="yellow-fruit" data-filter-status="active" class="filter-list__item">
      <div class="demo-card">
        <div class="demo-card__top">
          <div class="demo-card__visual">
            <div class="demo-card__visual-before"></div><span class="demo-card__emoji">🍍</span>
          </div>
        </div>
        <div class="demo-card__bottom">
          <h3 class="demo-card__h3">Pineapple</h3>
        </div>
      </div>
    </div>
    <div role="listitem" data-filter-name="yellow-fruit" data-filter-status="active" class="filter-list__item">
      <div class="demo-card">
        <div class="demo-card__top">
          <div class="demo-card__visual">
            <div class="demo-card__visual-before"></div><span class="demo-card__emoji">🍋</span>
          </div>
        </div>
        <div class="demo-card__bottom">
          <h3 class="demo-card__h3">Lemon</h3>
        </div>
      </div>
    </div>
    <div role="listitem" data-filter-name="other" data-filter-status="active" class="filter-list__item">
      <div class="demo-card">
        <div class="demo-card__top">
          <div class="demo-card__visual">
            <div class="demo-card__visual-before"></div><span class="demo-card__emoji">🥥</span>
          </div>
        </div>
        <div class="demo-card__bottom">
          <h3 class="demo-card__h3">Coconut</h3>
        </div>
      </div>
    </div>
    <div role="listitem" data-filter-name="red-fruit" data-filter-status="active" class="filter-list__item">
      <div class="demo-card">
        <div class="demo-card__top">
          <div class="demo-card__visual">
            <div class="demo-card__visual-before"></div><span class="demo-card__emoji">🍓</span>
          </div>
        </div>
        <div class="demo-card__bottom">
          <h3 class="demo-card__h3">Strawberry</h3>
        </div>
      </div>
    </div>
    <div role="listitem" data-filter-name="other" data-filter-status="active" class="filter-list__item">
      <div class="demo-card">
        <div class="demo-card__top">
          <div class="demo-card__visual">
            <div class="demo-card__visual-before"></div><span class="demo-card__emoji">🍑</span>
          </div>
        </div>
        <div class="demo-card__bottom">
          <h3 class="demo-card__h3">Peach</h3>
        </div>
      </div>
    </div>
    <div role="listitem" data-filter-name="red-fruit" data-filter-status="active" class="filter-list__item">
      <div class="demo-card">
        <div class="demo-card__top">
          <div class="demo-card__visual">
            <div class="demo-card__visual-before"></div><span class="demo-card__emoji">🍎</span>
          </div>
        </div>
        <div class="demo-card__bottom">
          <h3 class="demo-card__h3">Apple</h3>
        </div>
      </div>
    </div>
    <div role="listitem" data-filter-name="other" data-filter-status="active" class="filter-list__item">
      <div class="demo-card">
        <div class="demo-card__top">
          <div class="demo-card__visual">
            <div class="demo-card__visual-before"></div><span class="demo-card__emoji">🥝</span>
          </div>
        </div>
        <div class="demo-card__bottom">
          <h3 class="demo-card__h3">Kiwi</h3>
        </div>
      </div>
    </div>
    <div role="listitem" data-filter-name="other" data-filter-status="active" class="filter-list__item">
      <div class="demo-card">
        <div class="demo-card__top">
          <div class="demo-card__visual">
            <div class="demo-card__visual-before"></div><span class="demo-card__emoji">🍐</span>
          </div>
        </div>
        <div class="demo-card__bottom">
          <h3 class="demo-card__h3">Pear</h3>
        </div>
      </div>
    </div>
  </div>
</div>

HTML structure is not required for this resource.

Step 2: Add CSS

CSS

Copy
.filter-group {
  min-height: 100vh;
  padding-bottom: 10em;
}

/* Filter Buttons */
.filter-buttons {
  grid-column-gap: .5em;
  grid-row-gap: .5em;
  flex-flow: wrap;
  justify-content: flex-start;
  padding: 1em 1em 3em;
  display: flex;
}

.filter-btn {
  -webkit-appearance: none;
  appearance: none;
  background-color: #efeeec;
  border-radius: 10em;
  padding: .65em 1.25em;
  font-size: 1.5em;
  transition: color 0.6s cubic-bezier(0.625, 0.05, 0, 1), background-color 0.6s cubic-bezier(0.625, 0.05, 0, 1);
}

.filter-btn[data-filter-status="active"] {
  background-color: #131313;
  color: #EFEEEC;
}

/* Filter List */
.filter-list {
  flex-flow: wrap;
  width: 100%;
  display: flex;
}

.filter-list__item {
  width: 25%;
  padding: .75em;
}

.filter-list__item[data-filter-status="active"] {
  transition: opacity 0.6s cubic-bezier(0.625, 0.05, 0, 1), transform 0.6s cubic-bezier(0.625, 0.05, 0, 1);
  transform: scale(1) rotate(0.001deg);
  opacity: 1;
  visibility: visible;
  position: relative;
}
.filter-list__item[data-filter-status="transition-out"] {
  transition: opacity 0.45s cubic-bezier(0.625, 0.05, 0, 1), transform 0.45s cubic-bezier(0.625, 0.05, 0, 1);
  transform: scale(0.9) rotate(0.001deg);
  opacity: 0;
  visibility: visible;
}
.filter-list__item[data-filter-status="not-active"] {
  transform: scale(0.9) rotate(0.001deg);
  opacity: 0;
  visibility: hidden; 
  position: absolute;
}

/* Demo Card */
.demo-card {
  grid-column-gap: 1em;
  grid-row-gap: 1em;
  background-color: #efeeec;
  border-radius: 1.5em;
  flex-flow: column;
  width: 100%;
  padding: 1em;
  display: flex;
}

.demo-card__bottom {
  justify-content: flex-start;
  align-items: center;
  padding-bottom: .25em;
  padding-left: .5em;
  padding-right: .5em;
  display: flex;
}

.demo-card__h3 {
  margin-top: 0;
  margin-bottom: 0;
  font-size: 1.25em;
  font-weight: 500;
  line-height: 1;
}

.demo-card__visual {
  background-color: #e2dfdf;
  border-radius: .5em;
  justify-content: center;
  align-items: center;
  width: 100%;
  display: flex;
  position: relative;
}

.demo-card__visual-before {
  padding-top: 66%;
}

.demo-title {
  padding: 10em 1em 2em;
}

.demo-title__h2 {
  font-size: 5em;
  font-weight: 500;
  line-height: 1;
}

.demo-card__emoji {
  font-size: 4em;
}

@media screen and (max-width: 991px) {
  .filter-list__item {
    width: 50%;
  }
}

@media screen and (max-width: 767px) {
  .filter-list__item {
    width: 100%;
  }

  .demo-title__h2 {
    font-size: 4em;
  }
}

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 initFilterBasic() {
  // Find all filter groups on the page
  const groups = document.querySelectorAll('[data-filter-group]');

  groups.forEach((group) => {
    const buttons = group.querySelectorAll('[data-filter-target]');
    const items = group.querySelectorAll('[data-filter-name]');
    const transitionDelay = 300; // Delay for transition effect (in milliseconds)

    // Function to update the status and accessibility attributes of items
    const updateStatus = (element, shouldBeActive) => {
      // If the item should be active, set it to "active", otherwise "not-active"
      element.setAttribute('data-filter-status', shouldBeActive ? 'active' : 'not-active');
      element.setAttribute('aria-hidden', shouldBeActive ? 'false' : 'true');
    };

    // Function to handle filtering logic when a button is clicked
    const handleFilter = (target) => {
      // Loop through all items and ensure every item transitions out first
      items.forEach((item) => {
        const shouldBeActive = target === 'all' || item.getAttribute('data-filter-name') === target;
        const currentStatus = item.getAttribute('data-filter-status');

        // Only transition items currently visible (status: active)
        if (currentStatus === 'active') {
          item.setAttribute('data-filter-status', 'transition-out');
          // After the transition delay, set the final status
          setTimeout(() => updateStatus(item, shouldBeActive), transitionDelay);
        } else {
          // For items not currently visible, simply update their status after the delay
          setTimeout(() => updateStatus(item, shouldBeActive), transitionDelay);
        }
      });

      // Update the active status for all buttons
      buttons.forEach((button) => {
        const isActive = button.getAttribute('data-filter-target') === target;
        button.setAttribute('data-filter-status', isActive ? 'active' : 'not-active');
        button.setAttribute('aria-pressed', isActive ? 'true' : 'false'); // Accessibility: indicate active state
      });
    };

    // Attach click event listeners to each button
    buttons.forEach((button) => {
      button.addEventListener('click', () => {
        const target = button.getAttribute('data-filter-target');

        // If the button is already active, do nothing
        if (button.getAttribute('data-filter-status') === 'active') return;

        // Trigger the filter logic with the selected target
        handleFilter(target);
      });
    });
  });
}

// Initialize Basic Filter Setup
document.addEventListener('DOMContentLoaded', () => {
  initFilterBasic();
});

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
/* Filter Button */
.filter-btn {
  transition: color 0.6s cubic-bezier(0.625, 0.05, 0, 1), background-color 0.6s cubic-bezier(0.625, 0.05, 0, 1);
}

.filter-btn[data-filter-status="active"] {
  background-color: #131313;
  color: #EFEEEC;
}

/* Filter List Item */
.filter-list__item[data-filter-status="active"] {
  transition: opacity 0.6s cubic-bezier(0.625, 0.05, 0, 1), transform 0.6s cubic-bezier(0.625, 0.05, 0, 1);
  transform: scale(1) rotate(0.001deg);
  opacity: 1;
  visibility: visible;
  position: relative;
}

.filter-list__item[data-filter-status="transition-out"] {
  transition: opacity 0.45s cubic-bezier(0.625, 0.05, 0, 1), transform 0.45s cubic-bezier(0.625, 0.05, 0, 1);
  transform: scale(0.9) rotate(0.001deg);
  opacity: 0;
  visibility: visible;
}

.filter-list__item[data-filter-status="not-active"] {
  transform: scale(0.9) rotate(0.001deg);
  opacity: 0;
  visibility: hidden; 
  position: absolute;
}

Implementation

Filter Group

A container with the [data-filter-group] attribute that keeps buttons and items organised, ensuring multiple groups on the page function independently.

Filter Button

Buttons with the [data-filter-target="example"] attribute indicate which items to display, while the [data-filter-status] attribute tracks whether a button is active or not.

Filter Item

Items to be filtered are identified by the [data-filter-name="example"] attribute, matching the target of a button, and their visibility is controlled by the [data-filter-status] attribute.

Filter Status (not-active, active, transition-out)

The [data-filter-status] attribute determines an item’s state:

  • active: Makes the item visible.
  • not-active: Hides the item.
  • transition-out: Temporarily applied to items for smooth animations when transitioning from active to not-active. Items remain in this state for a specified duration (transitionDelay in JavaScript, in milliseconds: 300ms = 0.3s) before their final state is set.

All button

A special button with [data-filter-target="all"] will display all items in the group when clicked, regardless of their data-filter-name. This button also uses [data-filter-status] to indicate if it is active or not.

Resource Details

Basic
Javascript
Form
Input
Script
Select
Setup
Structure
Tabs

Original source

Dennis Snellenberg

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.