Where: Component JS / Filter and Load More, placed inside each filter section. Used on /branchen, /lexikon (search only), /newsletter-archiv, /roadmap (chips, with the roadmap script on top), /guides-vorlagen, /webinare and every other page with chips or search.
What it does
Turns a normal Webflow Collection List into a searchable, filterable list. Visitors type in a search box or click category chips, and the list updates instantly without a page reload. It also loads the list's other pages in the background, so the filter searches all items, not only the first page, and it replaces Webflow's page numbers with a "load more" button.
How it works, step by step
- On page load it looks for every
[data-filter="section"] (fallback class .section_filter). Each section is set up once and then marked data-filter-ready="true". - Inside the section it finds the results area, the chip groups and the search box. If a section has neither chips nor search, or no items, it does nothing.
- It reads every item (card) once: its title, its text (for search) and its values, which are the texts of the small tags on the card (
[data-filter="value"] or .chip_item). Values are normalised: lowercase, ä→a, ß→ss, &→und, punctuation removed. - Each chip's label is normalised the same way. A chip matches a card when the two normalised texts are equal. That is the whole matching logic: chip label text = tag text on the card.
- Each group holds one active value. Clicking a second chip in the same group replaces the first. Groups combine with AND (Branche = X and Kategorie = Y). Search is AND on top.
- Search waits 180 ms after typing, then shows only cards whose title or text contains the term, and highlights the match with
<mark>. - Visible cards fade in (420 ms, small stagger). The empty state shows when nothing matches. A hidden live region announces "X Ergebnisse gefunden" for screen readers.
- The state is written to the URL: each group's name becomes a parameter, search becomes
?q=. The URL can be shared and the back button works. - When the browser is idle, it fetches the list's other Webflow pages (max 40, 8 s timeout each), adds their cards hidden, and from then on filters across all of them. Fetched pages are cached in sessionStorage, keyed by the site's "Last Published" date, so every publish starts fresh.
- The "load more" button shows the next batch (batch size = items per page set on the Collection List) and moves keyboard focus to the first new card.
- Block mode (cards grouped under headings, e.g. Branchen categories, Lexikon letters): when
[data-filter="block"] exists, a block hides when none of its cards are visible, background page loading is off, and an ItemList JSON-LD schema is added to the page head.
What it needs
| Attribute (class fallback) | On | Required? |
|---|
data-filter="section" (.section_filter) | outer wrapper | yes |
data-filter="results" (.filter_form_bottom_right_wrap) | wrapper around the list | yes |
data-filter="item" (.w-dyn-item) | each card | recommended |
data-filter="group" + data-filter-name | each chip group; name becomes the URL parameter | for chips |
data-filter="chip" (.filter_category_wrap) | each chip, must contain a radio or checkbox | for chips |
data-filter="chip-label" (.filter_category_label) | chip text | optional |
data-filter="value" (.chip_item) | tag on the card that chips match against | for chips |
data-filter="title" / "text" | what search reads (fallback: headings, .u-rich-text) | optional |
data-filter="search" (.search_bar_input) | search input | for search |
data-filter="reset" (.filter_reset_link) | "Alle" chip / reset link | optional |
data-filter="empty" (.filter_empty_state_wrap) | no results message | recommended |
data-filter="pagination" (.w-pagination-wrapper) | Webflow pagination of the list | for load more |
data-filter="load-more" / "load-more-count" | button / remaining count | optional (else Webflow's Next link is used) |
data-filter="block" / "block-name" | group of cards / its heading | block mode only |
Change it safely
- Add a new filter page by copying an existing one (e.g. /webinare) and changing the Collection List source. All attributes come with it.
- Add a chip: duplicate a chip and change its label so it matches the card tag text exactly (case and umlauts do not matter).
- Rename a URL parameter: change
data-filter-name on the group. Old shared links with the old name stop pre-filtering. - Change the batch size: change "items per page" on the Collection List, not the script.
- The embed is minified. Do not edit the line by hand in the Designer. If the logic must change: copy it into a formatter (e.g. prettier), use the function map below, change, test on a branch page, paste the whole block back.
What breaks it
- A chip label that differs from the card tag text ("Handwerk & Bau" chip vs "Handwerk und Bau" tag works, "Handwerker" vs "Handwerk" does not).
- Removing the radio/checkbox inside a chip: the chip is ignored.
- Leftover Finsweet attributes (
fs-cmsfilter-*) or a second filter script on the same section. - Two
data-filter="section" wrappers nested inside each other. - Draft items: they are not on the published pages, so the filter cannot find them.
How to test
// 1. Is it running, and how many cards does it see?
document.querySelectorAll('[data-filter="section"],.section_filter').forEach(s =>
console.log(s.dataset.filterReady, s.querySelectorAll('[data-filter="item"],.w-dyn-item').length));
// 2. What did the last action find?
document.querySelector('[data-filter="status"]')?.textContent
// 3. Force fresh background pages (clears the cache)
Object.keys(sessionStorage).filter(k => k.startsWith('omxf:')).forEach(k => sessionStorage.removeItem(k));
Then click every chip once, type a search term with an umlaut, use the back button, open the page with the URL parameters copied, and check "load more" moves focus.