
rich-input is a dependency-free Web Component for search fields that mix free text with structured keyword:value filters.
It autocompletes both filter names and their allowed values, highlights recognized filters inside the field, and uses nested <datalist> elements for configuration.
Rich option markup can display logos or avatars in suggestion rows, and the component participates in standard form submission.
Shadow Parts and slots expose the control for CSS and custom content, while getParsedQuery() splits the current value into free text and structured filters.
Features
- Free text and structured
keyword:valuefilters in one search field. - Autocomplete for both filter names and filter values, with matching based on the complete active token.
- Declarative
<datalist>configuration with runtime updates. - Automatic quotes for selected values that contain spaces, plus highlighting for invalid keywords and values.
- Images, icons, and other option markup inside suggestion rows.
- Native form submission,
FormData, and form reset behavior. - In-input filter styling through the CSS Custom Highlight API.
- Shadow Parts and leading/trailing slots for control customization.
- ARIA combobox states and keyboard controls for Arrow keys, Enter, Tab, and Escape.
How To Use It
Installation
Load the ES module directly in the HTML document:
<script type="module" src="https://esm.sh/rich-input/es2022/rich-input.bundle.mjs"> </script>
For npm projects:
npm install rich-input
Import the package from your app entry file:
import 'rich-input';
Basic Usage
Place one or more <datalist> elements inside <rich-input>. Each datalist ID becomes a filter keyword.
<rich-input
name="q"
placeholder="Search products..."
>
<datalist id="brand" label="Brand">
<option value="Acme"></option>
<option value="Northstar Audio"></option>
<option value="Pixel Works"></option>
</datalist>
<datalist id="year" label="Release Year" data-type="number">
<option value="2026"></option>
<option value="2025"></option>
<option value="2024"></option>
</datalist>
<datalist id="category" label="Category">
<option value="Headphones"></option>
<option value="Portable Audio"></option>
<option value="Speakers"></option>
</datalist>
</rich-input>
Typing brand: opens the values configured for that filter. Free text can appear beside structured filters:
wireless brand:"Northstar Audio" year:2026
Datalist Configuration
| Element / Attribute | Type | Description |
|---|---|---|
<datalist id="..."> | string | Required keyword identifier. id="brand" creates the brand: filter. Matching is case-insensitive. |
<datalist label="..."> | string | Human-readable label shown in the suggestions panel. Defaults to a capitalized form of the ID. |
<datalist data-type="..."> | string | Sets the value type to string or number. The default is string. |
<option value="..."> | string | Value inserted into the query. Values containing spaces receive quotes automatically. |
<option label="..."> | string | Optional descriptive text for the option. |
<option> children | Node | Optional custom markup such as an image or icon rendered with the suggestion. |
Rich Suggestions With Images
Child markup inside an <option> appears in the suggestion row. Selecting the suggestion inserts the option’s value into the search field.
<rich-input placeholder="Search catalog...">
<datalist id="brand" label="Brand">
<option value="Northstar Audio">
<img
src="northstar.svg"
width="40"
height="40"
alt="Northstar Audio"
>
Northstar Audio
</option>
<option value="Pixel Works">
<img
src="pixel-works.svg"
width="40"
height="40"
alt="Pixel Works"
>
Pixel Works
</option>
</datalist>
</rich-input>
Style Keyword Highlights
Each configured keyword can have its own ::highlight() rule. rich-input-keyword styles filter prefixes, and rich-input-invalid marks unrecognized filters or values.
The Custom Highlight API accepts text-oriented properties such as color, background-color, text-decoration, text-shadow, -webkit-text-stroke-color, -webkit-text-stroke-width, and -webkit-text-fill-color.
-:highlight(brand) {
background-color: #dbeafe;
color: #1e40af;
}
-:highlight(year) {
background-color: #fef3c7;
color: #92400e;
}
-:highlight(rich-input-keyword) {
color: #64748b;
}
-:highlight(rich-input-invalid) {
text-decoration: underline wavy #dc2626;
text-decoration-skip-ink: none;
}
Instance-specific Highlight Styles
Place a <style> element directly inside <rich-input> when one instance needs its own highlight rules.
<rich-input
value='brand:"Northstar Audio"'
placeholder="Search products..."
>
<style>
-:highlight(brand) {
background-color: #dbeafe;
color: #1e40af;
}
</style>
<datalist id="brand" label="Brand">
<option value="Northstar Audio"></option>
<option value="Pixel Works"></option>
</datalist>
</rich-input>
Style the Component With Shadow Parts
rich-input::part(control) {
border: 2px solid #2563eb;
border-radius: 9999px;
padding: 0 1rem;
}
rich-input::part(input) {
font-family: monospace;
font-size: 1rem;
}
rich-input::part(popover) {
border-radius: 12px;
box-shadow: 0 12px 30px rgb(0 0 0 / 0.15);
}
rich-input::part(suggestion-item-active) {
background-color: #dbeafe;
}
rich-input::part(suggestion-image) {
width: 2rem;
height: 2rem;
border-radius: 50%;
object-fit: cover;
}
Available Shadow Parts
| Shadow Part | Description |
|---|---|
::part(control) | Outer control containing the icon, input, and clear button. |
::part(input) | Internal text input. |
::part(icon) | Default search icon. |
::part(clear-button) | Clear button displayed when text is present. |
::part(popover) | Autocomplete suggestions panel. |
::part(suggestions-header) | Header at the top of the suggestions panel. |
::part(suggestions-list) | <ul> containing the suggestions. |
::part(suggestion-item) | Individual suggestion row. |
::part(suggestion-item-active) | Focused or hovered suggestion row. |
::part(suggestion-item-selected) | Suggestion matching the current input value. |
::part(suggestion-keyword) | Keyword text inside a suggestion. |
::part(suggestion-value) | Value text inside a suggestion. |
::part(suggestion-content) | Content wrapper inside a suggestion row. |
::part(suggestion-image) | Image or icon rendered inside a rich suggestion. |
Slots
Use slot="leading" to replace the default search icon. slot="trailing" places custom content after the clear button.
<rich-input placeholder="Search products...">
<svg
slot="leading"
width="18"
height="18"
viewBox="0 0 24 24"
aria-hidden="true"
>
<!-- icon paths -->
</svg>
<datalist id="category" label="Category">
<option value="Audio"></option>
<option value="Computers"></option>
</datalist>
<kbd slot="trailing">/</kbd>
</rich-input>
| Slot | Description |
|---|---|
leading | Custom leading icon or content. The default search icon acts as fallback content. |
trailing | Custom content rendered after the clear button. |
| Default slot | Holds <datalist> configuration elements. |
Form Integration
Set name to include the current rich-input value in standard form submission and FormData.
<form id="product-search" action="/search" method="GET">
<rich-input name="q" placeholder="Search products...">
<datalist id="category" label="Category">
<option value="Audio"></option>
<option value="Computers"></option>
</datalist>
</rich-input>
<button type="submit">Search</button>
</form>
<script>
const form = document.querySelector('#product-search');
form.addEventListener('submit', function (event) {
event.preventDefault();
const data = new FormData(form);
console.log(data.get('q'));
});
</script>
Parse the Current Query
getParsedQuery() separates ordinary search text from recognized filters.
const input = document.querySelector('rich-input');
input.value =
'wireless brand:"Northstar Audio" year:2026';
console.log(input.getParsedQuery());
The result uses this structure:
{
raw: 'wireless brand:"Northstar Audio" year:2026',
text: 'wireless',
keywords: {
brand: ['Northstar Audio'],
year: ['2026']
},
tokens: [
// parsed token data
]
}
Update Datalists at Runtime
rich-input watches its slotted configuration for changes. Insert, remove, or update datalists after initialization to refresh the available filters and suggestions.
const input = document.querySelector('rich-input');
const datalist = document.createElement('datalist');
datalist.id = 'rating';
datalist.label = 'Rating';
for (const value of ['5', '4', '3']) {
const option = document.createElement('option');
option.value = value;
datalist.append(option);
}
input.append(datalist);
JavaScript API
Properties
| Property | Type | Description |
|---|---|---|
value | string | Gets or sets the current input value and refreshes highlights and form data. |
placeholder | string | Gets or sets the placeholder text. |
disabled | boolean | Enables or disables the control. |
name | string | Sets the form field name. |
selectionStart | number | Gets the start index of the current selection or caret. |
selectionEnd | number | Gets the end index of the current selection or caret. |
Methods
| Method | Description |
|---|---|
getParsedQuery() | Returns { raw, text, keywords, tokens } for the current query. |
getKeywords() | Returns the configured datalist keyword definitions and options. |
focus(options) | Focuses the internal input. |
blur() | Removes focus from the internal input. |
select() | Selects all text in the field. |
setSelectionRange(start, end, direction) | Sets the caret or text selection range. |
Events
| Event | Description |
|---|---|
input | Fires when the value changes. The event bubbles and is composed. |
change | Fires when a value change is committed or the field loses focus. |
search | Fires when Enter is pressed with the suggestions panel closed. |
rich-input-select | Fires when an autocomplete suggestion is selected. |
rich-input-select exposes the selected suggestion through event.detail:
const input = document.querySelector('rich-input');
input.addEventListener('rich-input-select', function (event) {
const {
type,
keyword,
value,
label,
query
} = event.detail;
console.log(keyword, value, query);
});
Alternatives & Related Resources
- Accessible Autocomplete Component In Vanilla JavaScript – Autocomplete
- Modern Tags Input/Multiple Select Component – multi-input
- Advanced Feature-rich Autocomplete JavaScript Library – awesomplete
- Lightweight JS Autocomplete & Autosuggest: Ajax & Local Data – Ajax Autocomplete Vanilla
- Real-time Input Colorization Web Component – RichInput
FAQs
Q: Can rich-input load autocomplete values from an API?
A: Remote data is not a built-in data-source mode. Fetch the values in application code, populate or update the nested <datalist>, and rich-input will detect those changes.
Q: Can rich-input submit through a normal HTML form?
A: Yes. Set its name and the current value participates in standard form submission and FormData.
Q: Does rich-input always use a native <input> internally?
A: The OpaqueRange implementation uses a native text input. On browsers that need its highlighting fallback, the component can switch to an adapted single-line contenteditable element inside its Shadow DOM.







