Formsstable.search
Search field
A search input with a magnifier icon and a visually hidden label, for filtering lists.
Sits above a list or table and filters it ("Mitglieder suchen"). The label is visually hidden because the icon and placeholder make the purpose obvious — but it is always present for screen readers.
Filter as the user types (debounced ~250 ms) for local lists, or on Enter for server searches. Put the query into the URL (?q=…) so reload and back keep it (UX guide §3.3).
When nothing matches, show an empty state that repeats the query and offers to reset filters ("Keine Mitglieder für «Mueler»"). Announce result counts in a polite live region if the list updates while typing.
Examples
Member search
Data
render({
"id": "member-search",
"label": "Mitglieder suchen",
"placeholder": "Name, z. B. Müller",
"controls": "member-list"
})Markup
<div class="search">
<svg class="icon" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#search"/></svg>
<label class="u-visually-hidden" for="member-search">Mitglieder suchen</label>
<input class="input" id="member-search" name="q" type="search" enterkeyhint="search" autocomplete="off" placeholder="Name, z. B. Müller" aria-controls="member-list">
</div>With query from the URL
Data
render({
"id": "exercise-search",
"label": "Übungen suchen",
"value": "Dollyo Chagi",
"placeholder": "Übung oder Technik"
})Markup
<div class="search">
<svg class="icon" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#search"/></svg>
<label class="u-visually-hidden" for="exercise-search">Übungen suchen</label>
<input class="input" id="exercise-search" name="q" type="search" enterkeyhint="search" autocomplete="off" value="Dollyo Chagi" placeholder="Übung oder Technik">
</div>Data contract
What render(data) accepts — validated against this JSON Schema in development and tests. Unknown fields are rejected. The same schema is in components.json.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | required | — | Id of the input. Must be unique on the page. pattern ^[A-Za-z][\w-]*$ |
label | string | required | — | Accessible label, visually hidden ("Mitglieder suchen"). min 1 chars |
name | string | optional | "q" | Form field / query-string name. |
value | string | optional | — | Current query (e.g. restored from the URL). |
placeholder | string | optional | — | Example of what can be searched ("Name, z. B. Müller"). Not a substitute for the label. |
controls | string | optional | — | Id of the list or table the search filters (aria-controls).pattern ^[A-Za-z][\w-]*$ |
JSON Schema
{
"type": "object",
"additionalProperties": false,
"required": [
"id",
"label"
],
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z][\\w-]*$",
"description": "Id of the input. Must be unique on the page."
},
"label": {
"type": "string",
"minLength": 1,
"description": "Accessible label, visually hidden (\"Mitglieder suchen\")."
},
"name": {
"type": "string",
"default": "q",
"description": "Form field / query-string name."
},
"value": {
"type": "string",
"description": "Current query (e.g. restored from the URL)."
},
"placeholder": {
"type": "string",
"description": "Example of what can be searched (\"Name, z. B. Müller\"). Not a substitute for the label."
},
"controls": {
"type": "string",
"pattern": "^[A-Za-z][\\w-]*$",
"description": "Id of the list or table the search filters (`aria-controls`)."
}
}
}Markup & states
Root: div.search — usable without render() by writing the markup directly.
Attributes
| Attribute | Values | Default | Description |
|---|---|---|---|
type="search" (on input) | — | — | Search keyboard on phones, native clear button. |
enterkeyhint="search" | — | — | Enter key labelled "Suchen" on phones. |
aria-controls (on input) | id | — | The filtered list. |
Parts
| Part | Description |
|---|---|
svg.icon | Magnifier, decorative, positioned inside the input. |
label.u-visually-hidden | Hidden label bound with for. |
.input | The search input with extra start padding for the icon. |
States
| State | Description |
|---|---|
:focus-visible | Global focus ring (from .input). |
Accessibility
- A real
<label for>exists, only visually hidden — the placeholder is not the label. - The icon is
aria-hidden. - If results update while typing, announce the count in a polite live region ("12 Mitglieder gefunden").
Guidance
Related: Form field · List · Table · Empty state