Tailwind Css
Sidebar
Filtre
Catalogue
Transitions
Utility First
Tags
Responsive
Html
Snippet
Sidebar de filtres Tailwind CSS avec transitions fluides, design utility-first, tags actifs supprimables et animation collapse native.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="copyright" content="AngularForAll" />
<meta name="author" content="AngularForAll" />
<meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="public, max-age=604800" />
<title>Snippets Sidebarfilter Tailwindcss 2026 05011544 | AngularForAll</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Configuration -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: {
50: '#eef2ff',
100: '#e0e7ff',
500: '#6366f1',
600: '#4f46e5',
700: '#4338ca',
}
}
}
}
}
</script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body class="bg-gray-50 min-h-screen">
<div class="flex flex-col lg:flex-row min-h-screen">
<!-- Mobile Toggle -->
<div class="lg:hidden p-4 bg-white border-b sticky top-0 z-40">
<button id="mobileFilterToggle"
class="w-full flex items-center justify-center gap-2 bg-brand-500 text-white px-4 py-3 rounded-lg font-medium hover:bg-brand-600 transition-colors">
<i class="fas fa-filter"></i>
Filtres
<span id="mobileFilterCount" class="bg-white text-brand-500 px-2 py-0.5 rounded-full text-xs font-bold">3</span>
</button>
</div>
<!-- Overlay Mobile -->
<div id="sidebarOverlay"
class="fixed inset-0 bg-black/50 z-40 hidden lg:hidden transition-opacity duration-300">
</div>
<!-- Sidebar -->
<aside id="sidebar"
class="fixed lg:sticky top-0 left-0 z-50 lg:z-0 w-80 h-full bg-white border-r border-gray-200 shadow-xl lg:shadow-none transform -translate-x-full lg:translate-x-0 transition-transform duration-300 overflow-y-auto flex-shrink-0">
<!-- Header -->
<div class="sticky top-0 bg-white z-10 px-5 py-4 border-b border-gray-200 flex items-center justify-between">
<h2 class="text-lg font-bold text-gray-800 flex items-center gap-2">
<i class="fas fa-filter text-brand-500"></i>
Filtres
</h2>
<div class="flex items-center gap-2">
<button id="clearAllBtn" class="text-sm text-brand-500 hover:text-brand-700 font-medium">
Effacer
</button>
<button id="closeSidebarBtn" class="lg:hidden text-gray-400 hover:text-gray-600" aria-label="Fermer les filtres">
<i class="fas fa-times text-xl"></i>
</button>
</div>
</div>
<!-- Active Filters -->
<div id="activeFilterTags" class="px-5 py-3 border-b border-gray-100 flex flex-wrap gap-2">
<!-- Dynamic tags -->
</div>
<!-- Filter Sections -->
<div class="divide-y divide-gray-100">
<!-- Categories -->
<div class="filter-section">
<button class="w-full px-5 py-3 flex items-center justify-between hover:bg-gray-50 transition-colors"
onclick="toggleFilterSection(this)">
<span class="font-semibold text-gray-700 text-sm flex items-center gap-2">
<i class="fas fa-th-large text-brand-500 w-4"></i>
Catégories
</span>
<i class="fas fa-chevron-down text-gray-400 text-xs transition-transform duration-300"></i>
</button>
<div class="filter-content px-5 pb-4">
<div class="space-y-1">
<button class="w-full flex items-center justify-between px-3 py-2 rounded-lg bg-brand-50 text-brand-700 font-medium text-sm transition-colors" data-category="all">
Tout
<span class="bg-brand-500 text-white text-xs px-2 py-0.5 rounded-full">120</span>
</button>
<button class="w-full flex items-center justify-between px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 text-sm transition-colors" data-category="electronics">
Électronique
<span class="text-gray-400 text-xs">34</span>
</button>
<button class="w-full flex items-center justify-between px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 text-sm transition-colors" data-category="clothing">
Vêtements
<span class="text-gray-400 text-xs">28</span>
</button>
<button class="w-full flex items-center justify-between px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 text-sm transition-colors" data-category="home">
Maison
<span class="text-gray-400 text-xs">22</span>
</button>
<button class="w-full flex items-center justify-between px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 text-sm transition-colors" data-category="sports">
Sports
<span class="text-gray-400 text-xs">18</span>
</button>
</div>
</div>
</div>
<!-- Price Range -->
<div class="filter-section">
<button class="w-full px-5 py-3 flex items-center justify-between hover:bg-gray-50 transition-colors"
onclick="toggleFilterSection(this)">
<span class="font-semibold text-gray-700 text-sm flex items-center gap-2">
<i class="fas fa-euro-sign text-brand-500 w-4"></i>
Prix
</span>
<i class="fas fa-chevron-down text-gray-400 text-xs transition-transform duration-300"></i>
</button>
<div class="filter-content px-5 pb-4">
<div class="flex items-center gap-2 mb-3">
<input type="number" id="priceMin" placeholder="Min" value="0"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-brand-500 focus:border-brand-500 outline-none">
<span class="text-gray-400">—</span>
<input type="number" id="priceMax" placeholder="Max" value="1000"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-brand-500 focus:border-brand-500 outline-none">
</div>
<button onclick="applyPriceFilter()"
class="w-full bg-brand-500 text-white py-2 rounded-lg text-sm font-medium hover:bg-brand-600 transition-colors">
Appliquer
</button>
</div>
</div>
<!-- Ratings -->
<div class="filter-section">
<button class="w-full px-5 py-3 flex items-center justify-between hover:bg-gray-50 transition-colors"
onclick="toggleFilterSection(this)">
<span class="font-semibold text-gray-700 text-sm flex items-center gap-2">
<i class="fas fa-star text-brand-500 w-4"></i>
Notes
</span>
<i class="fas fa-chevron-down text-gray-400 text-xs transition-transform duration-300"></i>
</button>
<div class="filter-content px-5 pb-4 hidden">
<label class="flex items-center gap-2 py-2 cursor-pointer hover:text-brand-600 transition-colors">
<input type="checkbox" value="4" class="rating-checkbox w-4 h-4 text-brand-500 rounded focus:ring-brand-500">
<span class="text-yellow-400">★★★★</span><span class="text-gray-300">☆</span>
<span class="text-sm text-gray-600">& plus</span>
</label>
<label class="flex items-center gap-2 py-2 cursor-pointer hover:text-brand-600 transition-colors">
<input type="checkbox" value="3" class="rating-checkbox w-4 h-4 text-brand-500 rounded focus:ring-brand-500">
<span class="text-yellow-400">★★★</span><span class="text-gray-300">☆☆</span>
<span class="text-sm text-gray-600">& plus</span>
</label>
<label class="flex items-center gap-2 py-2 cursor-pointer hover:text-brand-600 transition-colors">
<input type="checkbox" value="2" class="rating-checkbox w-4 h-4 text-brand-500 rounded focus:ring-brand-500">
<span class="text-yellow-400">★★</span><span class="text-gray-300">☆☆☆</span>
<span class="text-sm text-gray-600">& plus</span>
</label>
</div>
</div>
<!-- Colors -->
<div class="filter-section">
<button class="w-full px-5 py-3 flex items-center justify-between hover:bg-gray-50 transition-colors"
onclick="toggleFilterSection(this)">
<span class="font-semibold text-gray-700 text-sm flex items-center gap-2">
<i class="fas fa-palette text-brand-500 w-4"></i>
Couleurs
</span>
<i class="fas fa-chevron-down text-gray-400 text-xs transition-transform duration-300"></i>
</button>
<div class="filter-content px-5 pb-4 hidden">
<div class="flex flex-wrap gap-2">
<button class="color-btn w-9 h-9 rounded-full border-2 border-gray-200 hover:scale-110 transition-transform bg-gray-900" data-color="noir" title="Noir"></button>
<button class="color-btn w-9 h-9 rounded-full border-2 border-gray-300 hover:scale-110 transition-transform bg-gray-100" data-color="blanc" title="Blanc"></button>
<button class="color-btn w-9 h-9 rounded-full border-2 border-gray-200 hover:scale-110 transition-transform bg-red-600" data-color="rouge" title="Rouge"></button>
<button class="color-btn w-9 h-9 rounded-full border-2 border-gray-200 hover:scale-110 transition-transform bg-blue-600" data-color="bleu" title="Bleu"></button>
<button class="color-btn w-9 h-9 rounded-full border-2 border-gray-200 hover:scale-110 transition-transform bg-green-600" data-color="vert" title="Vert"></button>
<button class="color-btn w-9 h-9 rounded-full border-2 border-gray-200 hover:scale-110 transition-transform bg-yellow-500" data-color="jaune" title="Jaune"></button>
<button class="color-btn w-9 h-9 rounded-full border-2 border-gray-200 hover:scale-110 transition-transform bg-purple-600" data-color="violet" title="Violet"></button>
</div>
</div>
</div>
<!-- Sizes -->
<div class="filter-section">
<button class="w-full px-5 py-3 flex items-center justify-between hover:bg-gray-50 transition-colors"
onclick="toggleFilterSection(this)">
<span class="font-semibold text-gray-700 text-sm flex items-center gap-2">
<i class="fas fa-ruler text-brand-500 w-4"></i>
Tailles
</span>
<i class="fas fa-chevron-down text-gray-400 text-xs transition-transform duration-300"></i>
</button>
<div class="filter-content px-5 pb-4 hidden">
<div class="flex flex-wrap gap-2">
<button class="size-btn min-w-[44px] px-3 py-2 border border-gray-300 rounded-lg text-sm hover:border-brand-500 transition-colors" data-size="XS">XS</button>
<button class="size-btn min-w-[44px] px-3 py-2 border border-gray-300 rounded-lg text-sm hover:border-brand-500 transition-colors" data-size="S">S</button>
<button class="size-btn min-w-[44px] px-3 py-2 border-2 border-brand-500 bg-brand-50 text-brand-700 rounded-lg text-sm font-medium" data-size="M">M</button>
<button class="size-btn min-w-[44px] px-3 py-2 border border-gray-300 rounded-lg text-sm hover:border-brand-500 transition-colors" data-size="L">L</button>
<button class="size-btn min-w-[44px] px-3 py-2 border border-gray-300 rounded-lg text-sm hover:border-brand-500 transition-colors" data-size="XL">XL</button>
<button class="size-btn min-w-[44px] px-3 py-2 border border-gray-200 rounded-lg text-sm text-gray-300 cursor-not-allowed line-through" data-size="XXL" disabled>XXL</button>
</div>
</div>
</div>
<!-- Brands -->
<div class="filter-section">
<button class="w-full px-5 py-3 flex items-center justify-between hover:bg-gray-50 transition-colors"
onclick="toggleFilterSection(this)">
<span class="font-semibold text-gray-700 text-sm flex items-center gap-2">
<i class="fas fa-tag text-brand-500 w-4"></i>
Marques
</span>
<i class="fas fa-chevron-down text-gray-400 text-xs transition-transform duration-300"></i>
</button>
<div class="filter-content px-5 pb-4 hidden">
<input type="text" placeholder="Rechercher une marque..."
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm mb-3 focus:ring-2 focus:ring-brand-500 focus:border-brand-500 outline-none"
oninput="searchBrands(this.value)">
<div class="space-y-2" id="brandList">
<label class="flex items-center gap-2 cursor-pointer hover:text-brand-600 transition-colors">
<input type="checkbox" value="apple" class="brand-checkbox w-4 h-4 text-brand-500 rounded focus:ring-brand-500"> Apple
</label>
<label class="flex items-center gap-2 cursor-pointer hover:text-brand-600 transition-colors">
<input type="checkbox" value="samsung" class="brand-checkbox w-4 h-4 text-brand-500 rounded focus:ring-brand-500"> Samsung
</label>
<label class="flex items-center gap-2 cursor-pointer hover:text-brand-600 transition-colors">
<input type="checkbox" value="nike" class="brand-checkbox w-4 h-4 text-brand-500 rounded focus:ring-brand-500"> Nike
</label>
<label class="flex items-center gap-2 cursor-pointer hover:text-brand-600 transition-colors">
<input type="checkbox" value="adidas" class="brand-checkbox w-4 h-4 text-brand-500 rounded focus:ring-brand-500"> Adidas
</label>
<label class="flex items-center gap-2 cursor-pointer hover:text-brand-600 transition-colors">
<input type="checkbox" value="sony" class="brand-checkbox w-4 h-4 text-brand-500 rounded focus:ring-brand-500"> Sony
</label>
</div>
</div>
</div>
</div>
<!-- Apply Button (Mobile) -->
<div class="lg:hidden sticky bottom-0 bg-white border-t border-gray-200 p-4">
<button id="applyMobileBtn" class="w-full bg-brand-500 text-white py-3 rounded-lg font-medium hover:bg-brand-600 transition-colors">
<i class="fas fa-check mr-2"></i>
Appliquer les filtres
</button>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 p-4 lg:p-8">
<h1 class="text-3xl font-bold text-gray-800 mb-2">Produits</h1>
<p class="text-gray-500 mb-6">
<span id="resultsCount">120</span> résultats trouvés
</p>
<!-- Loading -->
<div id="loadingSpinner" class="hidden justify-center py-12">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-brand-500"></div>
</div>
<!-- Products Area -->
<div class="bg-white rounded-xl border border-gray-200 p-8 text-center text-gray-400">
<i class="fas fa-arrow-left text-3xl mb-3"></i>
<p>Utilisez les filtres dans la barre latérale</p>
<p class="text-sm mt-1">Sur mobile, cliquez sur le bouton "Filtres"</p>
</div>
</main>
</div>
<script>
// État
const state = {
categories: ['all'],
priceMin: 0,
priceMax: 1000,
ratings: [],
colors: [],
sizes: ['M'],
brands: []
};
// Toggle sections accordéon
function toggleFilterSection(button) {
const content = button.nextElementSibling;
const arrow = button.querySelector('.fa-chevron-down');
content.classList.toggle('hidden');
arrow.style.transform = content.classList.contains('hidden') ? 'rotate(0deg)' : 'rotate(180deg)';
}
// Catégories
document.querySelectorAll('[data-category]').forEach(btn => {
btn.addEventListener('click', function() {
const parent = this.parentElement;
parent.querySelectorAll('button').forEach(b => {
b.className = 'w-full flex items-center justify-between px-3 py-2 rounded-lg text-gray-600 hover:bg-gray-50 text-sm transition-colors';
b.querySelector('span:last-child').className = 'text-gray-400 text-xs';
});
this.className = 'w-full flex items-center justify-between px-3 py-2 rounded-lg bg-brand-50 text-brand-700 font-medium text-sm transition-colors';
this.querySelector('span:last-child').className = 'bg-brand-500 text-white text-xs px-2 py-0.5 rounded-full';
state.categories = [this.dataset.category];
updateUI();
});
});
// Prix
function applyPriceFilter() {
state.priceMin = parseFloat(document.getElementById('priceMin').value) || 0;
state.priceMax = parseFloat(document.getElementById('priceMax').value) || 1000;
updateUI();
}
// Notes
document.querySelectorAll('.rating-checkbox').forEach(cb => {
cb.addEventListener('change', function() {
state.ratings = Array.from(document.querySelectorAll('.rating-checkbox:checked'))
.map(input => parseInt(input.value));
updateUI();
});
});
// Couleurs
document.querySelectorAll('.color-btn').forEach(btn => {
btn.addEventListener('click', function() {
this.classList.toggle('ring-2');
this.classList.toggle('ring-brand-500');
this.classList.toggle('ring-offset-2');
const color = this.dataset.color;
if (this.classList.contains('ring-2')) {
if (!state.colors.includes(color)) state.colors.push(color);
} else {
state.colors = state.colors.filter(c => c !== color);
}
updateUI();
});
});
// Tailles
document.querySelectorAll('.size-btn:not([disabled])').forEach(btn => {
btn.addEventListener('click', function() {
const size = this.dataset.size;
if (this.classList.contains('bg-brand-50')) {
this.className = 'size-btn min-w-[44px] px-3 py-2 border border-gray-300 rounded-lg text-sm hover:border-brand-500 transition-colors';
state.sizes = state.sizes.filter(s => s !== size);
} else {
this.className = 'size-btn min-w-[44px] px-3 py-2 border-2 border-brand-500 bg-brand-50 text-brand-700 rounded-lg text-sm font-medium';
if (!state.sizes.includes(size)) state.sizes.push(size);
}
updateUI();
});
});
// Marques
document.querySelectorAll('.brand-checkbox').forEach(cb => {
cb.addEventListener('change', function() {
state.brands = Array.from(document.querySelectorAll('.brand-checkbox:checked'))
.map(input => input.value);
updateUI();
});
});
function searchBrands(query) {
document.querySelectorAll('#brandList label').forEach(label => {
const text = label.textContent.toLowerCase();
label.style.display = text.includes(query.toLowerCase()) ? 'flex' : 'none';
});
}
// Mise à jour UI
function updateUI() {
updateActiveTags();
updateMobileCount();
applyFilters();
}
function updateActiveTags() {
const container = document.getElementById('activeFilterTags');
let tags = [];
if (state.categories[0] !== 'all') {
tags.push({ label: `Catégorie: ${state.categories[0]}`, clear: () => {
state.categories = ['all'];
document.querySelector('[data-category="all"]').click();
}});
}
if (state.priceMin > 0 || state.priceMax < 1000) {
tags.push({ label: `${state.priceMin}€ - ${state.priceMax}€`, clear: () => {
state.priceMin = 0; state.priceMax = 1000;
document.getElementById('priceMin').value = 0;
document.getElementById('priceMax').value = 1000;
updateUI();
}});
}
container.innerHTML = tags.map(tag => `
<span class="inline-flex items-center gap-1 bg-brand-50 text-brand-700 px-2.5 py-1 rounded-full text-xs font-medium">
${tag.label}
<button class="hover:text-brand-900 ml-1" onclick="arguments[0].stopPropagation();">
<i class="fas fa-times"></i>
</button>
</span>
`).join('');
// Attacher les événements
container.querySelectorAll('button').forEach((btn, i) => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
tags[i].clear();
});
});
}
function updateMobileCount() {
const count =
(state.categories[0] !== 'all' ? 1 : 0) +
(state.priceMin > 0 || state.priceMax < 1000 ? 1 : 0) +
state.ratings.length +
state.colors.length +
(state.sizes.length > 0 ? 1 : 0) +
state.brands.length;
document.getElementById('mobileFilterCount').textContent = count || '0';
}
function applyFilters() {
const spinner = document.getElementById('loadingSpinner');
spinner.classList.remove('hidden');
spinner.classList.add('flex');
setTimeout(() => {
spinner.classList.add('hidden');
spinner.classList.remove('flex');
const count = Math.floor(Math.random() * 50) + 10;
document.getElementById('resultsCount').textContent = count;
}, 400);
}
// Clear all
document.getElementById('clearAllBtn').addEventListener('click', () => {
state.categories = ['all'];
state.priceMin = 0;
state.priceMax = 1000;
state.ratings = [];
state.colors = [];
state.sizes = ['M'];
state.brands = [];
document.querySelector('[data-category="all"]').click();
document.getElementById('priceMin').value = 0;
document.getElementById('priceMax').value = 1000;
document.querySelectorAll('.rating-checkbox').forEach(cb => cb.checked = false);
document.querySelectorAll('.color-btn').forEach(btn => {
btn.classList.remove('ring-2', 'ring-brand-500', 'ring-offset-2');
});
document.querySelectorAll('.size-btn:not([disabled])').forEach(btn => {
if (btn.dataset.size === 'M') {
btn.className = 'size-btn min-w-[44px] px-3 py-2 border-2 border-brand-500 bg-brand-50 text-brand-700 rounded-lg text-sm font-medium';
} else {
btn.className = 'size-btn min-w-[44px] px-3 py-2 border border-gray-300 rounded-lg text-sm hover:border-brand-500 transition-colors';
}
});
document.querySelectorAll('.brand-checkbox').forEach(cb => cb.checked = false);
updateUI();
});
// Mobile sidebar toggle
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
const body = document.body;
document.getElementById('mobileFilterToggle').addEventListener('click', () => {
sidebar.classList.remove('-translate-x-full');
overlay.classList.remove('hidden');
body.style.overflow = 'hidden';
});
function closeSidebar() {
sidebar.classList.add('-translate-x-full');
overlay.classList.add('hidden');
body.style.overflow = '';
}
document.getElementById('closeSidebarBtn').addEventListener('click', closeSidebar);
overlay.addEventListener('click', closeSidebar);
document.getElementById('applyMobileBtn').addEventListener('click', closeSidebar);
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !sidebar.classList.contains('-translate-x-full')) {
closeSidebar();
}
});
// Initial
updateMobileCount();
</script>
</body>
</html>
Télécharger le fichier source