# CollectionSearch

**Category:** Features/Filter/Components

## Design

### Description

`CollectionSearch` adds a search input to the collection toolbar, allowing users to filter items by free-text search. When a user types in the search box, the search term is passed to your `fetchData` function via `query.search`, enabling server-side filtering.

The search input integrates automatically with [`CollectionPage`](./?path=/story/base-components-pages-collection-page--collectionpage) and any collection component (Table, Grid, etc.) through the toolbar.

#### Features

- **Character limit** — Use the `maxLength` prop to limit how many characters users can type.
- **Custom placeholder** — Set a `placeholder` prop to guide users on what to search for.
- **Search highlighting** — Combine with [`HighlightedSearch`](./?path=/story/features-display--highlighted-search-results) to visually highlight matching text in results.
- **Debounced input** — Search queries are debounced to avoid excessive API calls while typing.

#### Usage

Pass `CollectionSearch` as a child of the collection toolbar, or use the `search` prop on `CollectionPage.Header`:

```tsx
}
/>
```

The search term is available in your `fetchData` function:

```tsx
fetchData: async (query) => {
  const { items, total } = await api.list({
    search: query.search, // The user's search text
    limit: query.limit,
    offset: query.offset,
  });
  return { items, total };
}
```

#### Related

- [`HighlightedSearch`](./?path=/story/features-display--highlighted-search-results) — Highlight matching text in search results
- [`CollectionToolbarFilters`](./?path=/story/features-filter-components--collectiontoolbarfilters) — Add filter dropdowns alongside search


```tsx
import { CollectionSearch } from '@wix/patterns';
```

### Default

The search input will bw shown be default

```tsx
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { Page } from '@wix/design-system';
import {
  Table,
  PageWrapper,
  useTableCollection,
  OffsetQuery,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

function Default() {
  const state = useTableCollection<contacts.Contact>({
    queryName: 'contacts-Default',
    paginationMode: 'offset',
    fqdn: 'wix.patterns.dummyservice.v1.dummy_entity',
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {},
  });

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header />
        <Page.Content>
          <Table
            state={state}
            columns={[
              {
                id: 'name',
                title: 'Name',
                width: '250px',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
              },
            ]}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

### Max length

An option to limit the amout of characaters

```tsx
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { Page } from '@wix/design-system';
import {
  Table,
  PageWrapper,
  CollectionSearch,
  useTableCollection,
  OffsetQuery,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

function MaxLength() {
  const state = useTableCollection<contacts.Contact>({
    queryName: 'contacts-MaxLength',
    paginationMode: 'offset',
    fqdn: 'wix.patterns.dummyservice.v1.dummy_entity',
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {},
  });

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header />
        <Page.Content>
          <Table
            state={state}
            search={<CollectionSearch maxLength={3} />}
            columns={[
              {
                id: 'name',
                title: 'Name',
                width: '250px',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
              },
            ]}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

### Placeholder

An option to define the placeholder text.

```tsx
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { Page } from '@wix/design-system';
import {
  Table,
  PageWrapper,
  CollectionSearch,
  useTableCollection,
  OffsetQuery,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

function Placeholder() {
  const state = useTableCollection<contacts.Contact>({
    queryName: 'contacts-placeholder',
    paginationMode: 'offset',
    fqdn: 'wix.patterns.dummyservice.v1.dummy_entity',
    fetchData: (query: OffsetQuery) => {
      const { limit, offset, search, filters } = query;

      let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

      if (search) {
        queryBuilder = queryBuilder.startsWith('info.name.first', search);
      }

      return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
        items,
        total,
      }));
    },
    itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
    fetchErrorMessage: () => 'Error fetching contacts',
    filters: {},
  });

  return (
    <PageWrapper>
      <Page height="400px">
        <Page.Header />
        <Page.Content>
          <Table
            state={state}
            search={<CollectionSearch placeholder="your placeholder" />}
            columns={[
              {
                id: 'name',
                title: 'Name',
                width: '250px',
                render: (contact) =>
                  `${contact.info?.name?.first} ${contact.info?.name?.last}`,
              },
            ]}
          />
        </Page.Content>
      </Page>
    </PageWrapper>
  );
}
```

### Search highlighting

Gets highlighted search results in the table. Learn how to [enable it](./?path=/story/features-display--highlighted-search-results).

```tsx
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { Page } from '@wix/design-system';
import {
    Table,
    PageWrapper,
    CollectionSearch,
    useTableCollection,
    OffsetQuery,
} from '@wix/patterns';
import { contacts } from '@wix/crm';

function SearchHighlighting() {
    const state = useTableCollection<contacts.Contact>({
        queryName: 'contacts-SearchHighlighting',
        paginationMode: 'offset',
        fqdn: 'wix.patterns.dummyservice.v1.dummy_entity',
        fetchData: (query: OffsetQuery) => {
            const { limit, offset, search, filters } = query;

            let queryBuilder = contacts.queryContacts().limit(limit).skip(offset);

            if (search) {
                queryBuilder = queryBuilder.startsWith('info.name.first', search);
            }

            return queryBuilder.find().then(({ items = [], totalCount: total }) => ({
                items,
                total,
            }));
        },
        itemName: (item) => `${item.info?.name?.first} ${item.info?.name?.last}`,
        fetchErrorMessage: () => 'Error fetching contacts',
        filters: {},
    });

    return (
        <PageWrapper>
            <Page height="400px">
                <Page.Header />
                <Page.Content>
                    <Table
                        state={state}
                        search={<CollectionSearch />}
                        columns={[
                            {
                                id: 'name',
                                title: 'Name',
                                width: '250px',
                                searchable: true,
                                render: (contact) =>
                                    `${contact.info?.name?.first} ${contact.info?.name?.last}`,
                            },
                        ]}
                    />
                </Page.Content>
            </Page>
        </PageWrapper>
    );
}
```

## API

### Props

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `maxLength` | `number` | No | - | Sets the maximum number of characters that can be entered into a field |
| `placeholder` | `string` | No | - | Sets a placeholder message to display |

## BI

### Events

 Event   |   Description   |
--------- | --------------- |
[144:116](https://bo.wix.com/data-tools/bi-catalog-app/event/144:116) | Sent when the search results are displayed, after a user searches for something in a Wix Patterns component



### Component load events
Event   |   Description   |
--------- | --------------- |
[144:110](https://bo.wix.com/data-tools/bi-catalog-app/event/144:110) | Sent when a Wix Patterns component starts loading
[144:111](https://bo.wix.com/data-tools/bi-catalog-app/event/144:111) | Sent when a Wix Patterns component is done loading


#### 🐪 Couldn't find what you need?
* Check our [BI Catalog](https://bo.wix.com/data-tools/bi-catalog-app?viewId=all-items-view&selectedColumns=src%2Cevid%2Cname%2Cowner%2Cproduct%2CuserType+false%2CdateUpdated%2CdateCreated+false%2CcreatedBy+false%2Cstatus&source=%5B%7B%22id%22%3A%22144%22%2C%22name%22%3A%22144+-+Cairo%22%7D%5D)
* Contact us on [#cairo-bi](https://wix.slack.com/archives/C03N53KURH9)


