Skip to content

Instantly share code, notes, and snippets.

@manchumahara
Created August 13, 2026 16:44
Show Gist options
  • Select an option

  • Save manchumahara/b9a56590350503786fd170a97f4cd222 to your computer and use it in GitHub Desktop.

Select an option

Save manchumahara/b9a56590350503786fd170a97f4cd222 to your computer and use it in GitHub Desktop.
Shadcn based advance search filter
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Advanced Search Filter — shadcn-style demo</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
:root{
--background:0 0% 100%;
--foreground:222.2 84% 4.9%;
--muted:210 40% 96.1%;
--muted-foreground:215.4 16.3% 46.9%;
--border:214.3 31.8% 91.4%;
--input:214.3 31.8% 91.4%;
--ring:222.2 84% 4.9%;
--primary:222.2 47.4% 11.2%;
--primary-foreground:210 40% 98%;
--secondary:210 40% 96.1%;
--secondary-foreground:222.2 47.4% 11.2%;
--accent:210 40% 96.1%;
--accent-foreground:222.2 47.4% 11.2%;
--destructive:0 84.2% 60.2%;
--destructive-foreground:210 40% 98%;
--radius:0.5rem;
}
body{ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: hsl(210 40% 98%); color: hsl(var(--foreground)); }
/* shadcn-style primitives, hand-rolled in plain CSS so this stays a single static file */
.sc-input{
display:flex; width:100%; height:2.5rem; border-radius: calc(var(--radius) - 2px);
border:1px solid hsl(var(--input)); background-color: hsl(var(--background));
padding: 0 0.75rem; font-size: 0.875rem; line-height:1.25rem; color:hsl(var(--foreground));
transition: box-shadow .15s ease, border-color .15s ease;
}
.sc-input::placeholder{ color: hsl(var(--muted-foreground)); }
.sc-input:focus{ outline:none; border-color: hsl(215 20% 65%); box-shadow: 0 0 0 3px hsl(222.2 47.4% 11.2% / 0.08); }
.sc-select{
appearance:none; -webkit-appearance:none;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'></polyline></svg>");
background-repeat:no-repeat; background-position: right 0.6rem center; padding-right:2rem;
}
.sc-label{ font-size:0.8125rem; font-weight:500; color: hsl(222.2 47.4% 20%); margin-bottom:0.375rem; display:block; }
.sc-hint{ font-size:0.75rem; color:hsl(var(--muted-foreground)); margin-top:0.25rem; }
.sc-btn{
display:inline-flex; align-items:center; justify-content:center; gap:0.4rem;
height:2.5rem; padding:0 1rem; border-radius: calc(var(--radius) - 2px);
font-size:0.875rem; font-weight:500; white-space:nowrap; cursor:pointer;
transition: background-color .15s ease, border-color .15s ease, color .15s ease;
border: 1px solid transparent;
}
.sc-btn:focus-visible{ outline:none; box-shadow: 0 0 0 3px hsl(222.2 47.4% 11.2% / 0.15); }
.sc-btn-primary{ background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); }
.sc-btn-primary:hover{ background-color: hsl(222.2 47.4% 18%); }
.sc-btn-outline{ background-color: hsl(var(--background)); border-color: hsl(var(--border)); color: hsl(var(--foreground)); }
.sc-btn-outline:hover{ background-color: hsl(var(--muted)); }
.sc-btn-ghost{ background-color: transparent; color: hsl(222.2 47.4% 20%); }
.sc-btn-ghost:hover{ background-color: hsl(var(--muted)); }
.sc-card{
background-color: hsl(var(--background)); border:1px solid hsl(var(--border));
border-radius: var(--radius); box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.04);
}
.sc-checkbox{
appearance:none; -webkit-appearance:none; width:1rem; height:1rem; border-radius:0.25rem;
border:1px solid hsl(215 20% 70%); background-color:#fff; display:inline-block; position:relative;
cursor:pointer; flex-shrink:0; transition: background-color .12s ease, border-color .12s ease;
}
.sc-checkbox:checked{ background-color: hsl(var(--primary)); border-color: hsl(var(--primary)); }
.sc-checkbox:checked::after{
content:""; position:absolute; left:3px; top:0px; width:4px; height:8px;
border:solid white; border-width:0 2px 2px 0; transform:rotate(45deg);
}
.sc-checkbox:focus-visible{ outline:none; box-shadow: 0 0 0 3px hsl(222.2 47.4% 11.2% / 0.15); }
.sc-radio{
appearance:none; -webkit-appearance:none; width:1rem; height:1rem; border-radius:9999px;
border:1px solid hsl(215 20% 70%); background-color:#fff; display:inline-block; position:relative;
cursor:pointer; flex-shrink:0; transition: border-color .12s ease;
}
.sc-radio:checked{ border-color: hsl(var(--primary)); border-width:4px; }
.sc-radio:focus-visible{ outline:none; box-shadow: 0 0 0 3px hsl(222.2 47.4% 11.2% / 0.15); }
.sc-switch{ appearance:none; -webkit-appearance:none; width:2.25rem; height:1.25rem; border-radius:9999px; background-color: hsl(215 20% 85%); position:relative; cursor:pointer; transition: background-color .15s ease; flex-shrink:0; }
.sc-switch::after{ content:""; position:absolute; top:2px; left:2px; width:1rem; height:1rem; background:#fff; border-radius:9999px; transition: transform .15s ease; box-shadow: 0 1px 2px rgb(0 0 0 / 0.2); }
.sc-switch:checked{ background-color: hsl(var(--primary)); }
.sc-switch:checked::after{ transform: translateX(1rem); }
.sc-switch:focus-visible{ outline:none; box-shadow: 0 0 0 3px hsl(222.2 47.4% 11.2% / 0.15); }
.sc-badge{
display:inline-flex; align-items:center; height:1.25rem; padding:0 0.45rem; border-radius:9999px;
font-size:0.6875rem; font-weight:600; background-color: hsl(var(--primary)); color:hsl(var(--primary-foreground));
}
.sc-chip{
display:inline-flex; align-items:center; gap:0.35rem; height:1.75rem; padding:0 0.5rem 0 0.65rem;
border-radius:9999px; font-size:0.75rem; font-weight:500; background-color: hsl(var(--muted));
color: hsl(222.2 47.4% 20%); border:1px solid hsl(var(--border));
}
.sc-chip button{ color:hsl(var(--muted-foreground)); }
.sc-chip button:hover{ color:hsl(var(--foreground)); }
.sc-slider{ -webkit-appearance:none; appearance:none; width:100%; height:0.35rem; border-radius:9999px; background: hsl(210 40% 90%); }
.sc-slider::-webkit-slider-thumb{ -webkit-appearance:none; width:1rem; height:1rem; border-radius:9999px; background:hsl(var(--primary)); cursor:pointer; border:2px solid white; box-shadow:0 0 0 1px hsl(var(--border)); }
.sc-slider::-moz-range-thumb{ width:1rem; height:1rem; border-radius:9999px; background:hsl(var(--primary)); cursor:pointer; border:2px solid white; box-shadow:0 0 0 1px hsl(var(--border)); }
#advanced-panel{ display:grid; grid-template-rows: 0fr; opacity:0; transition: grid-template-rows .25s ease, opacity .2s ease, margin .25s ease; }
#advanced-panel.open{ grid-template-rows: 1fr; opacity:1; }
#advanced-panel > div{ overflow:hidden; }
#adv-chevron{ transition: transform .2s ease; }
#adv-chevron.open{ transform: rotate(180deg); }
</style>
</head>
<body class="min-h-screen">
<div class="max-w-5xl mx-auto px-4 sm:px-6 py-10 sm:py-14">
<div class="mb-6">
<h1 class="text-xl font-semibold tracking-tight text-slate-900">Search records</h1>
<p class="text-sm text-slate-500 mt-1">Static HTML demo of an advanced search filter, styled after shadcn/ui components.</p>
</div>
<form id="search-form" class="sc-card p-4 sm:p-5">
<!-- Primary search row -->
<div class="flex flex-col sm:flex-row gap-2.5">
<div class="relative flex-1">
<svg class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input
type="text"
id="q"
name="q"
class="sc-input pl-9"
placeholder="Search by keyword, title, claim, author, or reference ID…"
autocomplete="off"
/>
</div>
<div class="flex gap-2.5 shrink-0">
<button type="submit" class="sc-btn sc-btn-primary">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
Search
</button>
<button type="button" id="reset-btn" class="sc-btn sc-btn-outline">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
Reset
</button>
</div>
</div>
<!-- Advanced toggle row -->
<div class="flex items-center justify-between mt-3.5">
<button type="button" id="advanced-toggle" class="sc-btn sc-btn-ghost !h-8 !px-2 text-slate-600">
<svg class="w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="21" x2="4" y2="14"></line><line x1="4" y1="10" x2="4" y2="3"></line><line x1="12" y1="21" x2="12" y2="12"></line><line x1="12" y1="8" x2="12" y2="3"></line><line x1="20" y1="21" x2="20" y2="16"></line><line x1="20" y1="12" x2="20" y2="3"></line><line x1="1" y1="14" x2="7" y2="14"></line><line x1="9" y1="8" x2="15" y2="8"></line><line x1="17" y1="16" x2="23" y2="16"></line></svg>
<span>Advanced filters</span>
<span id="active-count" class="sc-badge hidden">0</span>
<svg id="adv-chevron" class="w-3.5 h-3.5 ml-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
</button>
<div id="active-chip-row" class="hidden sm:flex flex-wrap gap-1.5 justify-end"></div>
</div>
<!-- Advanced filters panel -->
<div id="advanced-panel">
<div>
<div class="pt-5 mt-4 border-t border-slate-200 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-5 gap-y-5">
<!-- Category (select) -->
<div>
<label class="sc-label" for="category">Category</label>
<select id="category" name="category" class="sc-input sc-select">
<option value="">All categories</option>
<option value="politics">Politics</option>
<option value="health">Health</option>
<option value="economy">Economy</option>
<option value="education">Education</option>
<option value="environment">Environment</option>
<option value="misc">Miscellaneous</option>
</select>
</div>
<!-- Author / Source (text) -->
<div>
<label class="sc-label" for="author">Author or source</label>
<input type="text" id="author" name="author" class="sc-input" placeholder="e.g. Prothom Alo, John Doe" />
</div>
<!-- Location (text) -->
<div>
<label class="sc-label" for="location">Location</label>
<input type="text" id="location" name="location" class="sc-input" placeholder="e.g. Dhaka, Chattogram" />
</div>
<!-- Date range (from/to) -->
<div>
<label class="sc-label">Date range</label>
<div class="flex items-center gap-2">
<input type="date" id="date_from" name="date_from" class="sc-input" />
<span class="text-slate-400 text-xs shrink-0">to</span>
<input type="date" id="date_to" name="date_to" class="sc-input" />
</div>
</div>
<!-- Sort by (select) -->
<div>
<label class="sc-label" for="sort_by">Sort by</label>
<select id="sort_by" name="sort_by" class="sc-input sc-select">
<option value="relevance">Relevance</option>
<option value="newest">Newest first</option>
<option value="oldest">Oldest first</option>
<option value="most_viewed">Most viewed</option>
<option value="most_discussed">Most discussed</option>
</select>
</div>
<!-- Language (select) -->
<div>
<label class="sc-label" for="language">Language</label>
<select id="language" name="language" class="sc-input sc-select">
<option value="">Any language</option>
<option value="bn">Bangla</option>
<option value="en">English</option>
<option value="hi">Hindi</option>
</select>
</div>
<!-- Verdict / status (radio group) -->
<div class="lg:col-span-1">
<label class="sc-label">Verdict status</label>
<div class="flex flex-col gap-2 pt-0.5">
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="radio" name="verdict" value="" class="sc-radio" checked />
Any status
</label>
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="radio" name="verdict" value="true" class="sc-radio" />
True / verified
</label>
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="radio" name="verdict" value="false" class="sc-radio" />
False / debunked
</label>
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="radio" name="verdict" value="unverified" class="sc-radio" />
Unverified
</label>
</div>
</div>
<!-- Confidence (radio group, ICD 203 style) -->
<div>
<label class="sc-label">Confidence level</label>
<div class="flex flex-col gap-2 pt-0.5">
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="radio" name="confidence" value="" class="sc-radio" checked />
Any confidence
</label>
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="radio" name="confidence" value="high" class="sc-radio" />
High
</label>
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="radio" name="confidence" value="moderate" class="sc-radio" />
Moderate
</label>
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="radio" name="confidence" value="low" class="sc-radio" />
Low
</label>
</div>
</div>
<!-- Tags (checkbox group) -->
<div>
<label class="sc-label">Tags</label>
<div class="flex flex-col gap-2 pt-0.5 max-h-32 overflow-y-auto pr-1">
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="checkbox" name="tags" value="election" class="sc-checkbox" /> Election
</label>
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="checkbox" name="tags" value="viral" class="sc-checkbox" /> Viral claim
</label>
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="checkbox" name="tags" value="social-media" class="sc-checkbox" /> Social media
</label>
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="checkbox" name="tags" value="government" class="sc-checkbox" /> Government statement
</label>
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="checkbox" name="tags" value="ai-generated" class="sc-checkbox" /> AI-generated media
</label>
</div>
</div>
<!-- Reliability score (range slider) -->
<div>
<label class="sc-label" for="reliability">
Minimum source reliability
<span id="reliability_val" class="text-slate-400 font-normal">(3+)</span>
</label>
<input type="range" id="reliability" name="reliability" min="1" max="5" step="1" value="3" class="sc-slider mt-3" />
<div class="flex justify-between text-[11px] text-slate-400 mt-1">
<span>1</span><span>2</span><span>3</span><span>4</span><span>5</span>
</div>
</div>
<!-- Reference ID (text) -->
<div>
<label class="sc-label" for="ref_id">Reference ID</label>
<input type="text" id="ref_id" name="ref_id" class="sc-input" placeholder="e.g. INF-2026-00231" />
</div>
<!-- Checkboxes: extra options -->
<div class="flex flex-col gap-3 justify-center">
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="checkbox" name="has_media" value="1" class="sc-checkbox" />
Has photo or video evidence
</label>
<label class="flex items-center gap-2 text-sm text-slate-700 cursor-pointer">
<input type="checkbox" name="has_sources" value="1" class="sc-checkbox" />
Has cited sources
</label>
</div>
<!-- Switch: include archived -->
<div class="flex items-center justify-between sm:justify-start sm:gap-3">
<label class="sc-label !mb-0" for="include_archived">Include archived records</label>
<input type="checkbox" id="include_archived" name="include_archived" class="sc-switch" />
</div>
</div>
<!-- Advanced panel footer actions -->
<div class="flex items-center justify-end gap-2 mt-5 pt-4 border-t border-slate-200">
<button type="button" id="clear-advanced" class="sc-btn sc-btn-ghost text-slate-600">
Clear advanced filters
</button>
<button type="submit" class="sc-btn sc-btn-primary">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
Apply filters
</button>
</div>
</div>
</div>
</form>
<!-- Result summary (demo only, no real search wiring) -->
<p id="result-note" class="text-xs text-slate-400 mt-4"></p>
</div>
<script>
const form = document.getElementById('search-form');
const toggleBtn = document.getElementById('advanced-toggle');
const panel = document.getElementById('advanced-panel');
const chevron = document.getElementById('adv-chevron');
const activeCount = document.getElementById('active-count');
const chipRow = document.getElementById('active-chip-row');
const resetBtn = document.getElementById('reset-btn');
const clearAdvancedBtn = document.getElementById('clear-advanced');
const resultNote = document.getElementById('result-note');
const reliability = document.getElementById('reliability');
const reliabilityVal = document.getElementById('reliability_val');
// Toggle advanced filters visibility
function setAdvancedOpen(open){
panel.classList.toggle('open', open);
chevron.classList.toggle('open', open);
toggleBtn.setAttribute('aria-expanded', String(open));
}
toggleBtn.addEventListener('click', () => {
setAdvancedOpen(!panel.classList.contains('open'));
});
reliability.addEventListener('input', () => {
reliabilityVal.textContent = `(${reliability.value}+)`;
});
// Reset everything
resetBtn.addEventListener('click', () => {
form.reset();
reliabilityVal.textContent = '(3+)';
updateActiveState();
resultNote.textContent = '';
});
// Clear only advanced fields, keep main keyword search
clearAdvancedBtn.addEventListener('click', () => {
panel.querySelectorAll('input, select').forEach(el => {
if (el.type === 'radio') el.checked = el.value === '';
else if (el.type === 'checkbox') el.checked = false;
else if (el.type === 'range') el.value = 3;
else el.value = '';
});
reliabilityVal.textContent = '(3+)';
updateActiveState();
});
// Track and display active advanced filter count + chips
function getActiveFilters(){
const active = [];
panel.querySelectorAll('input, select').forEach(el => {
if (el.type === 'checkbox' && el.checked) {
active.push({ id: el.id || el.name, label: el.closest('label') ? el.closest('label').textContent.trim() : el.name, field: el });
} else if (el.type === 'radio' && el.checked && el.value !== '') {
active.push({ id: el.name, label: el.closest('label').textContent.trim(), field: el, isRadioGroup: true });
} else if ((el.tagName === 'SELECT' || el.type === 'text' || el.type === 'date') && el.value) {
const labelEl = document.querySelector(`label[for="${el.id}"]`);
const label = labelEl ? labelEl.textContent.trim() + ': ' + (el.tagName === 'SELECT' ? el.options[el.selectedIndex].text : el.value) : el.value;
active.push({ id: el.id, label, field: el });
}
});
return active;
}
function updateActiveState(){
const active = getActiveFilters();
if (active.length > 0){
activeCount.textContent = active.length;
activeCount.classList.remove('hidden');
} else {
activeCount.classList.add('hidden');
}
chipRow.innerHTML = '';
active.slice(0, 4).forEach(item => {
const chip = document.createElement('span');
chip.className = 'sc-chip';
chip.innerHTML = `${item.label} <button type="button" aria-label="Remove filter">&times;</button>`;
chip.querySelector('button').addEventListener('click', () => {
if (item.field.type === 'checkbox') item.field.checked = false;
else if (item.isRadioGroup) item.field.checked = false;
else item.field.value = '';
updateActiveState();
});
chipRow.appendChild(chip);
});
if (active.length > 0) chipRow.classList.remove('hidden'); else chipRow.classList.add('hidden');
}
panel.addEventListener('change', updateActiveState);
document.getElementById('q').addEventListener('input', () => {}); // reserved for live search hook
form.addEventListener('submit', (e) => {
e.preventDefault();
const data = new FormData(form);
const q = data.get('q') || '(empty)';
resultNote.textContent = `Demo only — no backend wired up. Submitted keyword: "${q}", advanced filters active: ${getActiveFilters().length}.`;
});
updateActiveState();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment