Skip to main content

SearchField

A debounced search input with a leading search icon, optional loading spinner, and built-in clear button. Bind via an atom (recommended) or controlled value / setValue.

Quick start
import { SearchField } from "mates-ui";
import { atom } from "mates";





const query = atom("");

SearchField({
  atom: query,
  debounceMs: 300,
  placeholder: "Search users…",
  label: "Search",
});

Atom binding

import { html, atom, renderApp } from 'mates';
import { SearchField } from 'mates-ui';

const App = () => {
const query = atom("");
return () => html`
<x-col gap="var(--md-space-4)" style="max-width:28rem;width:100%;">
${SearchField({ atom: query, debounceMs: 300, placeholder: "Search users…", label: "Search" })}
</x-col>
`;
};

renderApp(App, document.getElementById('app'));

value / setValue binding

import { html, atom, renderApp } from 'mates';
import { SearchField } from 'mates-ui';

const App = () => {
const query = atom("");
return () => html`
<x-col gap="var(--md-space-4)" style="max-width:28rem;width:100%;">
${SearchField({ value: query(), setValue: (v) => query.set(v), debounceMs: 500, label: "Search", placeholder: "Search products…" })}
</x-col>
`;
};

renderApp(App, document.getElementById('app'));

Debounce delay comparison

import { html, atom, renderApp } from 'mates';
import { SearchField } from 'mates-ui';

const App = () => {
const instant = atom("");
const fast = atom("");
const slow = atom("");
return () => html`
<x-row gap="var(--md-space-5)" wrap style="align-items:flex-start;">
<x-col gap="var(--md-space-2)" style="min-width:14rem;">
${SearchField({ atom: instant, debounceMs: 0, placeholder: "Instant…", label: "0 ms" })}
</x-col>
<x-col gap="var(--md-space-2)" style="min-width:14rem;">
${SearchField({ atom: fast, debounceMs: 300, placeholder: "300 ms…", label: "300 ms (default)" })}
</x-col>
<x-col gap="var(--md-space-2)" style="min-width:14rem;">
${SearchField({ atom: slow, debounceMs: 1000, placeholder: "1 000 ms…", label: "1 000 ms" })}
</x-col>
</x-row>
`;
};

renderApp(App, document.getElementById('app'));

Loading state

import { html, atom, renderApp } from 'mates';
import { SearchField, FilledButton } from 'mates-ui';

const App = () => {
const fixed = atom("results loading");
const toggleQ = atom("toggle me");
const loading = atom(false);
return () => html`
<x-col gap="var(--md-space-5)" style="max-width:28rem;width:100%;">
${SearchField({ atom: fixed, loading: true, label: "Always loading", placeholder: "Search…" })}
<x-col gap="var(--md-space-3)">
${SearchField({ atom: toggleQ, loading: loading(), label: "Toggle loading" })}
${FilledButton(loading() ? "Stop loading" : "Start loading", {
onClick: () => loading.set((v) => !v),
})}
</x-col>
</x-col>
`;
};

renderApp(App, document.getElementById('app'));

Sizes

import { html, atom, renderApp } from 'mates';
import { SearchField } from 'mates-ui';

const App = () => {
const sm = atom(""); const md = atom(""); const lg = atom("");
return () => html`
<x-col gap="var(--md-space-4)" style="max-width:28rem;width:100%;">
${SearchField({ atom: sm, size: "sm", placeholder: "Small…", label: "sm" })}
${SearchField({ atom: md, size: "md", placeholder: "Medium…", label: "md" })}
${SearchField({ atom: lg, size: "lg", placeholder: "Large…", label: "lg" })}
</x-col>
`;
};

renderApp(App, document.getElementById('app'));

Variants

import { html, atom, renderApp } from 'mates';
import { SearchField } from 'mates-ui';

const App = () => {
const o = atom(""); const f = atom("");
return () => html`
<x-row gap="var(--md-space-6)" wrap style="align-items:flex-start;">
<x-col gap="var(--md-space-2)" style="min-width:16rem;">
${SearchField({ atom: o, variant: "outlined", label: "Outlined (default)", placeholder: "Search…" })}
</x-col>
<x-col gap="var(--md-space-2)" style="min-width:16rem;">
${SearchField({ atom: f, variant: "filled", label: "Filled", placeholder: "Search…" })}
</x-col>
</x-row>
`;
};

renderApp(App, document.getElementById('app'));

Disabled + Error

import { html, atom, renderApp } from 'mates';
import { SearchField } from 'mates-ui';

const App = () => {
const disabled = atom("pre-filled query");
const error = atom("???");
const helper = atom("");
return () => html`
<x-row gap="var(--md-space-5)" wrap style="align-items:flex-start;">
<x-col gap="var(--md-space-2)" style="min-width:16rem;">
${SearchField({ atom: disabled, disabled: true, label: "Disabled", placeholder: "Search…" })}
</x-col>
<x-col gap="var(--md-space-2)" style="min-width:16rem;">
${SearchField({ atom: error, errorText: "Query contains invalid characters.", label: "Error", placeholder: "Search…" })}
</x-col>
<x-col gap="var(--md-space-2)" style="min-width:16rem;">
${SearchField({ atom: helper, helperText: "Results update after 300 ms.", label: "Helper", placeholder: "Search…" })}
</x-col>
</x-row>
`;
};

renderApp(App, document.getElementById('app'));

SearchField

import { SearchField } from "mates-ui";
Prop Type Default Description
atom Atom<string> Reactive atom bound to the field. Recommended binding mode — reads initial value and writes debounced updates back.
attr AttrMap HTML attributes on the root element.
classes string | string[] Extra root classes — joined and appended after built-in classes.
classes string CSS class added to the root element.
clearable boolean true Shows a × clear button when the field has a value.
clearSearchLabel string "Clear search" Aria-label of the clear (×) button.
data DataAttrMap Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
debounceMs number 300 Delay in ms before the atom / setValue is updated. Set to 0 for instant (every keystroke).
disabled boolean false Disables the input.
errorText string Error message — puts the field into error state.
helperText string Hint text shown below the field.
id string Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
label string Floating label text.
loading boolean false Replaces the search icon with a circular spinner.
on OnEventMap DOM event handlers on the root element.
onClick (e: MouseEvent) => void Shorthand for `on.click`.
placeholder string "Search…" Input placeholder.
setValue (value: string) => void Called after the debounce delay with the committed query. Only used when atom is NOT provided.
size "sm" | "md" | "lg" "md" Controls height and font size.
style StyleMap Shorthand for `style`. Inline styles applied to the root element.
style StyleMap Inline CSS on the root element.
testId string Overrides the default `data-testid` attribute on the root element.
value string Controlled value (used when no atom is provided). Pass atom() here.
variant "outlined" | "filled" "outlined" Visual style of the field.