Filter chips
Category: Forms · Tags: filter, chips, checkbox, multi-select, fieldset
How to use it
Use for a handful of independent filters that can be combined, above a list of results. For one choice out of several use radio buttons or a select; for dozens of options use a searchable list instead. Change the legend, name and values, apply the filters in the change handler, and keep the active-count wording in your own language.
Accessibility
Each chip is a real <input type="checkbox"> wrapped in its <label>, all inside a <fieldset> with a <legend>, so Tab reaches every chip, Space toggles it and screen readers announce it as a checked or unchecked checkbox in the named group. The visible checkbox stays inside the chip, so the state never relies on colour alone, and a polite role="status" line announces the number of active filters after each change or after Clear all. Chip text is #0f172a on white (17.85:1, 14.48:1 on #e2e8f0 hovered) and white on #1d4ed8 when active (6.70:1); the focused chip and the Clear button show a 2px #1d4ed8 outline, drawn outside the chip so it is visible on selected chips too.
Source (copy-ready)
<fieldset class="dcl-fchips">
<legend class="dcl-fchips__legend">Filter recipes</legend>
<div class="dcl-fchips__list">
<label class="dcl-fchips__chip dcl-fchips__chip--on"><input class="dcl-fchips__input" type="checkbox" name="filter" value="vegetarian" checked>Vegetarian</label>
<label class="dcl-fchips__chip"><input class="dcl-fchips__input" type="checkbox" name="filter" value="vegan">Vegan</label>
<label class="dcl-fchips__chip"><input class="dcl-fchips__input" type="checkbox" name="filter" value="gluten-free">Gluten-free</label>
<label class="dcl-fchips__chip"><input class="dcl-fchips__input" type="checkbox" name="filter" value="under-30">Under 30 minutes</label>
<label class="dcl-fchips__chip"><input class="dcl-fchips__input" type="checkbox" name="filter" value="one-pot">One pot</label>
<label class="dcl-fchips__chip"><input class="dcl-fchips__input" type="checkbox" name="filter" value="freezer">Freezer friendly</label>
</div>
<div class="dcl-fchips__footer">
<p class="dcl-fchips__status" role="status">1 filter active</p>
<button type="button" class="dcl-fchips__clear">Clear all</button>
</div>
</fieldset>
<style>
.dcl-fchips { max-width: 36rem; min-width: 0; margin: 0; padding: 0.75rem 1rem 1rem; border: 1px solid #cbd5e1; border-radius: 0.5rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-fchips__legend { padding: 0 0.35rem; font-weight: 700; color: #0f172a; background-color: #ffffff; }
.dcl-fchips__list { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.dcl-fchips__chip { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.35rem 0.8rem 0.35rem 0.55rem; border: 1px solid #64748b; border-radius: 999px; cursor: pointer; color: #0f172a; background-color: #ffffff; }
.dcl-fchips__chip:hover { color: #0f172a; background-color: #e2e8f0; }
.dcl-fchips__chip.dcl-fchips__chip--on { border-color: #1d4ed8; font-weight: 600; color: #ffffff; background-color: #1d4ed8; }
.dcl-fchips__input { width: 1rem; height: 1rem; margin: 0; accent-color: #0f172a; }
.dcl-fchips__chip:focus-within { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-fchips__footer { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: 0.75rem; }
.dcl-fchips__status { margin: 0; font-size: 0.9rem; color: #475569; background-color: #ffffff; }
.dcl-fchips__clear { padding: 0.3rem 0.7rem; border: 1px solid #1d4ed8; border-radius: 6px; font: inherit; cursor: pointer; color: #1d4ed8; background-color: #ffffff; }
.dcl-fchips__clear:hover { color: #1e40af; background-color: #eff6ff; }
.dcl-fchips__clear:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
</style>
<script>
(function () {
document.querySelectorAll(".dcl-fchips").forEach(function (root) {
if (root.dataset.dclBound) { return; }
root.dataset.dclBound = "true";
var inputs = root.querySelectorAll(".dcl-fchips__input");
var status = root.querySelector(".dcl-fchips__status");
var clear = root.querySelector(".dcl-fchips__clear");
function update() {
var active = 0;
inputs.forEach(function (input) {
var chip = input.closest(".dcl-fchips__chip");
if (chip) { chip.classList.toggle("dcl-fchips__chip--on", input.checked); }
if (input.checked) { active += 1; }
});
if (status) {
status.textContent = active === 0 ? "No filters active" : active + (active === 1 ? " filter active" : " filters active");
}
}
inputs.forEach(function (input) { input.addEventListener("change", update); });
if (clear) {
clear.addEventListener("click", function () {
inputs.forEach(function (input) { input.checked = false; });
update();
});
}
update();
});
})();
</script>
Was this useful? Send feedback.