> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Search Field - Web

> Search Fields are text inputs that allow users to search for specific content.

> [!NOTE]
>
> For development, import `SearchField` from
> `@servicetitan/anvil2`. For design, find it in the
> [Anvil2 file](https://www.figma.com/design/xaNQsvRKczoxu7pj0M6QT8/Anvil2-v3?node-id=224-416).

![](https://mintcdn.com/servicetitan/H5FwKyiqhPVZ1UJQ/images/docs/web/components/shared/search-field-main-image.png?fit=max\&auto=format\&n=H5FwKyiqhPVZ1UJQ\&q=85\&s=0c8d705828f29386ed2382c7fbab4390)

## Anatomy

Four elements work together in the Search Field:

![search field
anatomy](https://mintcdn.com/servicetitan/Z-EIF5iqj0kJVjT1/images/docs/web/components/search-field/design/search-field-anatomy.png?fit=max\&auto=format\&n=Z-EIF5iqj0kJVjT1\&q=85\&s=7bc0e9fb6cba1e438ed578d691b16c22)

1. Placeholder text
2. Search icon
3. Search value
4. Clear button

## Options

Configure Search Field for different scenarios:

### Sizes

searchfield-size

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/searchfield-size?file=src/App.tsx)

```tsx lines theme={null}
import { SearchField, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex gap="6" direction="column">
      <SearchField size="small" />
      <SearchField size="medium" />
      <SearchField size="large" />
    </Flex>
  );
}

export default App;
```

| Size | Height |
| - | - |
| Small | 32px |
| Medium | 40px |
| Large | 48px |

## Behavior

Search Field has distinct visual states and handles clearing and overflow.

### Visual States

searchfield-data-interactive

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/searchfield-data-interactive?file=src/App.tsx)

```tsx lines theme={null}
import { SearchField, Flex } from "@servicetitan/anvil2";

function App() {
  return (
    <Flex gap="6" direction="column">
      <SearchField />
      <SearchField disabled />
    </Flex>
  );
}

export default App;
```

### Clearing

The clear button follows web standards:

* Shows when there is an input value
* Not focusable, only clickable

searchfield-clear

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/searchfield-clear?file=src/App.tsx)

```tsx lines theme={null}
import { SearchField } from "@servicetitan/anvil2";

function App() {
  return <SearchField defaultValue="Sample value" />;
}

export default App;
```

### Overflow

searchfield-overflow

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/searchfield-overflow?file=src/App.tsx)

```tsx lines theme={null}
import { SearchField } from "@servicetitan/anvil2";

function App() {
  return (
    <SearchField defaultValue="This is an example of overflow text inside of a searchfield to showcase what happens" />
  );
}

export default App;
```

Long text scrolls horizontally within the container. The visible portion follows the cursor.

## Usage Guidelines

| Standard | Requirement |
| - | - |
| DoSearching a Table | Place Search Field with a Table so users can find rows by keyword. |
| DoSearching a catalog of items | Help users find an item in a catalog that is too large to browse. |
| DoComplex Dialog selection | Use Search Field inside the complex Dialog selection pattern to narrow the options. |
| Don'tSmall datasets | Don't add search when the dataset is small or the content is easy to find on the page. Users can scan it directly. |

### Alternatives

#### Search Field vs Combobox

Use Combobox when users select from an overlayed list of options, typically in a form field. Use Search Field for traditional search experiences, filtering lists on the page, tables, large datasets, or results that need additional filtering.

### How to Use

Combine Search Field with other UI elements for a complete search experience. Customize it to fit your needs.

#### General search results guidance

* Keep the search term present after running search
* Show the number of results when possible
* Show filters when possible
* Leverage [Skeleton](https://anvil.servicetitan.com/docs/components/skeleton/web.md) loaders when waiting for results to populate
* Include a "no results" empty state when possible

##### Show the number of results when possible

searchfield-listbox-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/searchfield-listbox-do?file=src/App.tsx)

