Filter Chips

@use 'sprucecss/scss/spruce' as *;

.filter-chips {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: spacer('xs') spacer('s');

  &__list {
    @include layout-cluster('xs');
  }
}

.filter-chip {
  @include clear-btn;
  align-items: center;
  background-color: color('surface');
  border: config('border-width-base', $display) solid color('border-strong');
  border-radius: radius('pill');
  color: color('text');
  display: inline-flex;
  font-size: config('font-size-sm', $typography);
  font-weight: 600;
  gap: spacer('xxs');
  padding-block: spacer('xxs');
  padding-inline: spacer('s');
  @include transition($property: 'background-color, border-color, color');

  &::before {
    background-color: currentcolor;
    block-size: 0.9em;
    content: '';
    display: none;
    inline-size: 0.9em;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / contain no-repeat;
  }

  &:hover {
    border-color: color('primary');
    color: color('heading');
  }

  &[aria-pressed='true'] {
    background-color: color-alpha('primary', 0.12);
    border-color: color('primary');
    color: color('primary');

    &::before {
      display: block;
    }
  }

  &:focus-visible {
    outline: 2px solid color('focus-ring');
    outline-offset: 2px;
  }
}
<div class="filter-chips" data-filter-chips>
    <div class="filter-chips__list" role="group" aria-label="Filter jobs">
        <button class="filter-chip" type="button" aria-pressed="true">Remote</button>
        <button class="filter-chip" type="button" aria-pressed="false">Full-time</button>
        <button class="filter-chip" type="button" aria-pressed="true">Design</button>
        <button class="filter-chip" type="button" aria-pressed="false">Engineering</button>
        <button class="filter-chip" type="button" aria-pressed="false">Senior</button>
        <button class="filter-chip" type="button" aria-pressed="false">Part-time</button>
    </div>
    <button class="btn btn--link btn--sm filter-chips__clear" type="button" data-filter-chips-clear>Clear all</button>
    <p class="sr-only" aria-live="polite" data-filter-chips-status></p>
</div>
(() => {
  document.querySelectorAll('[data-filter-chips]').forEach((component) => {
    const chips = [...component.querySelectorAll('[aria-pressed]')];
    const clear = component.querySelector('[data-filter-chips-clear]');
    const status = component.querySelector('[data-filter-chips-status]');

    const update = () => {
      const selected = chips.filter((chip) => chip.getAttribute('aria-pressed') === 'true').map((chip) => chip.textContent.trim());

      clear.hidden = selected.length === 0;
      status.textContent = selected.length ? `Filters: ${selected.join(', ')}` : 'No filters selected';
      component.dispatchEvent(new CustomEvent('filterchange', { detail: selected }));
    };

    chips.forEach((chip) => {
      chip.addEventListener('click', () => {
        chip.setAttribute('aria-pressed', String(chip.getAttribute('aria-pressed') !== 'true'));
        update();
      });
    });

    clear.addEventListener('click', () => {
      chips.forEach((chip) => chip.setAttribute('aria-pressed', 'false'));
      update();
      chips[0].focus();
    });

    clear.hidden = !chips.some((chip) => chip.getAttribute('aria-pressed') === 'true');
  });
})();

Filter Chips

On this page

Toggleable filter chips for refining a list, with a "Clear all" action.

Technical Details

  • Every chip is a toggle button with aria-pressed, in a labelled group; the selected state shows a check mark, so it doesn't rely on color alone.
  • The script toggles the chips, keeps "Clear all" in sync and announces the active filters in a polite live region. It dispatches a filterchange event with the selected values, so you can filter your list.
  • For a single choice, use the Segmented Control or radio buttons instead.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our UI library of accessible components built with Spruce CSS.

Blocks and Pages

Full-width page blocks and complete page templates, ready to copy.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.