Components Medium
Filter Search Bar
Inline search input with scrollable category pill filters, active state, result counter, and keyboard shortcut hint.
Open in Lab
MCP
css javascript
Targets: HTML
Code
:root {
color-scheme: dark;
font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
background: #080808;
color: #e2e8f0;
}
.page {
max-width: 1280px;
margin: 0 auto;
padding: 2.5rem 1.5rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
/* ─── Filter bar container ─── */
.filter-bar {
display: flex;
flex-direction: column;
gap: 0.875rem;
}
/* ─── Search field ─── */
.search-wrap {
position: relative;
display: flex;
align-items: center;
}
.search-icon {
position: absolute;
left: 12px;
color: rgba(255, 255, 255, 0.3);
pointer-events: none;
}
.search-input {
width: 100%;
max-width: 380px;
padding: 9px 44px 9px 38px;
border-radius: 9px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.09);
color: #f8fafc;
font-size: 0.875rem;
font-family: inherit;
outline: none;
transition: border-color 0.15s, background 0.15s;
}
.search-input::placeholder {
color: rgba(255, 255, 255, 0.28);
}
.search-input:focus {
border-color: rgba(255, 255, 255, 0.18);
background: rgba(255, 255, 255, 0.07);
}
/* Remove native clear button in webkit */
.search-input::-webkit-search-cancel-button {
-webkit-appearance: none;
}
.search-kbd {
position: absolute;
right: 10px;
padding: 2px 6px;
border-radius: 5px;
background: rgba(255, 255, 255, 0.07);
border: 1px solid rgba(255, 255, 255, 0.08);
font-family: inherit;
font-size: 0.7rem;
color: rgba(255, 255, 255, 0.3);
pointer-events: none;
}
/* ─── Pills row ─── */
.pills-row {
display: flex;
align-items: center;
gap: 6px;
overflow-x: auto;
scrollbar-width: none;
padding-bottom: 2px;
}
.pills-row::-webkit-scrollbar {
display: none;
}
.pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 13px;
border-radius: 999px;
font-size: 0.8125rem;
font-family: inherit;
color: rgba(255, 255, 255, 0.45);
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
cursor: pointer;
white-space: nowrap;
transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.pill:hover {
color: rgba(255, 255, 255, 0.75);
background: rgba(255, 255, 255, 0.08);
}
.pill.active {
color: #f8fafc;
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.18);
}
.pill-count {
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.3);
}
.pill.active .pill-count {
color: rgba(255, 255, 255, 0.55);
}
/* ─── Result count ─── */
.result-count {
font-size: 0.8125rem;
color: rgba(255, 255, 255, 0.32);
}
.result-count strong {
color: rgba(255, 255, 255, 0.6);
font-weight: 600;
}
/* ─── Demo grid ─── */
.demo-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 10px;
}
.demo-item {
padding: 1rem;
border-radius: 10px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.07);
font-size: 0.8125rem;
color: rgba(255, 255, 255, 0.45);
text-align: center;
}<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Filter Search Bar</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="page">
<!-- Filter bar -->
<div class="filter-bar" role="search">
<div class="search-wrap">
<svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
<input
type="search"
class="search-input"
id="search-input"
placeholder="Search components…"
aria-label="Search components"
autocomplete="off"
spellcheck="false"
/>
<kbd class="search-kbd">⌘K</kbd>
</div>
<div class="pills-row" role="group" aria-label="Filter by category">
<button class="pill active" data-filter="all">All <span class="pill-count">553</span></button>
<button class="pill" data-filter="ui-components">UI Components <span class="pill-count">311</span></button>
<button class="pill" data-filter="web-animations">Web Animations <span class="pill-count">44</span></button>
<button class="pill" data-filter="patterns">Patterns <span class="pill-count">55</span></button>
<button class="pill" data-filter="pages">Pages <span class="pill-count">59</span></button>
<button class="pill" data-filter="design-styles">Design Styles <span class="pill-count">26</span></button>
<button class="pill" data-filter="architectures">Architectures <span class="pill-count">13</span></button>
<button class="pill" data-filter="boilerplates">Boilerplates <span class="pill-count">11</span></button>
<button class="pill" data-filter="components">Components <span class="pill-count">11</span></button>
</div>
</div>
<!-- Result count -->
<p class="result-count" id="result-count">Showing <strong>553</strong> results</p>
<!-- Demo grid placeholder -->
<div class="demo-grid" id="demo-grid">
<div class="demo-item" data-category="ui-components">Button Group</div>
<div class="demo-item" data-category="ui-components">Tooltip</div>
<div class="demo-item" data-category="web-animations">Fade In</div>
<div class="demo-item" data-category="patterns">Skeleton Loader</div>
<div class="demo-item" data-category="pages">Landing Page</div>
<div class="demo-item" data-category="design-styles">Glassmorphism</div>
<div class="demo-item" data-category="architectures">REST API</div>
<div class="demo-item" data-category="boilerplates">Next.js Starter</div>
<div class="demo-item" data-category="components">Nav Header</div>
<div class="demo-item" data-category="ui-components">Modal Dialog</div>
<div class="demo-item" data-category="web-animations">Parallax Scroll</div>
<div class="demo-item" data-category="patterns">Infinite Scroll</div>
</div>
</div>
<script>
const searchInput = document.getElementById('search-input');
const resultCount = document.getElementById('result-count');
const items = document.querySelectorAll('.demo-item');
const pills = document.querySelectorAll('.pill');
let activeFilter = 'all';
function applyFilters() {
const query = searchInput.value.toLowerCase().trim();
let count = 0;
items.forEach(item => {
const cat = item.dataset.category;
const text = item.textContent.toLowerCase();
const inCat = activeFilter === 'all' || cat === activeFilter;
const inQ = !query || text.includes(query);
const show = inCat && inQ;
item.style.display = show ? '' : 'none';
if (show) count++;
});
resultCount.innerHTML = `Showing <strong>${count}</strong> result${count !== 1 ? 's' : ''}`;
}
pills.forEach(pill => {
pill.addEventListener('click', () => {
const filter = pill.dataset.filter;
activeFilter = (activeFilter === filter && filter !== 'all') ? 'all' : filter;
pills.forEach(p => p.classList.toggle('active', p.dataset.filter === activeFilter));
applyFilters();
});
});
searchInput.addEventListener('input', applyFilters);
// ⌘K / Ctrl+K focus shortcut
document.addEventListener('keydown', (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
searchInput.focus();
searchInput.select();
}
});
</script>
</body>
</html>Filter Search Bar
A horizontal filter bar combining a text search input with scrollable category pill buttons. Clicking a pill activates it (visually highlighted) and updates a result count. The search input supports a ⌘K keyboard shortcut hint.
Features
- Text search with live result count
- Pill-style category filters with active/inactive states
- Horizontally scrollable pill row for many categories
⌘Kshortcut hint in the search field- Clears active filter when clicking the already-active pill
When to use
- Component/resource library browsing pages
- Dashboard tables with category filters
- Any grid that needs quick type-and-filter UX