```tsx lines theme={null}
import { SearchField, Flex, Text, Listbox } from "@servicetitan/anvil2";
import { useState, type ChangeEvent } from "react";

type Item = { id: number; label: string };

function App() {
  const items: Item[] = [
    { id: 1, label: "Apple" },
    { id: 2, label: "Banana" },
    { id: 3, label: "Cherry" },
    { id: 4, label: "Grape" },
    { id: 5, label: "Kiwi" },
    { id: 6, label: "Mango" },
    { id: 7, label: "Orange" },
    { id: 8, label: "Peach" },
    { id: 9, label: "Pineapple" },
    { id: 10, label: "Strawberry" },
  ];

  const [selected, setSelected] = useState<Item>(items[1]);
  const [result, setResult] = useState<Item[]>(items);

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    if (!e.target.value) {
      setResult(items);
      return;
    }
    const newResult = items.filter((item) =>
      item.label.toLowerCase().includes(e.target.value.toLowerCase()),
    );
    setResult(newResult);
  };

  return (
    <Flex direction="column">
      <Text el="h1" variant="headline">
        Fruit
      </Text>
      <Flex gap="6" direction="column">
        <Text variant="body">
          This is description text talking about the fruit.
        </Text>
        <SearchField
          onChange={handleChange}
          placeholder="Search for fruits..."
        />
        <Text el="h3" variant="headline" size="small">
          {result.length} results
        </Text>
        <Listbox
          style={{ padding: 0 }}
          defaultSelected={items[2]}
          items={result}
          selected={selected}
          onSelectionChange={(selection: Item | undefined) => {
            setSelected(
              items.find(
                (item) => item?.label === selection?.label,
              ) as (typeof items)[number],
            );
          }}
        >
          {({ items }) =>
            items.map((item) => (
              <Listbox.Option key={item.label} item={item}>
                {item.label}
              </Listbox.Option>
            ))
          }
        </Listbox>
      </Flex>
    </Flex>
  );
}

export default App;
```

#### Dropdown with results

* Consider creating sections to organize results.
* Consider adding filters to narrow down results.
* If there are additional results that are not shown, consider adding a link to a page with all available results.
* If the results become too complicated, consider using a [Dialog](https://anvil.servicetitan.com/docs/components/dialog/web.md) over a [Popover](https://anvil.servicetitan.com/docs/components/popover/web.md).

searchfield-popover-do

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/searchfield-popover-do?file=src/App.tsx)

