webdevpuneet.com

Search Box — Free HTML CSS JS Live Filter Snippet

Search Box · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<div class="demo">
  <div class="search-wrap">
    <svg class="icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
      <circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
    </svg>
    <input type="text" class="search" placeholder="Search anything…" oninput="filterList(this.value)" />
    <kbd>⌘K</kbd>
  </div>
  <ul class="results" id="list">
    <li>Dashboard overview</li>
    <li>User settings</li>
    <li>API documentation</li>
    <li>Billing & plans</li>
    <li>Team members</li>
  </ul>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

:focus-within on the wrapper div — unified border + ring when input is focused
box-shadow: 0 0 0 3px rgba — focus ring without border-width layout shift
Live list filter: classList.toggle("hidden", condition) — adds and removes in one call
Case-insensitive substring match: .toLowerCase().includes(q.toLowerCase())
Keyboard shortcut badge styled with semantic <kbd> element
Search icon aligned via flexbox inside the wrapper — no absolute positioning
5 lines of vanilla JavaScript — no library needed
Export as HTML file, React JSX, or React + Tailwind CSS
Live split-pane editor — preview updates as you type
Mobile (375px), Tablet (768px), Desktop device preview buttons

About this UI Snippet

Search Box — Live Filter, :focus-within Ring, and Keyboard Shortcut Badge

Screenshot of the Search Box snippet rendered live

A polished search box is more than a styled text input. It communicates interactivity, responds to focus with a visual ring, filters results instantly as the user types (for suggestions, see the autocomplete input), and ideally shows a keyboard shortcut hint — as in the command palette and keyboard shortcuts panel — for power users. This snippet covers all four in a minimal, clean design.

The :focus-within ring

The search input is visually separated from the container — the <input> has no border, and the .search-wrap div holds the border and focus ring. When any child element (the input) is focused, :focus-within applies to the parent container. This lets you style the outer wrapper on input focus: border-color: #6366f1 and box-shadow: 0 0 0 3px rgba(99,102,241,0.12). This approach is cleaner than styling the input directly because it keeps the icon and input visually unified within a single bordered container.

The keyboard shortcut badge

The <kbd> element in the search box shows a keyboard shortcut hint — typically Cmd+K or Ctrl+K — styled as a small pill with a light background and border. Showing the keyboard shortcut directly in the UI teaches power users the faster path. In production, wire a keydown listener for the shortcut that calls input.focus().

The live filter logic

The filterList(q) function uses querySelectorAll('#list li') to get all list items and classList.toggle('hidden', condition) to show or hide each. The condition is !li.textContent.toLowerCase().includes(q.toLowerCase()) — a case-insensitive substring match. classList.toggle(className, force) adds the class if force is true and removes it if false — a single call that both adds and removes without separate if/else branches.

Connecting to real data

The current list items are static HTML. To filter a real data array, render the list items from JavaScript: items.forEach(item => { const li = document.createElement('li'); li.textContent = item; list.appendChild(li); }). The filterList function then works unchanged on the generated items.

Adding debounce for API search

For a search that calls an API on each keystroke, add a debounce: clear a timeout on each input event and set a new one that fires after 300ms. This prevents an API call on every keystroke and instead fires only when the user pauses.

The :focus-within ring

The search container (not the input) has the focus ring: .search-box:focus-within { box-shadow: 0 0 0 3px rgba(accent, 0.2); border-color: accent; }. :focus-within applies when any descendant has focus. This means the border and ring apply to the container when the input inside is focused — creating a larger, more visually prominent focus indicator than the default browser outline on the input alone.

The live filter pattern

The search input's oninput handler calls filterItems(e.target.value). filterItems iterates all .item elements and toggles .hidden based on whether the item's text content includes the query string (case-insensitive). The filter runs on every keystroke without debounce for lists under 500 items. For larger lists, add a 150ms debounce: clearTimeout(timer); timer = setTimeout(() => filterItems(q), 150).

The clear button

The × clear button appears when the input has content and disappears when it is empty. Clicking it clears the input, triggers a filterItems('') to show all items, and returns focus to the input. Implement the visibility toggle with a CSS class: .search-box.has-value .clear-btn { display: flex; } and add/remove .has-value on every input event based on input.value.length.

Step by step

How to Use

  1. 1
    Type in the search boxType in the preview search box to see the list filter instantly. Items that do not match the query are hidden via classList.toggle.
  2. 2
    Update the result itemsIn the HTML panel, replace the li text inside #list with your own items. The filterList function filters them automatically.
  3. 3
    Update the keyboard shortcut badgeFind the <kbd> element in the HTML and change Cmd+K to whatever shortcut you use. Wire the keydown listener in your JS to call input.focus().
  4. 4
    Change the focus ring colourFind #6366f1 in the CSS and replace with your brand colour. Updates the focused border and the box-shadow ring.
  5. 5
    Connect to a real data sourceReplace the static li elements with JavaScript-rendered items from an array or API response. The filterList function works on any li elements inside #list.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.

Real-world uses

Common Use Cases

Site search and navigation search
Drop the search box into a site header or page sidebar. Connect the query to a real-time API call with a 300ms debounce to filter search results as the user types.
Dashboard and table filters
Use the live filter function to instantly filter a table or list on a dashboard. The classList.toggle pattern works on any set of elements — no virtual DOM needed.
Learn :focus-within and classList.toggle
The focus ring uses :focus-within on the wrapper, and the filter uses classList.toggle with a force boolean. Edit both in the panels to understand how each works.
Command palette search input
Use as the input for the Command Palette snippet. The keyboard shortcut badge, focus ring, and live filter are all components of a command palette search bar.
Settings panel search
Add a search box above a settings list so users can find options without scrolling. The filter hides non-matching settings instantly with no page reload.
Prototype search-driven UIs
Use the search box in any prototype that needs filtering. The live preview lets you test the filter behaviour at all three device widths before writing framework code.

Got questions?

Frequently Asked Questions

The filterList(q) function calls querySelectorAll("#list li") to get all items, then loops through them with classList.toggle("hidden", condition). The condition is !item.textContent.toLowerCase().includes(q.toLowerCase()) — if true, the hidden class is added; if false, it is removed. A single toggle call handles both showing and hiding.

:focus-within on .search-wrap applies styles when any child element — in this case the input — is focused. This lets the outer wrapper show a border colour change and box-shadow ring when the input is active, keeping the search icon visually inside the focused container without complex event handling.

Add: document.addEventListener("keydown", e => { if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); document.querySelector(".search").focus(); } }). Update the kbd badge text to match the shortcut you choose.

Add a debounced input handler: let timer; input.addEventListener("input", () => { clearTimeout(timer); timer = setTimeout(() => fetchResults(input.value), 300); }). Render the results by clearing the list and appending new li elements from the API response. The filterList function is then no longer needed.

Add a clear button inside .search-wrap positioned absolutely to the right. Show it when input.value is non-empty (oninput). On click, set input.value = "" and call filterList("") to show all items.

Yes. Click "JSX" to download a React component. Replace filterList with a useState filter: const filtered = items.filter(item => item.toLowerCase().includes(query.toLowerCase())). Bind the input to query state and render only filtered items.