UI Components Medium
Advanced Filters
An advanced filter panel for data tables with multiple filter types — text search, multi-select, date range, number range, and boolean toggles. Supports saving filter presets.
Open in Lab
MCP
css vanilla-js
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0f172a;
color: #f1f5f9;
min-height: 100vh;
padding: 1.5rem;
}
/* ── Layout ── */
.app-wrapper {
max-width: 1100px;
margin: 0 auto;
display: flex;
gap: 1.5rem;
align-items: flex-start;
}
/* ── Sidebar ── */
.filter-sidebar {
width: 280px;
flex-shrink: 0;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 1rem;
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
position: sticky;
top: 1.5rem;
}
.filter-sidebar.hidden {
display: none;
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.sidebar-title-row {
display: flex;
align-items: center;
gap: 0.5rem;
}
.sidebar-title {
font-size: 1rem;
font-weight: 700;
color: #f1f5f9;
}
.filter-count-badge {
background: #38bdf8;
color: #0f172a;
font-size: 0.6875rem;
font-weight: 700;
min-width: 1.25rem;
height: 1.25rem;
padding: 0 0.3rem;
border-radius: 999px;
display: grid;
place-items: center;
line-height: 1;
transition: opacity 0.15s ease;
}
.filter-count-badge[data-count="0"] {
opacity: 0;
}
.clear-all-btn {
font-size: 0.8125rem;
color: #64748b;
background: none;
border: none;
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 0.375rem;
transition: color 0.15s ease, background 0.15s ease;
}
.clear-all-btn:hover {
color: #f87171;
background: rgba(248, 113, 113, 0.08);
}
/* ── Filter groups ── */
.filter-group {
display: flex;
flex-direction: column;
gap: 0.625rem;
padding-bottom: 1.25rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.filter-group:last-child {
border-bottom: none;
padding-bottom: 0;
}
.filter-group-label {
font-size: 0.75rem;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.filter-input {
width: 100%;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.5rem;
color: #e2e8f0;
font-size: 0.875rem;
padding: 0.5rem 0.75rem;
font-family: inherit;
transition: border-color 0.15s ease, background 0.15s ease;
}
.filter-input:focus {
outline: none;
border-color: rgba(56, 189, 248, 0.5);
background: rgba(56, 189, 248, 0.05);
}
.filter-input::placeholder {
color: #475569;
}
input[type="date"].filter-input::-webkit-calendar-picker-indicator {
filter: invert(0.6);
cursor: pointer;
}
.search-input-wrapper {
position: relative;
}
.search-icon {
position: absolute;
left: 0.625rem;
top: 50%;
transform: translateY(-50%);
color: #475569;
pointer-events: none;
}
.search-input-wrapper .filter-input {
padding-left: 2rem;
}
/* ── Checkboxes ── */
.checkbox-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.checkbox-item {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: #94a3b8;
cursor: pointer;
padding: 0.25rem 0;
transition: color 0.12s ease;
}
.checkbox-item:hover {
color: #e2e8f0;
}
.checkbox-item input[type="checkbox"] {
width: 1rem;
height: 1rem;
accent-color: #38bdf8;
cursor: pointer;
flex-shrink: 0;
}
/* ── Toggle buttons ── */
.toggle-group {
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
}
.toggle-btn {
font-size: 0.8125rem;
font-weight: 500;
padding: 0.3rem 0.7rem;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.03);
color: #64748b;
cursor: pointer;
transition: all 0.15s ease;
}
.toggle-btn:hover {
color: #e2e8f0;
border-color: rgba(255, 255, 255, 0.2);
}
.toggle-btn.active {
background: rgba(56, 189, 248, 0.15);
border-color: rgba(56, 189, 248, 0.4);
color: #38bdf8;
}
/* ── Range inputs ── */
.range-inputs {
display: flex;
align-items: center;
gap: 0.5rem;
}
.range-input {
flex: 1;
min-width: 0;
}
.date-input {
flex: 1;
min-width: 0;
}
.range-sep {
color: #475569;
font-size: 0.875rem;
flex-shrink: 0;
}
/* ── Preset row ── */
.preset-dropdown-row {
display: flex;
gap: 0.5rem;
}
.preset-select {
flex: 1;
min-width: 0;
}
.save-preset-btn {
display: flex;
align-items: center;
gap: 0.35rem;
padding: 0.5rem 0.75rem;
background: rgba(56, 189, 248, 0.1);
border: 1px solid rgba(56, 189, 248, 0.25);
border-radius: 0.5rem;
color: #38bdf8;
font-size: 0.8125rem;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s ease, border-color 0.15s ease;
}
.save-preset-btn:hover {
background: rgba(56, 189, 248, 0.18);
border-color: rgba(56, 189, 248, 0.4);
}
/* ── Results area ── */
.results-area {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 1rem;
}
.results-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.results-count {
font-size: 0.9375rem;
font-weight: 600;
color: #94a3b8;
}
.toggle-sidebar-btn {
display: none;
align-items: center;
gap: 0.4rem;
padding: 0.45rem 0.85rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.5rem;
color: #94a3b8;
font-size: 0.875rem;
cursor: pointer;
transition: background 0.15s ease;
}
.toggle-sidebar-btn:hover {
background: rgba(255, 255, 255, 0.08);
color: #e2e8f0;
}
/* ── Chips ── */
.active-chips {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
min-height: 0;
}
.chip {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.3rem 0.7rem;
background: rgba(56, 189, 248, 0.12);
border: 1px solid rgba(56, 189, 248, 0.25);
border-radius: 999px;
font-size: 0.8125rem;
color: #7dd3fc;
font-weight: 500;
}
.chip-remove {
display: flex;
align-items: center;
justify-content: center;
width: 1rem;
height: 1rem;
background: none;
border: none;
color: #38bdf8;
cursor: pointer;
border-radius: 50%;
padding: 0;
line-height: 1;
transition: background 0.12s ease, color 0.12s ease;
}
.chip-remove:hover {
background: rgba(248, 113, 113, 0.15);
color: #f87171;
}
/* ── Employee list ── */
.employee-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.employee-card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 0.875rem;
padding: 1rem 1.25rem;
display: grid;
grid-template-columns: 2.5rem 1fr auto auto;
align-items: center;
gap: 1rem;
transition: border-color 0.15s ease, background 0.15s ease;
}
.employee-card:hover {
border-color: rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.04);
}
.employee-avatar {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
display: grid;
place-items: center;
font-size: 0.875rem;
font-weight: 700;
color: #0f172a;
flex-shrink: 0;
}
.employee-info {
display: flex;
flex-direction: column;
gap: 0.2rem;
min-width: 0;
}
.employee-name {
font-size: 0.9375rem;
font-weight: 600;
color: #f1f5f9;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.employee-meta {
font-size: 0.8125rem;
color: #64748b;
}
.employee-salary {
font-size: 0.9375rem;
font-weight: 600;
color: #94a3b8;
white-space: nowrap;
}
.status-badge {
font-size: 0.75rem;
font-weight: 600;
padding: 0.2rem 0.6rem;
border-radius: 999px;
white-space: nowrap;
}
.status-active {
background: rgba(52, 211, 153, 0.15);
color: #34d399;
}
.status-inactive {
background: rgba(248, 113, 113, 0.12);
color: #f87171;
}
.status-leave {
background: rgba(251, 191, 36, 0.12);
color: #fbbf24;
}
.no-results {
text-align: center;
padding: 3rem 1rem;
color: #475569;
font-size: 0.9375rem;
}
/* ── Modal ── */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
display: grid;
place-items: center;
z-index: 100;
}
.modal-overlay[hidden] {
display: none;
}
.modal-card {
background: #1e293b;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 1rem;
padding: 1.5rem;
width: min(380px, calc(100vw - 2rem));
display: flex;
flex-direction: column;
gap: 1rem;
}
.modal-title {
font-size: 1.0625rem;
font-weight: 700;
color: #f1f5f9;
}
.modal-actions {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
}
.modal-cancel {
padding: 0.5rem 1rem;
background: none;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 0.5rem;
color: #94a3b8;
font-size: 0.875rem;
cursor: pointer;
font-family: inherit;
transition: background 0.15s ease;
}
.modal-cancel:hover {
background: rgba(255, 255, 255, 0.05);
}
.modal-save {
padding: 0.5rem 1.125rem;
background: #38bdf8;
border: none;
border-radius: 0.5rem;
color: #0f172a;
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
font-family: inherit;
transition: background 0.15s ease;
}
.modal-save:hover {
background: #7dd3fc;
}
/* ── Responsive ── */
@media (max-width: 700px) {
.app-wrapper {
flex-direction: column;
}
.filter-sidebar {
width: 100%;
position: static;
}
.toggle-sidebar-btn {
display: flex;
}
}
@media (prefers-reduced-motion: reduce) {
.employee-card,
.toggle-btn,
.chip-remove {
transition: none;
}
}(function () {
"use strict";
// ── Data ──
const EMPLOYEES = [
{
id: 1,
name: "Alice Martin",
dept: "Engineering",
salary: 120000,
status: "Active",
startDate: "2021-03-15",
initials: "AM",
color: "#38bdf8",
},
{
id: 2,
name: "Bob Kowalski",
dept: "Design",
salary: 95000,
status: "Active",
startDate: "2022-07-01",
initials: "BK",
color: "#818cf8",
},
{
id: 3,
name: "Carol Schmidt",
dept: "Marketing",
salary: 78000,
status: "Inactive",
startDate: "2020-11-20",
initials: "CS",
color: "#f472b6",
},
{
id: 4,
name: "David Reyes",
dept: "Sales",
salary: 88000,
status: "Active",
startDate: "2023-01-10",
initials: "DR",
color: "#34d399",
},
{
id: 5,
name: "Emma Johnson",
dept: "Engineering",
salary: 135000,
status: "Active",
startDate: "2019-06-05",
initials: "EJ",
color: "#fb923c",
},
{
id: 6,
name: "Frank Lee",
dept: "HR",
salary: 72000,
status: "On Leave",
startDate: "2021-09-30",
initials: "FL",
color: "#fbbf24",
},
{
id: 7,
name: "Grace Park",
dept: "Engineering",
salary: 110000,
status: "Active",
startDate: "2022-04-18",
initials: "GP",
color: "#a78bfa",
},
{
id: 8,
name: "Henry Wilson",
dept: "Sales",
salary: 82000,
status: "Inactive",
startDate: "2020-08-22",
initials: "HW",
color: "#2dd4bf",
},
{
id: 9,
name: "Iris Thompson",
dept: "Design",
salary: 91000,
status: "Active",
startDate: "2023-05-14",
initials: "IT",
color: "#e879f9",
},
{
id: 10,
name: "Jake Novak",
dept: "Marketing",
salary: 76000,
status: "Active",
startDate: "2021-12-03",
initials: "JN",
color: "#60a5fa",
},
];
const DEFAULT_PRESETS = [
{
name: "Q1 Report",
state: {
search: "",
depts: ["Engineering", "Sales"],
status: "Active",
salaryMin: "",
salaryMax: "",
dateFrom: "",
dateTo: "",
},
},
{
name: "Engineering Only",
state: {
search: "",
depts: ["Engineering"],
status: "all",
salaryMin: "",
salaryMax: "",
dateFrom: "",
dateTo: "",
},
},
];
// ── State ──
let activeState = {
search: "",
depts: [],
status: "all",
salaryMin: "",
salaryMax: "",
dateFrom: "",
dateTo: "",
};
// ── DOM refs ──
const searchInput = document.getElementById("searchInput");
const deptChecks = document.querySelectorAll(".dept-check");
const statusGroup = document.getElementById("statusGroup");
const salaryMin = document.getElementById("salaryMin");
const salaryMax = document.getElementById("salaryMax");
const dateFrom = document.getElementById("dateFrom");
const dateTo = document.getElementById("dateTo");
const clearAllBtn = document.getElementById("clearAllBtn");
const resultsCount = document.getElementById("resultsCount");
const filterCountBadge = document.getElementById("filterCountBadge");
const activeChips = document.getElementById("activeChips");
const employeeList = document.getElementById("employeeList");
const presetSelect = document.getElementById("presetSelect");
const savePresetBtn = document.getElementById("savePresetBtn");
const presetModal = document.getElementById("presetModal");
const presetNameInput = document.getElementById("presetNameInput");
const modalCancel = document.getElementById("modalCancel");
const modalSave = document.getElementById("modalSave");
const toggleSidebarBtn = document.getElementById("toggleSidebarBtn");
const filterSidebar = document.getElementById("filterSidebar");
if (!employeeList) return;
// ── localStorage presets ──
const STORAGE_KEY = "advanced-filters-presets";
function loadPresets() {
try {
const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) || "null");
return Array.isArray(stored) ? stored : [...DEFAULT_PRESETS];
} catch {
return [...DEFAULT_PRESETS];
}
}
function savePresets(presets) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(presets));
} catch {}
}
let presets = loadPresets();
function renderPresetSelect() {
const current = presetSelect.value;
presetSelect.innerHTML = `<option value="">Load a preset…</option>`;
presets.forEach((p, i) => {
const opt = document.createElement("option");
opt.value = i;
opt.textContent = p.name;
presetSelect.appendChild(opt);
});
presetSelect.value = current;
}
renderPresetSelect();
// ── Filtering ──
function getFilteredEmployees() {
return EMPLOYEES.filter((emp) => {
if (activeState.search && !emp.name.toLowerCase().includes(activeState.search.toLowerCase()))
return false;
if (activeState.depts.length > 0 && !activeState.depts.includes(emp.dept)) return false;
if (activeState.status !== "all" && emp.status !== activeState.status) return false;
if (activeState.salaryMin !== "" && emp.salary < Number(activeState.salaryMin)) return false;
if (activeState.salaryMax !== "" && emp.salary > Number(activeState.salaryMax)) return false;
if (activeState.dateFrom && emp.startDate < activeState.dateFrom) return false;
if (activeState.dateTo && emp.startDate > activeState.dateTo) return false;
return true;
});
}
function countActiveFilters() {
let count = 0;
if (activeState.search) count++;
if (activeState.depts.length > 0) count++;
if (activeState.status !== "all") count++;
if (activeState.salaryMin || activeState.salaryMax) count++;
if (activeState.dateFrom || activeState.dateTo) count++;
return count;
}
// ── Render employee list ──
function renderEmployees(filtered) {
if (filtered.length === 0) {
employeeList.innerHTML = `<div class="no-results">No employees match the current filters.</div>`;
return;
}
employeeList.innerHTML = filtered
.map((emp) => {
const statusClass =
emp.status === "Active"
? "status-active"
: emp.status === "Inactive"
? "status-inactive"
: "status-leave";
const salary = emp.salary.toLocaleString("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
});
const date = new Date(emp.startDate + "T00:00:00").toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
return `
<div class="employee-card">
<div class="employee-avatar" style="background: ${emp.color}20; color: ${emp.color}; border: 1px solid ${emp.color}30">${emp.initials}</div>
<div class="employee-info">
<div class="employee-name">${emp.name}</div>
<div class="employee-meta">${emp.dept} · Joined ${date}</div>
</div>
<div class="employee-salary">${salary}</div>
<span class="status-badge ${statusClass}">${emp.status}</span>
</div>
`;
})
.join("");
}
// ── Render chips ──
function renderChips() {
const chips = [];
if (activeState.search) {
chips.push({
label: `Search: "${activeState.search}"`,
remove: () => {
activeState.search = "";
searchInput.value = "";
},
});
}
if (activeState.depts.length > 0) {
chips.push({
label: `Dept: ${activeState.depts.join(", ")}`,
remove: () => {
activeState.depts = [];
deptChecks.forEach((c) => {
c.checked = false;
});
},
});
}
if (activeState.status !== "all") {
chips.push({
label: `Status: ${activeState.status}`,
remove: () => {
activeState.status = "all";
statusGroup
.querySelectorAll(".toggle-btn")
.forEach((b) => b.classList.toggle("active", b.dataset.value === "all"));
},
});
}
if (activeState.salaryMin || activeState.salaryMax) {
const min = activeState.salaryMin
? `$${Number(activeState.salaryMin).toLocaleString()}`
: "Any";
const max = activeState.salaryMax
? `$${Number(activeState.salaryMax).toLocaleString()}`
: "Any";
chips.push({
label: `Salary: ${min} – ${max}`,
remove: () => {
activeState.salaryMin = "";
activeState.salaryMax = "";
salaryMin.value = "";
salaryMax.value = "";
},
});
}
if (activeState.dateFrom || activeState.dateTo) {
const from = activeState.dateFrom || "Any";
const to = activeState.dateTo || "Any";
chips.push({
label: `Date: ${from} – ${to}`,
remove: () => {
activeState.dateFrom = "";
activeState.dateTo = "";
dateFrom.value = "";
dateTo.value = "";
},
});
}
activeChips.innerHTML = chips
.map(
(chip, i) => `
<span class="chip">
${chip.label}
<button class="chip-remove" data-index="${i}" aria-label="Remove filter">×</button>
</span>
`
)
.join("");
activeChips.querySelectorAll(".chip-remove").forEach((btn) => {
btn.addEventListener("click", () => {
chips[Number(btn.dataset.index)].remove();
applyFilters();
});
});
}
function applyFilters() {
const filtered = getFilteredEmployees();
renderEmployees(filtered);
renderChips();
const count = countActiveFilters();
filterCountBadge.textContent = count;
filterCountBadge.dataset.count = count;
resultsCount.textContent = `Showing ${filtered.length} of ${EMPLOYEES.length} employees`;
}
// ── Sync inputs to state ──
searchInput.addEventListener("input", () => {
activeState.search = searchInput.value;
applyFilters();
});
deptChecks.forEach((cb) => {
cb.addEventListener("change", () => {
activeState.depts = Array.from(deptChecks)
.filter((c) => c.checked)
.map((c) => c.value);
applyFilters();
});
});
statusGroup.addEventListener("click", (e) => {
const btn = e.target.closest(".toggle-btn");
if (!btn) return;
statusGroup.querySelectorAll(".toggle-btn").forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
activeState.status = btn.dataset.value;
applyFilters();
});
salaryMin.addEventListener("input", () => {
activeState.salaryMin = salaryMin.value;
applyFilters();
});
salaryMax.addEventListener("input", () => {
activeState.salaryMax = salaryMax.value;
applyFilters();
});
dateFrom.addEventListener("change", () => {
activeState.dateFrom = dateFrom.value;
applyFilters();
});
dateTo.addEventListener("change", () => {
activeState.dateTo = dateTo.value;
applyFilters();
});
// ── Clear all ──
clearAllBtn.addEventListener("click", () => {
activeState = {
search: "",
depts: [],
status: "all",
salaryMin: "",
salaryMax: "",
dateFrom: "",
dateTo: "",
};
searchInput.value = "";
deptChecks.forEach((c) => {
c.checked = false;
});
statusGroup
.querySelectorAll(".toggle-btn")
.forEach((b) => b.classList.toggle("active", b.dataset.value === "all"));
salaryMin.value = "";
salaryMax.value = "";
dateFrom.value = "";
dateTo.value = "";
presetSelect.value = "";
applyFilters();
});
// ── Presets ──
presetSelect.addEventListener("change", () => {
const idx = presetSelect.value;
if (idx === "") return;
const preset = presets[Number(idx)];
if (!preset) return;
activeState = { ...preset.state };
// Sync UI to loaded state
searchInput.value = activeState.search;
deptChecks.forEach((c) => {
c.checked = activeState.depts.includes(c.value);
});
statusGroup
.querySelectorAll(".toggle-btn")
.forEach((b) => b.classList.toggle("active", b.dataset.value === activeState.status));
salaryMin.value = activeState.salaryMin;
salaryMax.value = activeState.salaryMax;
dateFrom.value = activeState.dateFrom;
dateTo.value = activeState.dateTo;
applyFilters();
});
savePresetBtn.addEventListener("click", () => {
presetNameInput.value = "";
presetModal.hidden = false;
presetNameInput.focus();
});
modalCancel.addEventListener("click", () => {
presetModal.hidden = true;
});
modalSave.addEventListener("click", () => {
const name = presetNameInput.value.trim();
if (!name) {
presetNameInput.focus();
return;
}
presets.push({ name, state: { ...activeState } });
savePresets(presets);
renderPresetSelect();
presetModal.hidden = true;
});
presetModal.addEventListener("click", (e) => {
if (e.target === presetModal) presetModal.hidden = true;
});
// ── Sidebar toggle (mobile) ──
toggleSidebarBtn.addEventListener("click", () => {
filterSidebar.classList.toggle("hidden");
});
// ── Init ──
applyFilters();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Advanced Filters</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="app-wrapper">
<!-- Sidebar Filters -->
<aside class="filter-sidebar" id="filterSidebar">
<div class="sidebar-header">
<div class="sidebar-title-row">
<h2 class="sidebar-title">Filters</h2>
<span class="filter-count-badge" id="filterCountBadge" aria-live="polite">0</span>
</div>
<button class="clear-all-btn" id="clearAllBtn">Clear all</button>
</div>
<!-- Search -->
<div class="filter-group">
<label class="filter-group-label" for="searchInput">Search</label>
<div class="search-input-wrapper">
<svg class="search-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
</svg>
<input type="text" id="searchInput" class="filter-input" placeholder="Search by name…" autocomplete="off" />
</div>
</div>
<!-- Department -->
<div class="filter-group">
<label class="filter-group-label">Department</label>
<div class="checkbox-list">
<label class="checkbox-item">
<input type="checkbox" value="Engineering" class="dept-check" /> Engineering
</label>
<label class="checkbox-item">
<input type="checkbox" value="Design" class="dept-check" /> Design
</label>
<label class="checkbox-item">
<input type="checkbox" value="Marketing" class="dept-check" /> Marketing
</label>
<label class="checkbox-item">
<input type="checkbox" value="Sales" class="dept-check" /> Sales
</label>
<label class="checkbox-item">
<input type="checkbox" value="HR" class="dept-check" /> HR
</label>
</div>
</div>
<!-- Status -->
<div class="filter-group">
<label class="filter-group-label">Status</label>
<div class="toggle-group" id="statusGroup">
<button class="toggle-btn active" data-value="all">All</button>
<button class="toggle-btn" data-value="Active">Active</button>
<button class="toggle-btn" data-value="Inactive">Inactive</button>
<button class="toggle-btn" data-value="On Leave">On Leave</button>
</div>
</div>
<!-- Salary Range -->
<div class="filter-group">
<label class="filter-group-label">Salary Range</label>
<div class="range-inputs">
<input type="number" id="salaryMin" class="filter-input range-input" placeholder="Min ($)" min="0" step="5000" />
<span class="range-sep">–</span>
<input type="number" id="salaryMax" class="filter-input range-input" placeholder="Max ($)" min="0" step="5000" />
</div>
</div>
<!-- Start Date Range -->
<div class="filter-group">
<label class="filter-group-label">Start Date</label>
<div class="range-inputs">
<input type="date" id="dateFrom" class="filter-input date-input" />
<span class="range-sep">–</span>
<input type="date" id="dateTo" class="filter-input date-input" />
</div>
</div>
<!-- Presets -->
<div class="filter-group preset-group">
<label class="filter-group-label">Saved Presets</label>
<div class="preset-dropdown-row">
<select id="presetSelect" class="filter-input preset-select">
<option value="">Load a preset…</option>
</select>
<button class="save-preset-btn" id="savePresetBtn" title="Save current filters as preset">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
<polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/>
</svg>
Save
</button>
</div>
</div>
</aside>
<!-- Main content -->
<main class="results-area">
<div class="results-header">
<div class="results-count" id="resultsCount">Showing 10 of 10 employees</div>
<button class="toggle-sidebar-btn" id="toggleSidebarBtn" aria-label="Toggle filters">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="14" y2="12"/>
<line x1="4" y1="18" x2="18" y2="18"/>
</svg>
Filters
</button>
</div>
<!-- Active filter chips -->
<div class="active-chips" id="activeChips" aria-live="polite"></div>
<!-- Employee list -->
<div class="employee-list" id="employeeList"></div>
</main>
</div>
<!-- Save preset modal -->
<div class="modal-overlay" id="presetModal" aria-modal="true" role="dialog" aria-label="Save filter preset" hidden>
<div class="modal-card">
<h3 class="modal-title">Save Filter Preset</h3>
<input type="text" id="presetNameInput" class="filter-input" placeholder="Preset name (e.g. Q1 Report)" />
<div class="modal-actions">
<button class="modal-cancel" id="modalCancel">Cancel</button>
<button class="modal-save" id="modalSave">Save Preset</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Advanced Filters
A production-ready filter panel that sits alongside a data table and supports multiple simultaneous filter types. Applied filters are shown as removable chips above the results, and any combination can be saved as a named preset to localStorage for quick reuse.
Features
- Text search, multi-select checkboxes, status toggles, salary range, and date range filters
- Active filter count badge on the panel header
- Removable filter chips rendered above the result list
- Save and load named filter presets via localStorage
- Real-time filtering — no submit button required