Search List
<quiet-search-list>
Search lists let you query a collection of items based on their content and keywords.
Search Lists provide real-time filtering of content as the user types, supporting exact or fuzzy matching, custom keywords, and customizable empty states. The component works with just about any type of content and can be customized with different layouts and styling.
Meowy McGee
Wonder Whiskers
Maine Attraction
Sir Pounce-a-lot
No matching results
<quiet-search-list match="fuzzy" id="search-list__overview"> <!-- Controller --> <quiet-text-field slot="controller" label="Search cats" description="Results will update as you type" type="search" clearable pill > <quiet-icon slot="start" name="search"></quiet-icon> </quiet-text-field> <!-- Items --> <quiet-card> <quiet-avatar image="https://images.unsplash.com/photo-1672487209629-4d52e0c043d0?q=80&w=256&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"></quiet-avatar> <div> <h4 class="name">Meowy McGee</h4> <div class="tagline">Freedom's just another word for nothing left to lose.</div> </div> <div class="buttons"> <quiet-button icon-label="Settings" appearance="text" pill> <quiet-icon name="dots"></quiet-icon> </quiet-button> </div> </quiet-card> <quiet-card> <quiet-avatar image="https://images.unsplash.com/photo-1529778873920-4da4926a72c2?q=80&w=256&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"></quiet-avatar> <div> <h4 class="name">Wonder Whiskers</h4> <div class="tagline">Living my best nine lives, one nap at a time.</div> </div> <div class="buttons"> <quiet-button icon-label="Settings" appearance="text" pill> <quiet-icon name="dots"></quiet-icon> </quiet-button> </div> </quiet-card> <quiet-card> <quiet-avatar image="https://images.unsplash.com/photo-1569591159212-b02ea8a9f239?q=80&w=256&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"></quiet-avatar> <div> <h4 class="name">Maine Attraction</h4> <div class="tagline">Big fluff, bigger personality.</div> </div> <div class="buttons"> <quiet-button icon-label="Settings" appearance="text" pill> <quiet-icon name="dots"></quiet-icon> </quiet-button> </div> </quiet-card> <quiet-card> <quiet-avatar image="https://images.unsplash.com/photo-1735820474275-dd0ff4f28d71?q=80&w=256&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"></quiet-avatar> <div> <h4 class="name">Sir Pounce-a-lot</h4> <div class="tagline">Professional sunbeam chaser and nap enthusiast.</div> </div> <div class="buttons"> <quiet-button icon-label="Settings" appearance="text" pill> <quiet-icon name="dots"></quiet-icon> </quiet-button> </div> </quiet-card> <!-- Empty state --> <quiet-empty-state slot="empty"> <quiet-icon slot="illustration" name="cat"></quiet-icon> <p>No matching results</p> </quiet-empty-state> </quiet-search-list> <style> #search-list__overview { quiet-card { &::part(body) { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 1rem; padding: 0.75rem; min-height: 0; } quiet-avatar { --size: 2.5rem; } .name, .tagline { margin: 0; line-height: 1.2; display: inline; } .name { display: block; font-size: 1.125rem; margin-right: 0.5rem; } .tagline { color: var(--quiet-text-muted); font-size: 0.875rem; } .buttons { margin: 0; } } div[slot="empty"] { text-align: center; color: var(--quiet-text-muted); margin-block-start: 1rem; quiet-icon { font-size: 2.5rem; stroke-width: 1px; } } } </style>
Examples Jump to heading
Providing a controller and items Jump to heading
Every search list must have a search box, or controller, that maintains the query. Controllers can
be <quiet-text-field>
or native
<input>
elements. To link a controller, place it in the search list's
controller
slot or assign an external one. Make
sure to add a label and description to ensure it's accessible.
Searchable items can be just about any element, but they must be direct descendants of the
<quiet-search-list>
element. By default, an item's
text content
will be used to determine a match, but you can also specify keywords. A
case-insensitive search is performed by default, but basic fuzzy matching and
custom matching are also available.
As the user types in the controller, the search list will update and show the matching results. When no query is entered, all items are shown. An optional empty state can be provided to show a custom message when a query is entered and no matches are found.
A minimal implementation looks something like this. Note the use of label
and
description
, which are important for accessibility.
<quiet-search-list> <!-- Controller --> <quiet-text-field slot="controller" label="Search" description="Results will update as you type" ></quiet-text-field> <!-- Items --> <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> </quiet-search-list>
Here's an example using a native <input>
element.
<quiet-search-list> <!-- Controller --> <label slot="controller" for="search">Search</label> <input slot="controller" id="search" aria-description="Results will update as you type"> <!-- Items --> <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> </quiet-search-list>
Items aren't given any styles by the component — that part is up to you! The item's container, however, is
styled as a flex column container by default. To change the layout, apply the desired CSS using the
::part(items)
selector. Both flex and grid layouts work really well here.
Here is the example from above, modified with example styles.
<quiet-search-list id="search-list__search"> <quiet-text-field slot="controller" label="Search" description="Results will update as you type" ></quiet-text-field> <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> </quiet-search-list> <style> #search-list__search { &::part(items) { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(180px, 100%), 1fr)); } > div { border: var(--quiet-border-style) var(--quiet-border-width) var(--quiet-neutral-stroke-softer); border-radius: var(--quiet-border-radius); background-color: var(--quiet-paper-color); box-shadow: var(--quiet-shadow-softer); text-align: center; padding: 1rem; } } </style>
Search lists are designed to listen for user input dispatched by their controllers. As such, canceling the
controller's input
event or programmatically modifying its value will cause the search list
to get out of sync. In that case, you can use the setQuery()
method to programmatically
update the search list.
Fuzzy matching Jump to heading
By default, the search list shows results based on case-insensitive, exact matches. For a more permissive
search, add match="fuzzy"
which is more forgiving to typos.
<quiet-search-list match="fuzzy" id="search-list__fuzzy"> <!-- Controller --> <quiet-text-field slot="controller" label="Search for names" type="search" clearable pill > <quiet-icon slot="start" name="search"></quiet-icon> </quiet-text-field> <!-- Items --> <div class="item">Luna</div> <div class="item">Oliver</div> <div class="item">Bella</div> <div class="item">Whiskers</div> <div class="item">Maple</div> <div class="item">Sushi</div> <div class="item">Pepper</div> <div class="item">Mittens</div> <div class="item">Shadow</div> <div class="item">Oreo</div> <div class="item">Mochi</div> <div class="item">Nova</div> <div class="item">Tiger</div> <div class="item">Ziggy</div> </quiet-search-list> <style> #search-list__fuzzy { /* Style the items in a flex row */ &::part(items) { flex-direction: row; gap: 0.5rem; } /* Custom item styles */ .item { border: var(--quiet-border-style) var(--quiet-border-width) var(--quiet-neutral-stroke-softer); border-radius: 9999px; background-color: var(--quiet-paper-color); box-shadow: var(--quiet-shadow-softer); padding: 0.5rem 1rem; } } </style>
For even more control over the matching algorithm, you can specific a custom match function.
Adding keywords Jump to heading
Add the data-keywords
attribute to any item to include additional keywords the search list
should match by. This is useful for adding terms you'd like the item to match on even when the term doesn't
appear in regular content.
<quiet-search-list id="search-list__keywords"> <!-- Controller --> <quiet-text-field slot="controller" label="Search by color" description="e.g. brown, orange, white, gray, black" type="search" clearable pill > <quiet-icon slot="start" name="search"></quiet-icon> </quiet-text-field> <!-- Items --> <img data-keywords="orange" src="https://images.unsplash.com/photo-1628612380382-e6204e135307?q=80&w=500&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="An orange kitten meows while perched on a stone wall" > <img data-keywords="white gray brown" src="https://images.unsplash.com/photo-1595252849939-1ec8070a354a?q=80&w=500&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A multicolored cat walking through the grass stops to look at the camera" > <img data-keywords="white gray" src="https://images.unsplash.com/photo-1583399704033-3db671c65f5c?q=80&w=500&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A white fluffy kitten lays comfortably on the arm of a chair" > <img data-keywords="gray black" src="https://images.unsplash.com/photo-1625060241508-22488e1e9264?q=80&w=500&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A gray tabby lays in a bed and looks out past the camera" > <img data-keywords="white black" src="https://images.unsplash.com/photo-1692901573513-41cda579d689?q=80&w=500&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A black and white kitten lays on its bed and looks at the camera" > <img data-keywords="gray black" src="https://images.unsplash.com/photo-1622273414093-27fd902ac078?q=80&w=500&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A young gray tabby lays on the steps and yawns" > <img data-keywords="white black brown" src="https://images.unsplash.com/photo-1601217155197-0419cd3fd698?q=80&w=500&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A multi-colored kitten poses for a picture" > <img data-keywords="gray brown" src="https://images.unsplash.com/photo-1622273509381-59a1e99afefd?q=80&w=500&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A young tabby rests its eyes while playfully putting its paw up" > <img data-keywords="brown" src="https://images.unsplash.com/photo-1644625986841-fdedeb19d37c?q=80&w=500&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="A brown cat looks up at the camera" > </quiet-search-list> <style> #search-list__keywords { &::part(items) { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(145px, 100%), 1fr)); } img { height: 100%; object-fit: cover; } } </style>
Providing an empty state Jump to heading
An optional empty state will be shown when no results are found. Use the empty
slot to add
text, icons, or illustrations to it. Empty state content is not styled by the component, so feel free to
bring your own look to it.
<quiet-search-list id="search-list__empty"> <!-- Controller --> <quiet-text-field slot="controller" label="Search" type="search" value="foo" clearable pill > <quiet-icon slot="start" name="search"></quiet-icon> </quiet-text-field> <!-- Items --> <div class="item">Luna</div> <div class="item">Oliver</div> <div class="item">Bella</div> <div class="item">Whiskers</div> <div class="item">Maple</div> <div class="item">Sushi</div> <div class="item">Pepper</div> <div class="item">Mittens</div> <div class="item">Shadow</div> <div class="item">Oreo</div> <div class="item">Mochi</div> <div class="item">Nova</div> <div class="item">Tiger</div> <div class="item">Ziggy</div> <!-- The empty state --> <quiet-empty-state slot="empty"> <quiet-icon slot="illustration" name="cat"></quiet-icon> No matching results </quiet-empty-state> </quiet-search-list> <style> #search-list__empty { &::part(items) { flex-direction: row; gap: 0.5rem; } .item { border: var(--quiet-border-style) var(--quiet-border-width) var(--quiet-neutral-stroke-softer); border-radius: var(--quiet-border-radius); background-color: var(--quiet-paper-color); box-shadow: var(--quiet-shadow-softer); padding: 0.5rem 1rem; } /* Empty state styles */ div[slot="empty"] { width: 100%; text-align: center; color: var(--quiet-text-muted); margin-block-start: 1rem; quiet-icon { font-size: 2.5rem; stroke-width: 1px; } } } </style>
Using a custom match function Jump to heading
For more control over the matching algorithm, set the match
attribute to
custom
and provide a callback using the isMatch
property. The function runs on
each item whenever the search query changes.
The callback receives three arguments: query
(the current search term),
content
(the element's searchable content, including its textContent
and
data-keywords
), and el
(a reference to the element being searched).
Luna
Sweet and friendly, loves window watching
Oliver
Adventurous explorer, loves the garden
Milo
Gentle soul who loves napping in sunbeams
Bella
Free spirit, enjoys climbing trees
Charlie
Playful cat who chases laser pointers
Lucy
Independent nature lover, great hunter
No cats match your search
Try searching by name or using the @indoor or @outdoor tags
<quiet-search-list match="custom" id="search-list__custom"> <!-- Controller --> <quiet-text-field slot="controller" label="Search cats" description="Add @indoor or @outdoor to filter by environment" type="search" clearable pill > <quiet-icon slot="start" name="search"></quiet-icon> </quiet-text-field> <!-- Items --> <div class="item" data-environment="indoor"> <h4>Luna</h4> <p>Sweet and friendly, loves window watching</p> </div> <div class="item" data-environment="outdoor"> <h4>Oliver</h4> <p>Adventurous explorer, loves the garden</p> </div> <div class="item" data-environment="indoor"> <h4>Milo</h4> <p>Gentle soul who loves napping in sunbeams</p> </div> <div class="item" data-environment="outdoor"> <h4>Bella</h4> <p>Free spirit, enjoys climbing trees</p> </div> <div class="item" data-environment="indoor"> <h4>Charlie</h4> <p>Playful cat who chases laser pointers</p> </div> <div class="item" data-environment="outdoor"> <h4>Lucy</h4> <p>Independent nature lover, great hunter</p> </div> <!-- Empty state --> <quiet-empty-state slot="empty"> <quiet-icon slot="illustration" name="cat"></quiet-icon> <h4>No cats match your search</h4> <p><small>Try searching by name or using the @indoor or @outdoor tags</small></p> </quiet-empty-state> </quiet-search-list> <script> const searchList = document.getElementById('search-list__custom'); // A very contrived custom match function searchList.isMatch = (query, content, el) => { query = query.toLowerCase().trim(); // If no query, show all items if (!query) return true; const environment = el.getAttribute('data-environment'); const envMatch = query.match(/@(indoor|outdoor)/); // Look for @indoor or @outdoor in the query if (envMatch) { // Remove the environment tag from the query for text search const searchEnvironment = envMatch[1]; const textQuery = query.replace(/@(indoor|outdoor)/, '').trim(); // Must match both environment and text (if any text remains) return environment === searchEnvironment && (!textQuery || content.toLowerCase().includes(textQuery)); } // If no environment tag was found, fall back to text search return content.toLowerCase().includes(query); }; </script> <style> #search-list__custom { &::part(items) { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(200px, 100%), 1fr)); gap: 1rem; } .item { border: var(--quiet-border-style) var(--quiet-border-width) var(--quiet-neutral-stroke-softer); border-radius: var(--quiet-border-radius); background-color: var(--quiet-paper-color); box-shadow: var(--quiet-shadow-softer); padding: 1rem; h4 { margin: 0; font-size: 1.125rem; } p { color: var(--quiet-text-muted); margin: 0.5rem 0; font-size: 0.875rem; margin-block-end: 0; } } div[slot="empty"] { text-align: center; color: var(--quiet-text-muted); margin-block-start: 1rem; quiet-icon { font-size: 2.5rem; stroke-width: 1px; } small { display: block; margin-top: 0.5rem; } } } </style>
Using an external controller Jump to heading
In some cases, the controller might need to exist outside of the component. In this case, give the
controller an ID and set the search list's controller
attribute to match.
<quiet-text-field id="external-controller" label="Search" description="Results will update as you type" ></quiet-text-field> <quiet-search-list controller="external-controller" id="search-list__external"> <div>Item 1</div> <div>Item 2</div> <div>Item 3</div> </quiet-search-list> <style> #search-list__external { margin-block-start: 1rem; &::part(items) { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(180px, 100%), 1fr)); } > div { border: var(--quiet-border-style) var(--quiet-border-width) var(--quiet-neutral-stroke-softer); border-radius: var(--quiet-border-radius); background-color: var(--quiet-paper-color); box-shadow: var(--quiet-shadow-softer); text-align: center; padding: 1rem; } } </style>
When using an external controller, make sure the position of it makes sense in reference to the search list. It should usually come immediately before the search list to ensure all users can interact with it properly.
API Jump to heading
Importing Jump to heading
The autoloader is the recommended way to import components but, if you prefer to do it manually, the following code snippets will be helpful.
To manually import <quiet-search-list>
from the CDN, use the following code.
import 'https://cdn.jsdelivr.net/npm/@quietui/quiet@1.0.0/dist/components/search-list/search-list.js';
To manually import <quiet-search-list>
from npm, use the following code.
import '@quietui/quiet/dist/components/search-list/search-list.js';
Slots Jump to heading
Search List supports the following slots. Learn more about using slots
Name | Description |
---|---|
(default) |
One or more elements to be searched. Each element must be a direct descendent of the host, i.e. do
not wrap items in other containers. If desired, you can apply flex and grid styles to the
items part to control how items appear in the list. By default, items will be displayed
in a flex column.
|
controller
|
A <quiet-text-field> or <input> element that will control the
search list.
|
empty
|
Optional content to display when the search yields no results. |
Properties Jump to heading
Search List has the following properties that can be set with corresponding attributes. In many cases, the attribute's name is the same as the property's name. If an attribute is different, it will be displayed after the property. Learn more about attributes and properties
Property / Attribute | Description | Reflects | Type | Default |
---|---|---|---|---|
controller
|
In most cases, you should slot the controller into the controller slot. However, when
the controller must exist outside the search list, you can set this property to the ID of an
external <input> or <quiet-text-field>
element instead.
|
|
string
|
|
match
|
The search behavior to use when finding a matching item. The exact search is
case-insensitive but requires an exact match. The fuzzy search is more forgiving to
typos. When using custom , you can set the isMatch property to a custom
function to determine if the provided query matches the element's content.
|
|
'exact'
|
'exact'
|
debounce
|
The time in milliseconds to use for debouncing the search results while the user types. |
|
number
|
300
|
isMatch
|
A custom search function you can provide to change the search behavior. The function is applied to
each item when the search query changes. The query argument is the current search term,
content is a string containing the element's searchable content, including its
textContent and data-keywords" , and el is the element
being searched. Property only.
|
|
(query: string, content: string, el: Element) => boolean
|
Methods Jump to heading
Search List supports the following methods. You can obtain a reference to the element and call them like functions in JavaScript. Learn more about methods
Name | Description | Arguments |
---|---|---|
setQuery() |
Sets the search query and updates the results. To clear the search, set this to an empty string. |
query:
|
CSS parts Jump to heading
Search List exposes internal elements that can be styled with CSS using the selectors shown below. Learn more about CSS parts
Name | Description | CSS selector |
---|---|---|
items |
The container that wraps the slotted items. Displays as a flex column by default. |
::part(items)
|
Custom States Jump to heading
Search List has the following custom states. You can target them with CSS using the selectors shown below. Learn more about custom states
Name | Description | CSS selector |
---|---|---|
empty |
Applied when a query is entered and no matching results are found. |
:state(empty)
|