```tsx lines theme={null}
import {
  SearchField,
  Popover,
  Flex,
  ButtonToggle,
  Text,
  Link,
  Avatar,
} from "@servicetitan/anvil2";
import { useState, type ChangeEvent } from "react";

type Item = { id: number; label: string; type: string };

function App() {
  const websiteItems: Item[] = [
    { id: 1, label: "Anvil1 web documentation", type: "Website" },
    { id: 2, label: "Anvil2 web documentation", type: "Website" },
    { id: 3, label: "ServiceTitan", type: "Website" },
    { id: 4, label: "Confluence", type: "Website" },
    { id: 5, label: "Jira Board", type: "Website" },
  ];

  const documentationItems: Item[] = [
    { id: 6, label: "Anvil Backlog", type: "Documentation" },
    { id: 7, label: "Anvil Design System Confluence", type: "Documentation" },
    { id: 8, label: "Anvil Design Work Process", type: "Documentation" },
    { id: 9, label: "Anvil Figma Library", type: "Documentation" },
  ];

  const peopleItems: Item[] = [
    { id: 11, label: "Alice Johnson", type: "People" },
    { id: 12, label: "Bob Smith", type: "People" },
    { id: 13, label: "Charlie Brown", type: "People" },
    { id: 14, label: "David Wilson", type: "People" },
    { id: 15, label: "Eve Davis", type: "People" },
    { id: 16, label: "Fiona Taylor", type: "People" },
    { id: 17, label: "George Miller", type: "People" },
    { id: 18, label: "Hannah Clark", type: "People" },
  ];

  const items = websiteItems.concat(documentationItems, peopleItems);
  const [result, setResult] = useState<Item[]>(items);

  const [isWebsiteChecked, setIsWebsiteChecked] = useState(false);
  const [isDocumentationChecked, setIsDocumentationChecked] = useState(false);
  const [isPeopleChecked, setIsPeopleChecked] = useState(false);

  const getAllFilter = () => {
    if (!isWebsiteChecked && !isDocumentationChecked && !isPeopleChecked)
      return true;
    return false;
  };

  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    if (!e.target.value) {
      setResult(items);
      return;
    }
    const newResult = items.filter((item) =>
      item.label.toLowerCase().includes(e.target.value.toLowerCase()),
    );
    setResult(newResult);
  };

  const hasWebsiteId = websiteItems.filter((item) =>
    result.some((resultItem) => resultItem.id === item.id),
  );

  const hasDocumentationId = documentationItems.filter((item) =>
    result.some((resultItem) => resultItem.id === item.id),
  );

  const hasPeopleId = peopleItems.filter((item) =>
    result.some((resultItem) => resultItem.id === item.id),
  );

  const showNoResults = () => {
    if (result.length === 0) return true;
    const filterState = [
      isWebsiteChecked,
      isDocumentationChecked,
      isPeopleChecked,
    ].filter((value) => value === true).length;
    const website = isWebsiteChecked && hasWebsiteId.length === 0;
    const documentation =
      isDocumentationChecked && hasDocumentationId.length === 0;
    const people = isPeopleChecked && hasPeopleId.length === 0;

    if (
      filterState === 2 &&
      ((website && documentation) ||
        (website && people) ||
        (documentation && people))
    )
      return true;
    if (filterState === 1 && (website || people || documentation)) return true;
    return false;
  };

  return (
    <div style={{ width: "500px", height: "672px" }}>
      <Popover open placement="bottom-start" disableShift disableFlip modal>
        <Popover.Button>Trigger</Popover.Button>
        <Popover.Content>
          <Flex gap="3" direction="column">
            <SearchField onChange={handleChange} />
            <Flex gap="2">
              <ButtonToggle
                checked={isWebsiteChecked}
                onClick={() => setIsWebsiteChecked((prev) => !prev)}
              >
                Websites {hasWebsiteId.length}
              </ButtonToggle>
              <ButtonToggle
                checked={isDocumentationChecked}
                onClick={() => setIsDocumentationChecked((prev) => !prev)}
              >
                Documentation {hasDocumentationId.length}
              </ButtonToggle>
              <ButtonToggle
                checked={isPeopleChecked}
                onClick={() => setIsPeopleChecked((prev) => !prev)}
              >
                People {hasPeopleId.length}
              </ButtonToggle>
            </Flex>
            {(getAllFilter() || isWebsiteChecked) &&
              hasWebsiteId.length > 0 && (
                <Text variant="eyebrow" style={{ marginTop: "1rem" }}>
                  Websites
                </Text>
              )}
            {(getAllFilter() || isWebsiteChecked) &&
              hasWebsiteId &&
              websiteItems.map((item) => {
                const idExistsInResult = result.some(
                  (resultItem) => resultItem.id === item.id,
                );
                return idExistsInResult && <Link ghost>{item.label}</Link>;
              })}

            {(getAllFilter() || isDocumentationChecked) &&
              hasDocumentationId.length > 0 && (
                <Text variant="eyebrow" style={{ marginTop: "1rem" }}>
                  Documentation
                </Text>
              )}
            {(getAllFilter() || isDocumentationChecked) &&
              hasDocumentationId &&
              documentationItems.map((item) => {
                const idExistsInResult = result.some(
                  (resultItem) => resultItem.id === item.id,
                );
                return idExistsInResult && <Link ghost>{item.label}</Link>;
              })}

            {(getAllFilter() || isPeopleChecked) && hasPeopleId.length > 0 && (
              <Text variant="eyebrow" style={{ marginTop: "1rem" }}>
                People
              </Text>
            )}
            <Flex gap="2">
              {(getAllFilter() || isPeopleChecked) &&
                hasPeopleId &&
                peopleItems.map((item) => {
                  const idExistsInResult = result.some(
                    (resultItem) => resultItem.id === item.id,
                  );
                  return idExistsInResult && <Avatar name={item.label} />;
                })}
            </Flex>
            {showNoResults() && <Text subdued>No results found</Text>}
          </Flex>
        </Popover.Content>
      </Popover>
    </div>
  );
}

export default App;
```

