Chip Filter — Multi-Select Filter Chips With Live Counts

Chip Filter · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

What's included

Features

Multi-select chips
Toggle any number of categories; "All" resets and a Clear chip appears while a selection is active.
Any / All matching
Switch between OR and AND logic for items that belong to several categories.
Faceted live counts
Each chip shows how many results it would lead to, and chips with zero results are disabled.
Search plus chips
Debounced, word-by-word search across name, description and category, combined with the chip filters.
View Transitions animation
Cards glide and fade with document.startViewTransition(), with a CSS fade-in fallback and reduced-motion support.
Shareable URL state
Filters sync to ?cat=…&q=…&match=… with pushState, and the back button restores previous filters.
Accessible toolbar
aria-pressed chips in a role="toolbar" with arrow-key navigation, a live result announcement and visible focus rings.
Responsive and dark mode
Auto-fill grid, swipeable chip row on phones, CSS custom properties and a prefers-color-scheme dark theme.

About this UI Snippet

Chip Filter — Multi-Select Filter Chips With Faceted Counts, Search and Shareable URLs

Screenshot of the Chip Filter snippet rendered live

Chip Filter is a complete, production-style filter bar for any grid of items: multi-select category chips with live counts, a search box, an Any/All match switch, animated results, and filters that live in the URL so a filtered view can be shared or bookmarked. It is plain HTML, CSS and vanilla JavaScript with no dependencies, and every chip, count and card is generated from one data array.

Filter chips (also called filter pills or tag filters) are how component libraries, marketplaces, job boards, docs sites and product catalogues let people narrow a list without leaving the page. This snippet covers the details that simple demos skip.

Multi-select with Any or All matching

Each category chip toggles on and off, so people can combine categories. The Any/All switch decides how they combine: Any shows items in at least one selected category (OR), All shows only items that belong to every selected category (AND). Items can carry several categories (the Data Table is both Data and Forms), so both modes give different, useful results. "All" resets the selection, and a dashed Clear chip appears whenever something is selected.

Faceted counts that update live

Each chip's badge shows how many results it would lead to, given the current search, and in All mode, the current selection too. Type "chart" and the counts drop to match. A chip that would lead to zero results is disabled and dimmed, so people never click into a dead end. This is the "faceted search" pattern used by large stores, done in about 20 lines.

Search combined with the chips

The search box matches name, description and category label, word by word, and works together with the chips. Input is debounced by 120 ms so the grid doesn't flicker while typing, and Escape clears it.

Animated results with View Transitions

Cards are built once and only shown or hidden, each with its own view-transition-name. Filtering runs inside document.startViewTransition(), so in supporting browsers cards glide to their new positions and fade in and out. Other browsers get a lightweight CSS fade-in, and prefers-reduced-motion turns animation off completely.

Shareable URLs

The current filter is written to the query string (?cat=forms,data&q=chart&match=all) with history.pushState for chip changes and replaceState for typing, and popstate restores it, so the back button undoes a filter and a link reopens the exact same view.

Accessible by default

Chips are real buttons with aria-pressed, grouped in a role="toolbar" you can move through with the arrow keys, Home and End. A role="status" line announces the result ("Showing 4 of 12 components in Data or Forms"), focus rings are visible, and the empty state offers a "Clear filters" button.

Responsive and themeable

The grid uses repeat(auto-fill, minmax(190px, 1fr)), and on phones the chips become one swipeable row with faded edges. Colours are CSS custom properties with a built-in dark theme via prefers-color-scheme, and the icons are inline SVG, so they look the same on every device.

Step by step

How to Use

  1. 1
    Toggle categoriesClick one or more category chips. Each selected chip fills with the accent colour, and the grid animates to show matching cards.
  2. 2
    Choose Any or AllUse the Any/All switch to show items in any selected category, or only items that are in every selected category.
  3. 3
    SearchType in the search box to narrow the results further. Chip counts update as you type, and chips with no results are disabled.
  4. 4
    Share the viewThe URL updates with your filters, so copy it to share or bookmark the exact view. The browser back button steps back through filter changes.
  5. 5
    Use the keyboardTab to the chips and use the arrow keys, Home and End to move between them, and Space or Enter to toggle.
  6. 6
    Plug in your dataEdit the CATS and ITEMS arrays. Each item needs an id, name, desc and a cats array. Chips, counts, tags and icons are generated automatically.

Real-world uses

Common Use Cases

