Hey,
I just changed over from Finsweet Attibutes Legacy to V2. Does anybody know if there is any way to split the tags, with each active filter being as a seperate tag element, like it was in Legacy. (In V2 only different filter labels are split not each filter, and so also clearing each filter seperately from the Tag is not possible.) Not sure why they changed it in V2.
There isn’t a built-in way to do this in Finsweet Attributes V2 with the default configuration, but you can achieve this behavior with some custom JavaScript.
document.addEventListener('DOMContentLoaded', function() {
// Add custom CSS to make tag items appear as separate tags
const style = document.createElement('style');
style.textContent = `
[fs-cmsfilter-element="tag"] {
display: none !important; /* Hide original tags */
}
.fs-custom-tag {
display: inline-flex;
align-items: center;
margin: 0.25rem;
padding: 0.25rem 0.5rem;
background-color: #f0f0f0;
border-radius: 4px;
font-size: 0.875rem;
}
.fs-custom-tag-remove {
margin-left: 0.5rem;
cursor: pointer;
font-weight: bold;
}
`;
document.head.appendChild(style);
// Process tags
function processFilterTags() {
const tagsContainer = document.querySelector('[fs-cmsfilter-element="tags-container"]');
if (!tagsContainer) return;
// Clear any existing custom tags
const existingCustomTags = document.querySelectorAll('.fs-custom-tag');
existingCustomTags.forEach(tag => tag.remove());
// Get all tags
const tags = document.querySelectorAll('[fs-cmsfilter-element="tag"]');
tags.forEach(tag => {
const field = tag.getAttribute('fs-cmsfilter-field');
const valuesStr = tag.getAttribute('fs-cmsfilter-value');
if (!valuesStr) return;
const values = valuesStr.split(',').map(v => v.trim());
// Create custom tag for each value
values.forEach(value => {
const customTag = document.createElement('div');
customTag.className = 'fs-custom-tag';
customTag.innerHTML = `
<span>${field}: ${value}</span>
<span class="fs-custom-tag-remove">Ă—</span>
`;
// Add click handler to remove individual filters
customTag.querySelector('.fs-custom-tag-remove').addEventListener('click', () => {
// Find if there are other values for this field
const remainingValues = values.filter(v => v !== value);
if (remainingValues.length > 0) {
// Update the original tag with remaining values
tag.setAttribute('fs-cmsfilter-value', remainingValues.join(','));
// Simulate click on clear filters button to apply changes
const clearAllBtn = document.querySelector('[fs-cmsfilter-element="clear"]');
if (clearAllBtn) {
const event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
clearAllBtn.dispatchEvent(event);
// Re-apply remaining filters
setTimeout(() => {
remainingValues.forEach(remainingValue => {
const filterBtn = document.querySelector(`[fs-cmsfilter-field="${field}"][fs-cmsfilter-value*="${remainingValue}"]`);
if (filterBtn) filterBtn.click();
});
}, 100);
}
} else {
// If it's the last value, just click the original tag's remove button
const removeBtn = tag.querySelector('[fs-cmsfilter-element="tag-remove"]');
if (removeBtn) removeBtn.click();
}
});
tagsContainer.appendChild(customTag);
});
});
}
// Watch for changes in the tags container
setTimeout(() => {
const tagsContainer = document.querySelector('[fs-cmsfilter-element="tags-container"]');
if (tagsContainer) {
const observer = new MutationObserver(processFilterTags);
observer.observe(tagsContainer, { childList: true, subtree: true });
// Initial processing
processFilterTags();
}
}, 1000);
});
Thanks. Where exactly would i place this code.
Inside a Custom Code block in your Webflow project:
- Go to your Webflow project dashboard
- Navigate to “Project Settings” or “Site Settings”
- Find the “Custom Code” section
- Add the code within a
<script>
tag in either the “Head Code” or “Footer Code” section (Footer is usually preferred for performance reasons)
I added the code but nothing happened. Could it be that in the code where it says fs-“cmsfilter-element=tag” it should say “fs-list-element=tag” as the new attributes have changed the attribute name of the filter?
V2 documentation shows that tags now use fs-list-element="tag"
, tag fields use fs-list-element="tag-field"
, tag values use fs-list-element :antCitation[]{citations="747e12e9-344b-49a3-818d-19d35f390b12"}="tag-value"
, and tag remove buttons use fs-list-element="tag-remove"
.
document.addEventListener('DOMContentLoaded', function() {
// Add custom CSS to make tag items appear as separate tags
const style = document.createElement('style');
style.textContent = `
[fs-list-element="tag"] {
display: none !important; /* Hide original tags */
}
.fs-custom-tag {
display: inline-flex;
align-items: center;
margin: 0.25rem;
padding: 0.25rem 0.5rem;
background-color: #f0f0f0;
border-radius: 4px;
font-size: 0.875rem;
}
.fs-custom-tag-remove {
margin-left: 0.5rem;
cursor: pointer;
font-weight: bold;
}
`;
document.head.appendChild(style);
// Process tags
function processFilterTags() {
const tagsContainer = document.querySelector('[fs-list-element="tags"]');
if (!tagsContainer) return;
// Clear any existing custom tags
const existingCustomTags = document.querySelectorAll('.fs-custom-tag');
existingCustomTags.forEach(tag => tag.remove());
// Get all tags
const tags = document.querySelectorAll('[fs-list-element="tag"]');
tags.forEach(tag => {
const field = tag.getAttribute('fs-list-field');
const valuesStr = tag.getAttribute('fs-list-value');
if (!valuesStr) return;
const values = valuesStr.split(',').map(v => v.trim());
// Create custom tag for each value
values.forEach(value => {
const customTag = document.createElement('div');
customTag.className = 'fs-custom-tag';
customTag.innerHTML = `
<span>${field}: ${value}</span>
<span class="fs-custom-tag-remove">Ă—</span>
`;
// Add click handler to remove individual filters
customTag.querySelector('.fs-custom-tag-remove').addEventListener('click', () => {
// Find if there are other values for this field
const remainingValues = values.filter(v => v !== value);
if (remainingValues.length > 0) {
// Update the original tag with remaining values
tag.setAttribute('fs-list-value', remainingValues.join(','));
// Simulate click on clear filters button to apply changes
const clearAllBtn = document.querySelector('[fs-list-element="clear"]');
if (clearAllBtn) {
const event = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
clearAllBtn.dispatchEvent(event);
// Re-apply remaining filters
setTimeout(() => {
remainingValues.forEach(remainingValue => {
const filterBtn = document.querySelector(`[fs-list-field="${field}"][fs-list-value*="${remainingValue}"]`);
if (filterBtn) filterBtn.click();
});
}, 100);
}
} else {
// If it's the last value, just click the original tag's remove button
const removeBtn = tag.querySelector('[fs-list-element="tag-remove"]');
if (removeBtn) removeBtn.click();
}
});
tagsContainer.appendChild(customTag);
});
});
}
// Watch for changes in the tags container
setTimeout(() => {
const tagsContainer = document.querySelector('[fs-list-element="tags"]');
if (tagsContainer) {
const observer = new MutationObserver(processFilterTags);
observer.observe(tagsContainer, { childList: true, subtree: true });
// Initial processing
processFilterTags();
}
}, 1000);
});
Thanks for the updated code. Problem is that i added it to the footer and now no tags are showing at all? Any idea whats going wrong?
-
Remove the old code from your footer
-
Add the new code above to your site’s footer (before
</body>
tag) -
Make sure you have the Finsweet V2 script properly loaded in your
<head>
-
Check tag structure: Ensure your tag template has the correct elements:
-
Main tag:
fs-list-element="tag"
-
Field name:
fs-list-element="tag-field"
-
Value:
fs-list-element="tag-value"
-
Remove button:
fs-list-element="tag-remove"
document.addEventListener('DOMContentLoaded', function() {
// Wait for Finsweet to initialize
function waitForFinsweet() {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
// Check if tags container exists (means Finsweet has loaded)
const tagsContainer = document.querySelector('[fs-list-element="tags"]');
if (tagsContainer) {
clearInterval(checkInterval);
resolve();
}
}, 100);
});
}
// Add custom CSS
const style = document.createElement('style');
style.textContent = `
[fs-list-element="tag"] {
display: none !important; /* Hide original tags */
}
.fs-custom-tag {
display: inline-flex;
align-items: center;
margin: 0.25rem;
padding: 0.25rem 0.75rem;
background-color: #e5e7eb;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 500;
color: #374151;
transition: all 0.2s ease;
}
.fs-custom-tag:hover {
background-color: #f3f4f6;
}
.fs-custom-tag-remove {
margin-left: 0.5rem;
padding: 0.125rem 0.25rem;
cursor: pointer;
font-weight: bold;
color: #6b7280;
border-radius: 3px;
transition: all 0.2s ease;
}
.fs-custom-tag-remove:hover {
background-color: #ef4444;
color: white;
}
.fs-custom-tags-container {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
margin-top: 0.5rem;
}
`;
document.head.appendChild(style);
// Main processing function
async function initializeCustomTags() {
await waitForFinsweet();
const tagsContainer = document.querySelector('[fs-list-element="tags"]');
if (!tagsContainer) {
console.log('Tags container not found');
return;
}
// Create container for custom tags
let customTagsContainer = document.querySelector('.fs-custom-tags-container');
if (!customTagsContainer) {
customTagsContainer = document.createElement('div');
customTagsContainer.className = 'fs-custom-tags-container';
tagsContainer.parentNode.insertBefore(customTagsContainer, tagsContainer.nextSibling);
}
function processFilterTags() {
// Clear existing custom tags
customTagsContainer.innerHTML = '';
// Get all active filter tags
const tags = document.querySelectorAll('[fs-list-element="tag"]');
tags.forEach(tag => {
// Get field name and values
const fieldElement = tag.querySelector('[fs-list-element="tag-field"]');
const valueElement = tag.querySelector('[fs-list-element="tag-value"]');
if (!fieldElement || !valueElement) return;
const fieldName = fieldElement.textContent.trim();
const valuesText = valueElement.textContent.trim();
// Split multiple values (in case there are comma-separated values)
const values = valuesText.split(',').map(v => v.trim()).filter(v => v);
// Create individual tags for each value
values.forEach(value => {
const customTag = document.createElement('div');
customTag.className = 'fs-custom-tag';
customTag.innerHTML = `
<span>${fieldName}: ${value}</span>
<span class="fs-custom-tag-remove" title="Remove filter">Ă—</span>
`;
// Add click handler for remove button
const removeBtn = customTag.querySelector('.fs-custom-tag-remove');
removeBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
// Find the corresponding remove button in the original tag
const originalRemoveBtn = tag.querySelector('[fs-list-element="tag-remove"]');
if (originalRemoveBtn) {
originalRemoveBtn.click();
}
});
customTagsContainer.appendChild(customTag);
});
});
}
// Initial processing
processFilterTags();
// Set up observer to watch for changes
const observer = new MutationObserver((mutations) => {
let shouldUpdate = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
// Check if tags were added or removed
const addedNodes = Array.from(mutation.addedNodes);
const removedNodes = Array.from(mutation.removedNodes);
if (addedNodes.some(node => node.nodeType === 1 && node.hasAttribute && node.hasAttribute('fs-list-element')) ||
removedNodes.some(node => node.nodeType === 1 && node.hasAttribute && node.hasAttribute('fs-list-element'))) {
shouldUpdate = true;
}
} else if (mutation.type === 'attributes' && mutation.attributeName && mutation.attributeName.startsWith('fs-list-')) {
shouldUpdate = true;
}
});
if (shouldUpdate) {
// Small delay to ensure DOM is fully updated
setTimeout(processFilterTags, 50);
}
});
// Observe the tags container and its parent for changes
observer.observe(tagsContainer, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['fs-list-element', 'fs-list-field', 'fs-list-value']
});
// Also observe the parent container in case tags are added/removed at a higher level
if (tagsContainer.parentNode) {
observer.observe(tagsContainer.parentNode, {
childList: true,
subtree: true
});
}
console.log('Custom filter tags initialized successfully');
}
// Initialize when DOM is ready
initializeCustomTags().catch(console.error);
});