Web Component
Docs API
~11 kB gzipped · Framework-agnostic · Web Components

Smart Search
for the open web

A lightweight, production-ready search input built with Custom Elements and Shadow DOM. Drop it into React, Vue, Svelte, Angular, or plain HTML — no framework required.

Filter chips
Keyboard nav
Dark mode
Custom renderers
Multi-select
Form integration
~11 kB gzipped

Basic

Set a datasource URL with a {{q}} placeholder and provide a transformResponse function to map the API response. Type to search DummyJSON products.

Live
index.html
<smart-search
  placeholder="Search products..."
  datasource="https://api.example.com/search?{{q}}"
  debounce="300"
></smart-search>

<script type="module">
  const el = document.querySelector('smart-search')

  el.transformResponse = (json) =>
    json.products.map((p) => ({
      value: String(p.id),
      label: p.title,
      description: p.category,
      group: p.category,
    }))

  el.addEventListener('ss-menu-select', (e) => {
    console.log('selected:', e.detail.result)
  })
</script>

With Filters

Pass a filters JSON array to show toggleable filter chips above the results. Active filters append field=value to the query string sent to your API.

Live
index.html
<smart-search
  datasource="https://api.example.com/search?{{q}}"
  filters='[
    {"field":"category","value":"smartphones",
     "label":"Smartphones"},
    {"field":"category","value":"laptops",
     "label":"Laptops"}
  ]'
></smart-search>

// Active filters compose the query:
// ?q=phone&category=smartphones

el.addEventListener('ss-filter-change', (e) => {
  console.log(e.detail.filters)
  // [{ field: 'category', value: 'smartphones' }]
})

Dark Theme

Add theme="dark" for the built-in dark palette. theme="auto" (default) follows prefers-color-scheme. All colors are overridable via CSS custom properties.

Live
index.html
<!-- Built-in dark palette -->
<smart-search
  theme="dark"
  datasource="..."
></smart-search>

<!-- Follow system preference -->
<smart-search theme="auto"></smart-search>

/* Override via CSS custom properties */
smart-search {
  --ss-accent: #7c3aed;
  --ss-radius: 6px;
  --ss-input-height: 44px;
}

Brand Themes

Override any CSS token per instance to match your brand. Set custom properties directly on the element — they cascade into the Shadow DOM automatically.

Live — three brand palettes, one component
Violet — SaaS / Fintech
Emerald — Health / Eco
Rose — Fashion / E-commerce
styles.css
/* Violet — SaaS / Fintech */
#violet-search {
  --ss-accent: #7c3aed;
  --ss-accent-ring: rgba(124, 58, 237, 0.2);
  --ss-active: #ede9fe;
}

/* Emerald — Health / Eco */
#emerald-search {
  --ss-accent: #059669;
  --ss-accent-ring: rgba(5, 150, 105, 0.2);
  --ss-active: #d1fae5;
}

/* Rose — Fashion / E-commerce */
#rose-search {
  --ss-accent: #e11d48;
  --ss-accent-ring: rgba(225, 29, 72, 0.2);
  --ss-active: #ffe4e6;
}

/* Or set inline on the element: */
<smart-search
  style="--ss-accent:#7c3aed;
         --ss-active:#ede9fe"
></smart-search>

Custom Renderer

Use resultItemRenderer to return any DOM node as the result content. CSS tokens like --ss-accent are available inside rendered nodes.

Live — product cards with thumbnail & price
main.js
el.resultItemRenderer = (result) => {
  const { metadata } = result

  const card = document.createElement('div')
  card.style.cssText =
    'display:flex;gap:12px;align-items:center'

  const img = document.createElement('img')
  img.src = metadata.thumbnail
  img.width = 44; img.height = 44
  img.style.cssText =
    'border-radius:6px;object-fit:cover'

  const info = document.createElement('div')
  info.innerHTML = `
    <strong>${result.label}</strong>
    <div style="color:var(--ss-accent)">
      $${metadata.price}
    </div>`

  card.append(img, info)
  return card
}

Multi-select

Add the multiselect attribute to enable selecting multiple items. Selected items appear as chips in the input. Listen to ss-multiselect-change or read el.selectedItems directly.

Live
index.html
<smart-search
  multiselect
  close-menu-on-select="false"
  datasource="..."
></smart-search>

el.addEventListener(
  'ss-multiselect-change',
  (e) => {
    // e.detail.items: SearchResult[]
    console.log(e.detail.items)
  }
)

// Read state directly
const selected = el.selectedItems

Static Options

Pre-load an options array for purely client-side filtering — no network requests. The component filters by label and description as you type.

Live — no network requests
index.html
                          <smart-search
                          placeholder="Search languages..."
                          options='[
                            {
                              "value": "js",
                              "label": "JavaScript",
                              "description": "Web scripting",
                              "group": "Frontend"
                            },
                            {
                              "value": "go",
                              "label": "Go",
                              "description": "Systems language",
                              "group": "Backend"
                            }
                          ]'
                          ></smart-search>

                          // Or set programmatically:
                          el.options = myItems

                      

Events Log

All interactions fire custom events on the element. Type, select, toggle a filter, or open the menu to see them stream in the log below.

Events will appear here as you interact…