## Content

Placeholder text and search results should clearly communicate the search context.

## Keyboard Interaction

Navigate the Search Field using standard keyboard controls.

### Accessibility

Search Field doesn't have a label, so place it near related content to make its purpose clear.

For more guidance on form field labels and context, see [input field context association best practices](https://anvil.servicetitan.com/docs/accessibility/labels-and-ctas.md#input-field-context-association).

## Code

**Implementation**

searchfield-playground

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/searchfield-playground?file=src/App.tsx)

```tsx lines expandable theme={null}
import { SearchField } from "@servicetitan/anvil2";

function App() {
  return <SearchField placeholder="Search for something..." />;
}

export default App;
```

## Common Examples

```tsx theme={null}
import { SearchField } from "@servicetitan/anvil2";

function ExampleComponent() {
  return <SearchField />;
}
```

### Controlling search inputs

The value of search inputs are uncontrolled by default. To control a search input, use the `value` prop.

#### Uncontrolled

searchfield-uncontrolled

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/searchfield-uncontrolled?file=src/App.tsx)

```tsx lines expandable theme={null}
import { SearchField } from "@servicetitan/anvil2";

function App() {
  return <SearchField defaultValue="truck" />;
}

export default App;
```

#### Controlled

searchfield-controlled

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/searchfield-controlled?file=src/App.tsx)

```tsx lines expandable theme={null}
import { SearchField } from "@servicetitan/anvil2";
import { useState } from "react";

function App() {
  const [value, setValue] = useState("truck");

  return (
    <SearchField
      value={value}
      onChange={(e) => setValue(e.target.value)}
      onClear={() => setValue("")}
    />
  );
}

export default App;
```

### Form Example

The `SearchField` component uses an HTML `input` and so can be used within an HTML `form` the same way that a regular HTML `input` element can.

searchfield-html

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/searchfield-html?file=src/App.tsx)

```tsx lines expandable theme={null}
import { SearchField, Flex, Button } from "@servicetitan/anvil2";
import { useState } from "react";

function App() {
  const [value, setValue] = useState("");

  return (
    <Flex direction="column" gap="6">
      <form
        onSubmit={(e) => {
          e.preventDefault();
          const formData = new FormData(e.target as HTMLFormElement);
          console.log(formData.get("something"));
        }}
      >
        <Flex direction="row" gap="6">
          <SearchField
            name="something"
            placeholder="Search for something..."
            value={value}
            onChange={(e) => setValue(e.target.value)}
            onClear={() => setValue("")}
          />

          <Button type="submit">Submit</Button>
        </Flex>
      </form>
    </Flex>
  );
}

export default App;
```

## React Accessibility

Add `aria-label` or `aria-labelledby` so that screen readers can properly announce what this is searching.

For more guidance on form field labels and context, see [input field context association best practices](https://anvil.servicetitan.com/docs/accessibility/labels-and-ctas.md#input-field-context-association).

**SearchField Props**

```tsx theme={null}
<SearchField
  autoComplete="off"
  disabled={false}
  size="medium"
  onClear={() => console.log("Cleared")}
  value="search term"
  onChange={(e) => console.log(e.target.value)}
  aria-label="Search"
/>
```

## `SearchField` Props

The `SearchField` component accepts any valid HTML `input` props, other than `prefix`, `required`, `children`, as well as the following:

**autoComplete** `React.HTMLInputAutoCompleteAttribute` (default: off)

**disabled** `boolean` (default: false)

**onClear** `() =\> void`

Callback when the clear button is clicked.

**size** `"small" | "medium" | "large"` (default: medium)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.