Use for search-first selection where the option list is driven by user input — supports both in-memory filtering and async fetch.
Pass filter and slot all <q2-option> children upfront. The component case-insensitively matches each option's display and innerText against the query and toggles hidden automatically.
<q2-search label="Find a fruit" filter clearable>
<q2-option value="apple" display="Apple"></q2-option>
<q2-option value="banana" display="Banana"></q2-option>
<q2-option value="cherry" display="Cherry"></q2-option>
<q2-option value="date" display="Date"></q2-option>
<q2-option value="elderberry" display="Elderberry"></q2-option>
</q2-search>
Listen to tctInput, fetch results, swap the slotted options, and toggle loading while the request is in flight. Debounce the fetch to avoid hammering the server on every keystroke.
<q2-search id="customers" label="Find a customer" placeholder="Type to search"></q2-search>
<script>
const CUSTOMERS = [
{ id: 'c1', name: 'Alice Johnson' },
{ id: 'c2', name: 'Bob Smith' },
{ id: 'c3', name: 'Carol White' },
{ id: 'c4', name: 'David Brown' },
{ id: 'c5', name: 'Eve Martinez' },
];
const el = document.getElementById('customers');
let timer;
el.addEventListener('tctInput', e => {
const q = e.detail.query.trim().toLowerCase();
clearTimeout(timer);
el.innerHTML = '';
if (!q) { el.loading = false; return; }
el.loading = true;
timer = setTimeout(() => {
const results = CUSTOMERS.filter(c => c.name.toLowerCase().includes(q));
el.innerHTML = results.map(r =>
`<q2-option value="${r.id}" display="${r.name}"></q2-option>`).join('');
el.loading = false;
}, 400);
});
</script>
Add multiple to allow selecting more than one option. Combine with filter for in-memory filtering, or with tctInput for async fetch.
<q2-search label="Find fruits" multiple filter>
<q2-option value="apple" display="Apple"></q2-option>
<q2-option value="banana" display="Banana"></q2-option>
<q2-option value="cherry" display="Cherry"></q2-option>
<q2-option value="date" display="Date"></q2-option>
<q2-option value="elderberry" display="Elderberry"></q2-option>
</q2-search>
Add show-selected-toggle to render a header inside the popover that lets the user switch between search results and only their selected options.
<q2-search label="Find fruits" multiple filter show-selected-toggle>
<q2-option value="apple" display="Apple"></q2-option>
<q2-option value="banana" display="Banana"></q2-option>
<q2-option value="cherry" display="Cherry"></q2-option>
<q2-option value="date" display="Date"></q2-option>
<q2-option value="elderberry" display="Elderberry"></q2-option>
</q2-search>
Wrap related <q2-option> children in <q2-optgroup> to render them as a labeled group. Useful for categorized results such as recents vs. suggestions.
<q2-search label="Find a transfer" filter>
<q2-optgroup label="Recent">
<q2-option value="r1" display="Checking → Savings"></q2-option>
<q2-option value="r2" display="Checking → Roth IRA"></q2-option>
</q2-optgroup>
<q2-optgroup label="Saved">
<q2-option value="s1" display="Rent payment"></q2-option>
<q2-option value="s2" display="Loan payment"></q2-option>
</q2-optgroup>
</q2-search>
Pre-slot <q2-option> children to show recent searches before the user types. Use the popover-top slot for persistent actions like "Clear recents", and popover-bottom for footer actions like "View all results".
<q2-search id="recents-search" label="Search">
<div slot="popover-top" style="padding: 8px 12px;">
<q2-btn id="clear-recents-btn" intent="workflow-secondary" style="width: 100%;">Clear recents</q2-btn>
</div>
<q2-option value="r1" display="Recent search 1"></q2-option>
<q2-option value="r2" display="Recent search 2"></q2-option>
</q2-search>
<script>
document.getElementById('clear-recents-btn').addEventListener('click', () => {
const search = document.getElementById('recents-search');
search.querySelectorAll('q2-option').forEach(o => o.remove());
});
</script>
When no options, empty slot, popover-top, or popover-bottom content is provided, the popover is suppressed and q2-search behaves as a styled search input. tctInput still fires on every keystroke.
<q2-search label="Free-text search" placeholder="Type anything"></q2-search>
Set --tct-search-loading-popover-height to reserve space while loading is true, preventing a layout jump when results arrive. In the example below the spinner appears immediately when you start typing.
<q2-search
id="reserve-height-search"
label="Find a customer"
placeholder="Type to search"
style="--tct-search-loading-popover-height: 220px;"
></q2-search>
<script>
const NAMES = ['Alice Johnson', 'Bob Smith', 'Carol White', 'David Brown', 'Eve Martinez'];
const el = document.getElementById('reserve-height-search');
let timer;
el.addEventListener('tctInput', e => {
const q = e.detail.query.trim().toLowerCase();
clearTimeout(timer);
el.innerHTML = '';
if (!q) { el.loading = false; return; }
el.loading = true;
timer = setTimeout(() => {
const results = NAMES.filter(n => n.toLowerCase().includes(q));
el.innerHTML = results.map((n, i) =>
`<q2-option value="n${i}" display="${n}"></q2-option>`).join('');
el.loading = false;
}, 800);
});
</script>
Use popover-alignment to anchor the popover to the left or right edge of the input rather than stretching it to match the input width. Requires --tct-popover-width (in px) on the host; without it the component logs a warning and falls back to input-width. When the viewport is narrower than the defined width, the component automatically falls back to block (input-width) mode.
left anchors the popover's left edge to the input's left edge — useful when the input sits near the right edge of the layout. right anchors the popover's right edge to the input's right edge — useful when the input sits near the left edge.
<div style="display: flex; gap: 16px; align-items: flex-start;">
<q2-search
label="Left-aligned"
popover-alignment="left"
style="--tct-popover-width: 320px; width: 160px;"
filter
>
<q2-option value="apple" display="Apple"></q2-option>
<q2-option value="banana" display="Banana"></q2-option>
<q2-option value="cherry" display="Cherry"></q2-option>
<q2-option value="date" display="Date"></q2-option>
<q2-option value="elderberry" display="Elderberry"></q2-option>
</q2-search>
<q2-search
label="Right-aligned"
popover-alignment="right"
style="--tct-popover-width: 320px; width: 160px; margin-left: 50px;"
filter
>
<q2-option value="apple" display="Apple"></q2-option>
<q2-option value="banana" display="Banana"></q2-option>
<q2-option value="cherry" display="Cherry"></q2-option>
<q2-option value="date" display="Date"></q2-option>
<q2-option value="elderberry" display="Elderberry"></q2-option>
</q2-search>
</div>
The q2-search element has multiple slots that can be used to insert custom content into the component.
An optional slot to display custom content when the popover is open and no options are visible.
An optional slot to display a custom label.
An optional slot for persistent content at the bottom of the popover.
An optional slot for persistent content at the top of the popover (e.g., secondary actions).
The q2-search element has one or more properties that support text localization. Those properties are indicated by the localizable badge in the description.
clearableRenders an icon button when the field is non-empty. Pressing the button clears the query and selection.
disabledDisables all interaction with the field and leverages the disabled visual style of q2-input.
errorsThe presence of errors will mark the field as invalid, putting it into an error state.
filterWhen true, the component case-insensitively matches each <q2-option>'s display and innerText
against the current query and toggles hidden. When false (default), the consumer is expected to
filter or fetch options in response to the tctInput event.
hideLabelhide-labelHides the field's <label> element from view.
Only use when a visible label is impractical.
invalidDetermines whether to show an error state. Its primary use-case is for an unfilled field.
labelThe text that will be used as the label for the field.
listLabellist-labelDetermines the label that is applied to the option list for accessibility purposes.
loadingOpens the popover and shows a spinner inside it until results populate. The clear button is suppressed while loading.
minRowsmin-rowsThe minimum number of rows the component will try to display below or above the component when opened.
multilineOptionsmultiline-optionsEnables text wrapping for q2-option elements. When false, the text truncates and does not wrap.
multipleEnables multi-select functionality.
noResultsMessageno-results-messageThe message displayed when the popover is open and no options are visible. Overridden by the empty slot.
optionalAppends "(optional)" to the field label, and sets aria-required on the nested input tag to false.
placeholderText that appears within the field when it is blurred and empty.
popoverAlignmentpopover-alignmentAligns the popover dropdown to the left or right side of the input. Requires
the --tct-popover-width CSS custom property to be set on the host; without
it q2-search logs a warning and renders the popover at input-width
(the default behavior). When the viewport is narrower than the defined width,
q2-search transparently falls back to input-width so the popover never
overflows the viewport.
popoverMaxHeightpopover-max-heightForce the maximum height of the popover. Interpreted as pixels. If unset or exceeds available space, the component auto-detects the maximum height.
readonlyAppends "(read only)" to the field label, and field becomes unusable but remains focusable.
Takes priority over optional if both are true.
selectedOptionsEach item in this array should correspond to the value of a q2-option element.
Only relevant for multiple (multi-select) implementations.
showSelectedToggleshow-selected-toggleRenders a header inside the popover with a toggle to switch between viewing search
results and only the selected options. Only applies to multiple (multi-select).
valueThe current value for the search. Should correspond to the value of a nested q2-option element.
Only relevant for single-select implementations.
The q2-search element exposes events that can be used to send and receive data from the component based on user interaction.
Emitted when an option is selected or deselected.
When multi-select is enabled, value is undefined and selectedOptions contains the selected values.
Ignore tctVModel — included for Vue v-model compatibility.
{ value: string; selectedOptions: string[]; tctVModel: string | string[]; }Emitted when the popover closes.
voidEmitted on every keystroke with the current query.
{ query: string; }Emitted when the popover opens.
voidThe q2-search element exposes methods that can be used to perform various actions on the component.
Closes the popover.
closePopover() => Promise<void>Opens the popover.
openPopover() => Promise<void>Resets the search to its initial state: clears the input, the selection, and closes the popover. Use this from consumers that treat selection as a one-shot trigger (e.g. navigation) rather than a committed form value.
Emits tctInput with an empty query so listeners can reconcile.
Emits tctClose as a side effect of closing the popover if it is open.
Does not emit tctChange.
reset() => Promise<void>Programmatically focuses the input, sets its value, and emits tctInput.
searchOptions(query: string) => Promise<void>Selects the option(s) with the specified value(s).
If multi-select is enabled and closePopover is true (default), the popover closes after selection.
setValue(values: string | string[], options?: { closePopover?: boolean; }) => Promise<void>Large or Dynamic Option Sets Use q2-search when the full option list is too large to display upfront, or when options are fetched from a server in response to a query (e.g., customer lookup, account search).
Search-First Workflows Ideal when the user already knows what they're looking for and would rather type than browse:
Small, Static Lists If the option set has fewer than ~10 items and the full list can be shown upfront, prefer q2-select — browsing is faster than searching for short lists.
Binary or Near-Binary Choices Avoid for yes/no or two-to-four option fields; use radio buttons or q2-select instead.
searchable)Both components emit tctInput and can drive async fetch. The difference is the interaction model:
| | q2-search | q2-select + searchable |
|---|---|---|
| Default state | Empty input, no list visible | Full option list visible immediately |
| User intent | Knows what they want — types to find it | May browse before deciding |
| Option set size | Large or unbounded (e.g., all customers) | Bounded — full list is meaningful to show |
| Multi-select | Yes (multiple prop) | Yes (multiple prop) |
| Async fetch | Yes | Yes (via tctInput) |
| Best for | Lookup, autocomplete, free-text search | Category, status, role — especially when browsing helps |
Choose q2-search when the full option list is too large or too dynamic to show upfront, or when the workflow is explicitly search-first (e.g., find a customer by name, look up a payee).
Choose q2-select + searchable when the list is bounded enough to browse, but a search shortcut improves usability for power users (e.g., a list of 20–50 accounts where a user might want to type "sav" to jump to savings accounts rather than scroll).
When no options are slotted and no popover-top, popover-bottom, or empty slot content is provided, q2-search suppresses the popover entirely and behaves as a styled search input. tctInput still fires on every keystroke — use this mode to submit a free-text query to a search results page rather than selecting from a dropdown.
Add multiple to allow the user to select more than one option. When blurred, the input shows the first selection's display text with a +N badge for the remaining count. When focused, the badge shows the total count alongside the in-progress query.
Add show-selected-toggle to render a header inside the popover that lets the user switch between viewing search results and only their selected options.
label — do not rely solely on placeholder.placeholder to hint at the expected input format (e.g., "Name or account number").loading for async fetch flows — show it while results are in flight and clear it when options are ready.popover-top slot for persistent actions like "Clear recent searches" or "Search all results".popover-bottom slot for footer actions like "View all results →".empty slot (or noResultsMessage prop) to give users constructive feedback when no results match.The following CSS variables are available to override the default theme styles of the q2-search component.
Many Tecton components consume other components to maintain visual and functional consistency. If you are looking for a CSS variable you think should exist but are not seeing it, it may be defined in one of the dependent components below. Either way, if you think it's something we should expose more conveniently, let us know!
The changelog provides a detailed history of new features, improvements, and bug fixes going back to Tecton 1.30.0. If the button is disabled, it indicates there have been no detectable changes since then.