# Search field

A recess for a question. A glass is printed on its floor at the start, the words run on, an optional count is printed behind an engraved line, and a clear key sits in the far corner while there is something to clear. Escape empties it.

Kit part · Free · https://tactileui.dev/parts/search-field

- Element: `<tui-search-field>`
- Needs: `kit.css` and `kit.mjs` ([how to install the kit](https://tactileui.dev/docs/installation#the-kit))
- Categories: Inputs, Search bars
- Finishes: Aluminium, Graphite, Oak, Walnut, Porcelain, Black, Daylight, Night, Paper, Phosphor, E-paper, AMOLED
- Volumes: Tactile, Quiet

## Variants

### Empty

```html
<div class="tui-field" style="width:min(280px,100%);white-space:normal;text-align:left"><div class="tui-search-field"><span class="tui-search-field__glyph" aria-hidden="true"><svg class="tui-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="7" cy="7" r="4.2"/><path d="m10.2 10.2 3.3 3.3"/></svg></span><input class="tui-search-field__input" type="search" value="" placeholder="Find a run" aria-label="Find a run" tabindex="-1"></div></div>
```

### Typed · clear key

```html
<div class="tui-field" style="width:min(280px,100%);white-space:normal;text-align:left"><div class="tui-search-field"><span class="tui-search-field__glyph" aria-hidden="true"><svg class="tui-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="7" cy="7" r="4.2"/><path d="m10.2 10.2 3.3 3.3"/></svg></span><input class="tui-search-field__input" type="search" value="refund" placeholder="Find a run" aria-label="Find a run" tabindex="-1"><button class="tui-search-field__key" type="button" tabindex="-1" aria-label="Clear search"><svg class="tui-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4.5 4.5l7 7M11.5 4.5l-7 7"/></svg></button></div></div>
```

### With a count

```html
<div class="tui-field" style="width:min(280px,100%);white-space:normal;text-align:left"><div class="tui-search-field"><span class="tui-search-field__glyph" aria-hidden="true"><svg class="tui-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="7" cy="7" r="4.2"/><path d="m10.2 10.2 3.3 3.3"/></svg></span><input class="tui-search-field__input" type="search" value="refund" placeholder="Find a run" aria-label="Find a run" tabindex="-1"><output class="tui-search-field__count">3 of 8 runs</output><button class="tui-search-field__key" type="button" tabindex="-1" aria-label="Clear search"><svg class="tui-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4.5 4.5l7 7M11.5 4.5l-7 7"/></svg></button></div></div>
```

### No results

```html
<div class="tui-field" style="width:min(280px,100%);white-space:normal;text-align:left"><div class="tui-search-field"><span class="tui-search-field__glyph" aria-hidden="true"><svg class="tui-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="7" cy="7" r="4.2"/><path d="m10.2 10.2 3.3 3.3"/></svg></span><input class="tui-search-field__input" type="search" value="chargeback" placeholder="Find a run" aria-label="Find a run" tabindex="-1"><output class="tui-search-field__count">No runs</output><button class="tui-search-field__key" type="button" tabindex="-1" aria-label="Clear search"><svg class="tui-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4.5 4.5l7 7M11.5 4.5l-7 7"/></svg></button></div></div>
```

### Count before typing

```html
<div class="tui-field" style="width:min(280px,100%);white-space:normal;text-align:left"><div class="tui-search-field"><span class="tui-search-field__glyph" aria-hidden="true"><svg class="tui-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="7" cy="7" r="4.2"/><path d="m10.2 10.2 3.3 3.3"/></svg></span><input class="tui-search-field__input" type="search" value="" placeholder="Find a run" aria-label="Find a run" tabindex="-1"><output class="tui-search-field__count">8 runs</output></div></div>
```

### Disabled

```html
<div class="tui-field" style="width:min(280px,100%);white-space:normal;text-align:left"><div class="tui-search-field"><span class="tui-search-field__glyph" aria-hidden="true"><svg class="tui-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="7" cy="7" r="4.2"/><path d="m10.2 10.2 3.3 3.3"/></svg></span><input class="tui-search-field__input" type="search" value="" placeholder="Find a run" aria-label="Find a run" tabindex="-1" disabled></div></div>
```

## Every state

- Rest
- Typed
- Key hover
- Key pressed

Hover, pressed and focus are forced with `.is-hover`, `.is-down` and `.is-focus`; checked and disabled are real.

## API

| Name | Kind | Values | What it does |
| - | - | - | - |
|  | element | around the glass, input, count and key | The well. Without the script it is the same well round a search input. |
| input.tui-search-field__input | element | type="search" | The real field. It posts, and Enter submits a form around it. Give it a name with aria-label or a label. |
| button[data-search-clear] | attribute | empty button at the end | The clear key. The script gives it its glyph and name, and shows it only while there is text. |
| output.tui-search-field__count | element | optional, before the key | Where the count is printed. Hidden while it is empty. |
| count · total · unit · plural | attribute | numbers and words | The count and what it counts: count="3" total="8" unit="run" prints “3 of 8 runs”. plural is for words that do not take an s. |
| count | property | number \| null | The number printed; null removes the count. |
| input · change | event | from the input | Fire for typing, for Clear and for Escape alike, so one listener does the filtering. |
| searchCountText(count, options) | function | { total, unit, plural } | The words a count prints: “No runs”, “1 run”, “3 of 8 runs”. Pure. |
| Escape · Enter · Tab | key | in the field | Empties it, and goes no further while there was text; submits a form; moves to the clear key. |

## Markup

```html
<search>
  <tui-search-field count="8" unit="run">
    <span class="tui-search-field__glyph" aria-hidden="true"><svg class="tui-icon" …></svg></span>
    <input class="tui-search-field__input" type="search" name="q" aria-label="Find a run" autocomplete="off">
    <output class="tui-search-field__count"></output>
    <button class="tui-search-field__key" type="button" data-search-clear></button>
  </tui-search-field>
</search>

<!-- A visible label above, as any field -->
<div class="tui-field">
  <label class="tui-label" for="q">Find a run</label>
  <tui-search-field>…</tui-search-field>
</div>
```

```js
import { searchCountText } from '/kit/kit.mjs';

const field = document.querySelector('tui-search-field');
field.input.addEventListener('input', () => {        // typing, Clear and Escape all fire this
  const hits = runs.filter((r) => r.title.includes(field.input.value));
  render(hits);
  field.setAttribute('total', runs.length);
  field.count = hits.length;                          // prints "3 of 8 runs" and says it aloud
});

searchCountText(0, { unit: 'run' });                 // "No runs"
searchCountText(3, { total: 8, unit: 'run' });       // "3 of 8 runs"
```

The demo on the page:

```html
<div class="tui-panel" style="width:min(100%,460px);display:grid;gap:16px;text-align:left"><div class="tui-panel__head" style="margin-bottom:0"><span class="tui-label">Payment runs</span><span class="tui-status"><span class="tui-lamp" data-on></span>Synced</span></div>
  <div class="tui-field"><label class="tui-label" for="sf-find">Find a run</label><tui-search-field count="8" unit="run" data-sf-demo><span class="tui-search-field__glyph" aria-hidden="true"><svg class="tui-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="7" cy="7" r="4.2"/><path d="m10.2 10.2 3.3 3.3"/></svg></span><input class="tui-search-field__input" id="sf-find" name="q" type="search" autocomplete="off" placeholder="Try refund or ledger"><output class="tui-search-field__count"></output><button class="tui-search-field__key" type="button" data-search-clear></button></tui-search-field></div>
  <ul class="tui-list" style="margin:0;font-size:var(--tui-t-body)" data-sf-list><li class="tui-list__row" style="min-height:44px;padding-block:4px" data-t="4410 refund batch"><span class="tui-list__text"><span class="tui-list__title">Run 4410</span><span class="tui-list__sub">Refund batch</span></span></li><li class="tui-list__row" style="min-height:44px;padding-block:4px" data-t="4411 invoice export"><span class="tui-list__text"><span class="tui-list__title">Run 4411</span><span class="tui-list__sub">Invoice export</span></span></li><li class="tui-list__row" style="min-height:44px;padding-block:4px" data-t="4412 refund retry"><span class="tui-list__text"><span class="tui-list__title">Run 4412</span><span class="tui-list__sub">Refund retry</span></span></li><li class="tui-list__row" style="min-height:44px;padding-block:4px" data-t="4413 ledger sync"><span class="tui-list__text"><span class="tui-list__title">Run 4413</span><span class="tui-list__sub">Ledger sync</span></span></li><li class="tui-list__row" style="min-height:44px;padding-block:4px" data-t="4414 invoice reminder"><span class="tui-list__text"><span class="tui-list__title">Run 4414</span><span class="tui-list__sub">Invoice reminder</span></span></li><li class="tui-list__row" style="min-height:44px;padding-block:4px" data-t="4415 payout run"><span class="tui-list__text"><span class="tui-list__title">Run 4415</span><span class="tui-list__sub">Payout run</span></span></li><li class="tui-list__row" style="min-height:44px;padding-block:4px" data-t="4416 ledger close"><span class="tui-list__text"><span class="tui-list__title">Run 4416</span><span class="tui-list__sub">Ledger close</span></span></li><li class="tui-list__row" style="min-height:44px;padding-block:4px" data-t="4417 refund audit"><span class="tui-list__text"><span class="tui-list__title">Run 4417</span><span class="tui-list__sub">Refund audit</span></span></li></ul></div>
```

## Usage notes

Type refund: the list narrows, the count says 3 of 8 runs and the clear key appears. Escape empties the field and the list comes back. Type something with no match and the count says No runs. The field is a search input, so Enter still submits a form around it.

## Accessibility

- The field is a native `type="search"` input with a visible label or an `aria-label`; wrap it in `<search>` (or a form with `role="search"`) when it searches the whole page or site.
- The count is an `<output>`, a polite live region, so “3 of 8 runs” and “No runs” are said as the list changes without taking the focus.
- The clear key is a real button named “Clear search”, in the tab order after the field and only while there is text. Pressing it, or Escape, empties the field and leaves the focus in it.
- Escape clears only when there is something to clear, and then stops there, so a dialog around the field stays open until it is pressed a second time.
- The glass is printed, not pressed, and is hidden from a screen reader.
- On a phone the well is 48px tall with a 16px text size, so the page does not zoom, and the clear key is 44px. The 70ms press is switched off under reduced motion.

## How it’s built

It is the field’s well, 38px high, with the glass printed on its floor and the count set behind the same engraved line the input group uses for its words. The clear key is a small cap inset three pixels from the wall, so its corner is the well’s less that gap. The native cross of a search input is turned off so there is one clear key, not two. The script keeps the key in step with the text, says the count aloud, and turns Escape into a clear; filtering is the page’s own job and listens for `input`.

## Rules

Filter a list as you type, say how many it found, and let Escape or one key start again, with nothing built but the well.

The full rules are in `search-field.rules.md` in the brain, which ships with the Kit.

## Related parts

- [Field](https://tactileui.dev/parts/field): Text entry in a recess, the label printed above it, and a hint that says how to fix an error.
- [Input group](https://tactileui.dev/parts/input-group): One recess that holds a field and everything that belongs to it. Words that are part of the value but are not typed, such as https:// or a currency, are printed on the floor of the well behind an engraved line; a glyph sits at the start; small keys sit at the end to clear the text, copy it, or show a password.
- [Combobox](https://tactileui.dev/parts/combobox): A field that filters a long list as you type. It starts as a plain select, so it posts with the form and works without a script; then the options card slides out of a slot under the well, the letters that match print heavier and the chosen option’s lamp is lit. Several choices sit in the well as tags.
- [Command palette](https://tactileui.dev/parts/command): Every action and page behind one key: a modal plate whose entry line is a lit display, with the results printed below and the chosen one pressed in.
