A drop-in search input for OutSystems Reactive Web that fires its search event only after the user pauses typing — so you get one backend call for "hello" instead of five for "h", "he", "hel", "hell", "hello". Includes a minimum-length threshold and a clear button. Pure client-side, no dependencies.
A naive search input fires on every keystroke. Type "hello" and the backend gets hit five times, results flicker as they arrive out of order, and the app feels heavy. Debounced Search Input waits for typing to pause for a configurable interval before firing its OnSearch event, so the backend gets called exactly once per pause with the current text. It's a tiny piece of code with an outsized impact on performance and perceived quality.
OnSearch
type=button
.oap
DebouncedSearchInput Placeholder = "Search products..." DebounceMs = 400 MinimumLength = 2 Handle OnSearch(SearchTerm): MyQuery = SearchTerm Refresh MyAggregate // your search results query
Drop the block on a screen, wire OnSearch to update your query variable, and refresh whatever list of results you show below. That's the whole integration.
DebouncedSearchInput
Value
""
Placeholder
"Search..."
DebounceMs
300
MinimumLength
0
ShowClearButton
True
SearchTerm
On every keystroke, the block cancels any pending "search" timer and starts a new one for DebounceMs. Only the last keystroke's timer survives to fire, and that's the one whose value gets passed to OnSearch. This is called debouncing — waiting for the user to pause before acting.
Two additional behaviors on top of the debounce:
This block does debouncing, which is what you want for search. If you need throttling, you're building a different component.
The default of 300ms is a good sweet spot — long enough that a normal typist gets one search per word, short enough that the result feels responsive. Guidance for other cases:
Above ~1000ms the delay starts to feel like lag rather than debouncing.
Setting MinimumLength = 2 or 3 is a common pattern for two reasons:
MinimumLength = 2
3
For long queries or specific searches (people names, product codes), MinimumLength = 3 is a good default. For quick filters where any input is meaningful, keep it at 0.
MinimumLength = 3
When ShowClearButton = True (the default), an X icon appears inside the input on the right whenever there's text. Clicking it:
ShowClearButton = True
OnSearch("")
Set ShowClearButton = False if your design provides its own way to clear the search (or if you don't want it to be clearable that way).
ShowClearButton = False
The block uses these classes: dsi-wrap (the wrapper), dsi-input (the text input), dsi-clear (the clear button), and dsi-x-icon (the X inside the clear button). Override in your app theme to customize:
dsi-wrap
dsi-input
dsi-clear
dsi-x-icon
.dsi-input { border-radius: 999px; padding-left: 40px; } /* pill shape */ .dsi-input:focus { border-color: #10b981; box-shadow: 0 0 0 3px rgba(16,185,129,.25); } .dsi-clear { color: #ef4444; }
The X icon is drawn with CSS pseudo-elements and adopts the button's text color via currentColor, so themeing the color just works.
currentColor
The input is a real HTML input, so it's keyboard-focusable, screen-reader-friendly, and honors browser autofill and spellcheck settings. The clear button is a real <button> with type="button" (won't submit forms) and an aria-label. Both are reachable via Tab and activate with Enter or Space.
<button>
type="button"
aria-label
For screen-reader users, consider pairing the block with an aria-live region elsewhere on your screen that announces "N results found" when results update — the standard pattern for accessible search.
aria-live
OnSearch fires on every keystroke.Check DebounceMs isn't 0. Also, if the block was re-initialized (a duplicate On Ready call), you may have two listeners attached — the block guards against this with an internal flag, but if you see doubles, republish to make sure the latest block code is loaded.
Nothing happens when I type.If MinimumLength is above 0, OnSearch won't fire until you've typed that many characters. Also confirm you're actually handling OnSearch on the consuming screen — the block fires the event; you have to react to it.
Enter doesn't work.Enter fires OnSearch immediately (skipping the debounce). If your form has other Enter behavior, that may take precedence — check the form's own submit handling.
Clear button doesn't appear.It only appears when there's text in the field (there's nothing to clear otherwise). Also confirm ShowClearButton = True.
Results still lag behind by a beat.That's how debouncing works — the last N milliseconds of typing haven't fired yet. Lower DebounceMs if you want faster feedback (at the cost of more backend calls).
DebounceMs = 300
MinimumLength = 0
Published on the OutSystems Forge as open, reusable code. Free to use and adapt in your OutSystems projects.