
rich-input is a dependency-free Web Component for search fields that mix free text with structured keyword:value filters.
It autocompletes filter names, combinators, and allowed values, highlights recognized query tokens inside the field, and uses nested <datalist> elements for filter configuration.
Queries can also use configurable prefix operators, boolean combinators, and grouping delimiters.
Rich option markup can display logos or avatars in suggestion rows, and the component participates in standard form submission.
Shadow Parts expose the control for CSS customization, while getParsedQuery() returns the raw query and an ordered token list for application logic.
Features
- Free text and structured
keyword:valuefilters in one search field. - Autocomplete for filter names, combinators, and filter values, with matching based on the complete active token.
- Declarative
<datalist>configuration with runtime updates. - Configurable prefix operators, boolean combinators, and grouping delimiter pairs.
- 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 styling for filter values, operators, combinators, delimiters, keywords, and invalid tokens through the CSS Custom Highlight API.
- Shadow Parts for control and suggestion styling.
- 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. The optional syntax attributes configure operators, combinators, and delimiter pairs.
<rich-input
name="q"
operators="-"
combinators="AND OR"
delimiters="()"
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. The configured syntax can group filters, join them with boolean combinators, or prefix a filter with an operator:
(brand:"Northstar Audio" OR year:2026) AND -category:Speakers
Datalist & Syntax Configuration
| Element / Attribute | Type | Description |
|---|---|---|
<rich-input operators="..."> | string | Space-separated single-character prefix operators. Defaults to "-". Set an empty value to disable operators. |
<rich-input combinators="..."> | string | Space-separated boolean query combinators such as AND OR NOT. The default is empty. |
<rich-input delimiters="..."> | string | Space-separated opening/closing delimiter pairs such as () [] {}. Defaults to "()". Set an empty value to disable delimiters. |
<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 also registers highlight names for operators, combinators, delimiters, keyword prefixes, and invalid tokens.
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-operator) {
color: #e11d48;
}
::highlight(rich-input-combinator) {
color: #7c3aed;
}
::highlight(rich-input-delimiter) {
color: #0284c7;
}
::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) | Leading icon element. Customize the default search icon with CSS content or background. |
::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-combinator) | Combinator 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. |
Customize the Leading Icon
The leading search icon is exposed through ::part(icon). Replace it with CSS content or a custom background image.
rich-input::part(icon) {
content: "🎵";
}
/* Or use a custom image */
rich-input::part(icon) {
content: "";
background: url("music-note.svg") no-repeat center / contain;
}
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() returns the raw query string and an ordered tokens array. Tokens can represent keywords, combinators, delimiters, text, and whitespace.
const input = document.querySelector('rich-input');
input.value = '-brand:Acme OR year:2026';
console.log(input.getParsedQuery());
The result uses this structure:
{
raw: '-brand:Acme OR year:2026',
tokens: [
{
type: 'keyword',
operator: '-',
keyword: 'brand',
innerValue: 'Acme',
start: 0,
end: 11
},
{
type: 'whitespace',
raw: ' ',
start: 11,
end: 12
},
{
type: 'combinator',
raw: 'OR',
combinator: 'OR',
start: 12,
end: 14
},
{
type: 'whitespace',
raw: ' ',
start: 14,
end: 15
},
{
type: 'keyword',
operator: null,
keyword: 'year',
innerValue: '2026',
start: 15,
end: 24
}
]
}
Update Datalists at Runtime
rich-input watches its datalists and syntax attributes for changes. Insert, remove, or update configuration after initialization to refresh the available query syntax 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. |
operators | string[] | string | Gets or sets prefix operators for this instance. Defaults to RichInput.operators (['-']). Set null to clear the local override. |
RichInput.operators | string[] | string | Static global default operators for instances with no local override. |
combinators | string[] | string | Gets or sets boolean combinators for this instance. Defaults to RichInput.combinators ([]). Set null to clear the local override. |
RichInput.combinators | string[] | string | Static global default combinators for instances with no local override. |
delimiters | string[] | string | Gets or sets delimiter pairs for this instance. Defaults to RichInput.delimiters (['()']). Set null to clear the local override. |
RichInput.delimiters | string[] | string | Static global default delimiter pairs for instances with no local override. |
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, tokens } for the current query. |
getKeywords() | Returns the configured datalist keyword definitions and options. |
getOperators() / setOperators(operators) | Gets or sets operators for this instance. Static RichInput.getOperators() and RichInput.setOperators() manage the global defaults. |
getCombinators() / setCombinators(combinators) | Gets or sets combinators for this instance. Static RichInput.getCombinators() and RichInput.setCombinators() manage the global defaults. |
getDelimiters() / setDelimiters(delimiters) | Gets or sets delimiter pairs for this instance. Static RichInput.getDelimiters() and RichInput.setDelimiters() manage the global defaults. |
getActiveValueRanges() | Returns active value highlight range descriptors. |
getActiveKeywordRanges() | Returns active keyword highlight range descriptors. |
getActiveOperatorRanges() | Returns active operator highlight range descriptors. |
getActiveCombinatorRanges() | Returns active combinator highlight range descriptors. |
getActiveDelimiterRanges() | Returns active delimiter highlight range descriptors. |
getActiveInvalidRanges() | Returns active invalid-token highlight range descriptors. |
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,
operator,
keyword,
value,
label,
query
} = event.detail;
console.log(operator, 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.
Changelog
v1.4 (09/21/2026)
- Support for (configurable) operators (e.g. +, -, ~, …)
- Support for (configurable) combinators (e.g. AND, OR, NOT, …)
- Support for (configurable) delimiters (e.g. (), {}, …)
- Only expose the raw string and tokenized tokens in getParsedQuery()
- Scope default shadow DOM ::highlight(…) rules to :host::highlight(…)
- Set the default text color on .popover and changed .suggestion-item / .suggestion-title to inherit
- Remove the slots and simply allow customization of the icon through ::part(icon)