🧩
Design system documentation
Let readers filter components by category and combine them with All mode, for example forms that also give feedback, while the URL keeps each filtered view linkable from docs.
🛍
Product catalogue filters
Use the chips for departments and the search box for product names, so the faceted counts show shoppers how many items each department has for their search before they click.
💼
Job board listings
Turn categories into role types or work styles like Remote and Full-time, and switch to All mode so candidates see only jobs that match every requirement they pick.
📚
Blog and resource libraries
Filter articles, templates or videos by topic, combined with keyword search, and share links like ?cat=css&q=grid in newsletters that open straight to the filtered list.
🎨
Portfolio project galleries
Tag projects with several skills and let visitors pick the ones they care about, while the View Transition animation keeps the gallery feeling polished as cards move.

Got questions?

Frequently Asked Questions

Any (OR) shows items that belong to at least one selected category, so selecting more chips shows more items. All (AND) shows only items that belong to every selected category, so selecting more chips narrows the results. All mode is useful when items carry several categories, like a component that is both Data and Forms.

Each count is calculated from the items that match the current search. In All mode it also takes the current selection into account, so it shows how many results you would get by adding that chip. Chips that would give zero results are disabled, so users never land on an empty grid by accident.

document.startViewTransition() works in current Chrome, Edge, Safari and other Chromium-based browsers. Where it is not available, cards use a short CSS fade-in instead, and the filter works the same. Users with "reduce motion" turned on get no animation at all.

Fetch your items once, map each to { id, name, desc, cats }, assign the result to ITEMS, then build the cards and call update(). Filtering stays in memory, so chip clicks don't need extra network requests. For very large lists, filter on the server and render only the current page.

Every change writes the selected categories, search text and match mode to the query string, using pushState for chip clicks and replaceState while typing. On load, and when the user presses Back or Forward, readUrl() restores the filters from the URL. In sandboxed previews that cannot change the URL, filtering still works normally.

Yes. Use the React, Vue, Angular or Tailwind export above the preview as a starting point. In React, keep selected, mode and query in state and derive the visible items and counts with useMemo; in Vue use computed properties; in Angular use signals or getters. The matching and faceted-count functions are framework-agnostic and can be copied as they are.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet into an AI coding assistant like Claude and ask it to adapt the filter to your data: load items from an API, add a sort dropdown or price range next to the chips, or turn it into a React, Vue or Svelte component. It is also a good way to understand the faceted-count logic, or to discuss when to move filtering to the server for very large lists.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a multi-select chip filter for a card grid in plain HTML, CSS and vanilla JavaScript, with no dependencies.

Requirements:
- Generate category chips from a CATS array and cards from an ITEMS array where each item has id, name, desc and a cats array (items can belong to several categories).
- Chips are toggle buttons with aria-pressed inside a role="toolbar" with arrow-key, Home and End navigation. Include an "All" chip that resets the selection and a "Clear" chip shown only while a selection exists.
- Add an Any/All switch: Any shows items in at least one selected category, All shows items in every selected category.
- Add a debounced search box that matches name, description and category labels word by word, combined with the chips.
- Show a faceted count on each chip (results it would produce given the search, and the selection in All mode) and disable chips that would give zero results.
- Build cards once and only show or hide them; animate changes with document.startViewTransition() using a unique view-transition-name per card, with a CSS fade-in fallback and no animation under prefers-reduced-motion.
- Sync selected categories, search and mode to the URL query string (pushState for chip changes, replaceState while typing) and restore them on load and on popstate.
- Announce results in a role="status" line ("Showing 4 of 12 components in Data or Forms"), show an empty state with a "Clear filters" button, use a responsive auto-fill grid, a swipeable chip row on small screens, CSS custom properties and a dark theme.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="cf">
  <header class="cf-head">
    <div>
      <h1 class="cf-title">Component Library</h1>
      <p class="cf-sub" id="cfSub"></p>
    </div>
    <label class="cf-search">
      <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg>
      <input id="cfSearch" type="search" placeholder="Search components" aria-label="Search components" autocomplete="off" spellcheck="false">
    </label>
  </header>

  <div class="cf-bar">
    <div class="cf-chips" id="cfChips" role="toolbar" aria-label="Filter by category"></div>
    <div class="cf-mode" role="group" aria-label="Match categories">
      <button type="button" class="cf-mode-btn" data-mode="any" aria-pressed="true" title="Show items in any selected category">Any</button>
      <button type="button" class="cf-mode-btn" data-mode="all" aria-pressed="false" title="Show items in every selected category">All</button>
    </div>
  </div>

  <p class="cf-status" id="cfStatus" role="status" aria-live="polite"></p>

  <div class="cf-grid" id="cfGrid"></div>

  <div class="cf-empty" id="cfEmpty" hidden>
    <svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5M8.5 11h5"/></svg>
    <p>No components match these filters.</p>
    <button type="button" class="cf-reset" id="cfReset">Clear filters</button>
  </div>
</div>