# Search customization

The search bar above the channel list finds channels, people and messages. This recipe changes how it is set up, how it looks and where it sits. How search works across the layers, and what each result does when clicked, is on [Search](https://getstream.io/chat/docs/sdk/react/features/channels/search/).

>
> **Note:** **What you'll build**
>
> - Search bar settings of your own, such as its placeholder
> - Compact channel results with name and member count
> - Search in a top bar instead of the sidebar
>

---

**Preview**

![Channel results in the sidebar search shown as compact rows with the channel name and member count](https://getstream.io/docs-assets/images/0e354ebaf578.png)

---

**Approach:** Replace one part and Compose. [Choosing where to start](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/overview/#choosing-where-to-start) explains the approaches.

**Starts from:** [the base app](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/overview/#the-base-app). The page shows only what the recipe adds.

## Best practices

- Override the smallest part you need: `Search` to set its props, `SearchSourceResultList` to change one source's results, `SearchBar` or `SearchResults` for the rest.
- Start from `DefaultSearchResultItems` and replace one entry, so the other sources keep their default results.
- Keep what a result does when clicked. A channel result opens the channel, watches it, and adds it to the channel lists that match it.
- Keep `role="option"` on a custom result's button. The arrow keys move between the results that have it.

## Configure search through `WithComponents`

`ChannelNavigation` renders the channel list header, the search bar and the channel lists. It renders search from the `Search` key, so to change the search bar's props, set a component on that key that renders `Search` with them:

```tsx title="src/SidebarSearch.tsx"
import type { PropsWithChildren } from "react";
import { Search, WithComponents } from "stream-chat-react";

const SidebarSearch = () => (
  <Search
    directMessagingChannelType="team"
    exitSearchOnInputBlur
    placeholder="Search teammates, channels and messages"
  />
);

// Declared once, so WithComponents gets the same object on every render.
const overrides = { Search: SidebarSearch };

export const WithSidebarSearch = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

Wrap `ChannelNavigation` in `WithSidebarSearch`, or the `ChatView` that renders it:

```tsx
<WithSidebarSearch>
  <ChatView layouts={layouts} views={views} />
</WithSidebarSearch>
```

- `directMessagingChannelType` is the channel type a user result opens; the default is `messaging`.
- `exitSearchOnInputBlur` closes search when focus moves from the input to something outside search, such as a click elsewhere.
- `placeholder`, `disabled` and `inputProps` go to the input.

The key applies only where the SDK renders search. A `Search` you render yourself takes the props directly, as in [Render search outside ChannelList](#render-search-outside-channellist).

## Customize search result items

`SearchSourceResultList` renders the results of one source. Its `SearchResultItems` prop maps each source's `type` (`channels`, `users`, `messages`) to the component that renders one result. This list renders channel results as compact rows with the channel's name and member count, and keeps the default user and message results:

```tsx label="React" title="src/CompactChannelResults.tsx"
import type { PropsWithChildren } from "react";
import type { Channel, ChannelStateData } from "stream-chat";
import {
  DefaultSearchResultItems,
  SearchSourceResultList,
  useChannelDisplayName,
  useChatContext,
  useStateStore,
  useWorkspaceNavigation,
  WithComponents,
} from "stream-chat-react";
import "./CompactChannelResults.css";

const memberCountSelector = ({ memberCount }: ChannelStateData) => ({
  memberCount,
});

const CompactChannelResult = ({ item }: { item: Channel }) => {
  const { channelManager, client } = useChatContext();
  const { openChannel } = useWorkspaceNavigation();
  const name = useChannelDisplayName(item);
  const { memberCount } = useStateStore(item.state, memberCountSelector);

  return (
    <button
      className="cookbook-channel-result"
      onClick={(event) => {
        openChannel(item, { event });
        // Channel search doesn't watch its results, so watch the channel to receive its events.
        item.watch().catch((error: Error) =>
          client.notifications.addError({
            message: "Failed to load the channel",
            options: { originalError: error },
            origin: { emitter: "CompactChannelResult" },
          }),
        );
        // Add the channel to the channel lists whose filters it matches.
        channelManager.ingestChannel(item);
      }}
      role="option"
      type="button"
    >
      <span className="cookbook-channel-result__name"># {name}</span>
      <span className="cookbook-channel-result__members">
        {memberCount} members
      </span>
    </button>
  );
};

const searchResultItems = {
  ...DefaultSearchResultItems,
  channels: CompactChannelResult,
};

const ResultList = () => (
  <SearchSourceResultList SearchResultItems={searchResultItems} />
);

// Declared once, so WithComponents gets the same object on every render.
const overrides = { SearchSourceResultList: ResultList };

export const WithCompactChannelResults = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

```css label="CSS" title="src/CompactChannelResults.css"
@layer stream-overrides {
  .cookbook-channel-result {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--str-chat__spacing-xs);
    width: 100%;
    padding: var(--str-chat__spacing-xs) var(--str-chat__spacing-md);
    border: 0;
    border-radius: var(--str-chat__radius-lg);
    background: none;
    color: var(--str-chat__text-primary);
    font: var(--str-chat__font-body-default);
    text-align: start;
    cursor: pointer;
  }

  .cookbook-channel-result:hover {
    background: var(--str-chat__background-utility-hover);
  }

  .cookbook-channel-result__name {
    font: var(--str-chat__font-body-emphasis);
  }

  .cookbook-channel-result__members {
    color: var(--str-chat__text-secondary);
    font: var(--str-chat__font-caption-default);
  }
}
```

The rules sit in the `stream-overrides` layer, so they win over the SDK's own rules once the SDK's stylesheet is in an earlier layer, as [Theming → CSS layers](https://getstream.io/chat/docs/sdk/react/features/app-experience/theming/#css-layers) sets up. They take their colors, spacing and type from the SDK's tokens, so they follow your theme. Wrap the part of the app that renders search in `WithCompactChannelResults`, as with `WithSidebarSearch` above.

To keep the default result and only add to it, wrap it instead. `ChannelSearchResultItem`, `UserSearchResultItem` and `MessageSearchResultItem` are exported, and each takes `onSelect` to replace its click. The [Search](https://getstream.io/chat/docs/sdk/react/features/channels/search/) feature page wraps the default user result this way.

## Render search outside ChannelList

`Search` reads the search controller from `Chat`, so it works anywhere inside `Chat`. This workspace puts it in a top bar and keeps the channel lists in the sidebar:

```tsx label="React" title="src/Workspace.tsx"
import {
  ChannelHeader,
  ChannelLists,
  MessageComposer,
  MessageList,
  Search,
  ThreadHeader,
} from "stream-chat-react";
import { ChannelSlot, ThreadSlot } from "stream-chat-react/slot-layout";
import type { LayoutDescriptor } from "stream-chat-react/slot-layout";
import "./Workspace.css";

export const layouts: LayoutDescriptor[] = [
  { id: "channels", slots: ["channel", "thread"] },
];

export const ChannelsView = () => (
  <div className="cookbook-workspace">
    <header className="cookbook-workspace__top-bar">
      <Search placeholder="Search everything" />
    </header>
    <div className="cookbook-workspace__body">
      <aside className="cookbook-workspace__sidebar">
        <ChannelLists />
      </aside>
      <ChannelSlot slot="channel">
        <ChannelHeader />
        <MessageList />
        <MessageComposer />
      </ChannelSlot>
      <ThreadSlot slot="thread">
        <ThreadHeader />
        <MessageList />
        <MessageComposer />
      </ThreadSlot>
    </div>
  </div>
);
```

```css label="CSS" title="src/Workspace.css"
@layer stream-overrides {
  /* The top bar spans the view. The channel lists, the channel and the thread share the row below it. */
  .cookbook-workspace {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
  }

  .cookbook-workspace__top-bar {
    position: relative;
    padding: var(--str-chat__spacing-sm) var(--str-chat__spacing-md);
    border-bottom: 1px solid var(--str-chat__border-core-default);
    background: var(--str-chat__background-core-elevation-1);
  }

  .cookbook-workspace__body {
    display: flex;
    flex: 1;
    min-height: 0;
  }

  .cookbook-workspace__sidebar {
    flex: 0 0 var(--str-chat__channel-list-width);
    overflow-y: auto;
    border-inline-end: 1px solid var(--str-chat__border-core-default);
    background: var(--str-chat__background-core-elevation-1);
  }

  /* The SDK has no variable for the width of a thread beside a channel. */
  .cookbook-workspace__body > .str-chat__thread-container {
    flex: 0 0 400px;
  }

  /* The SDK renders the results in the flow under the search bar, with no
     dropdown mode; this rule opens them over the content below it. */
  .cookbook-workspace__top-bar .str-chat__search-results {
    position: absolute;
    z-index: 10;
    top: 100%;
    inset-inline: 0;
    max-height: 60vh;
    overflow-y: auto;
    border-bottom: 1px solid var(--str-chat__border-core-default);
    background: var(--str-chat__background-core-elevation-1);
    box-shadow: var(--str-chat__box-shadow-2);
  }
}
```

Render `ChannelsView` as the channels view of `ChatView`:

```tsx
<ChatView layouts={layouts} views={{ channels: <ChannelsView /> }} />
```

The rules sit in the `stream-overrides` layer, so they win over the SDK's own rules once the SDK's stylesheet is in an earlier layer, as [Theming → CSS layers](https://getstream.io/chat/docs/sdk/react/features/app-experience/theming/#css-layers) sets up. They take their colors, spacing and type from the SDK's tokens, so they follow your theme.

`ChannelLists` renders every list registered on `client.channelManager`. Unlike `ChannelNavigation`, it doesn't hide the lists while search is open, so the sidebar stays as it is and the results show in the top bar. The channels view lays out its children in a row, so `ChannelsView` puts them in a column of its own: the top bar, then a row with the lists, the channel and the thread. The results render inside `Search`, under its search bar, and the SDK has no setting that opens them as a dropdown, so the CSS positions them over the content below. [Workspace with ChatView](https://getstream.io/chat/docs/sdk/react/ui/layout/workspace-with-chatview/) explains `layouts`, views and slots.

---

**Results under the top bar**

![Search results open as a dropdown under a search bar that spans the top of the app](https://getstream.io/docs-assets/images/a34b640d0cdf.png)

---

## Go lower-level when you need custom data sources

The components above change how search looks. What it searches comes from the `SearchController` that `Chat` holds, with one source per kind of result:

- To search fewer kinds of results, or to change a source's filters, pass your own controller to `Chat` as `searchController`. The [Search](https://getstream.io/chat/docs/sdk/react/features/channels/search/) feature page shows how, under "Choosing what to search".
- To search your own data, such as projects or documents, write a source as [Adding your own source](https://getstream.io/chat/docs/sdk/react/state/loading-data/search-sources/#adding-your-own-source) describes, add it to the controller, and map its `type` in `SearchResultItems` as above.

## How it works

>
> **Info:** **Example**
>
> The user types "des" in the sidebar search, with `WithCompactChannelResults` around the app, and clicks Design review:
>
> 1. `Search` passes the text to the search controller that `Chat` holds, and the results replace the channel lists in the sidebar.
> 2. `SearchSourceResultList` renders the channel results with `CompactChannelResult`, which shows each channel's name and member count. The people and message results render with the defaults from `DefaultSearchResultItems`.
> 3. The user clicks Design review. `WorkspaceNavigation.openChannel()` hands the channel to the layout, which decides where it opens. The event is passed on, so ⌘/ctrl-click can open it beside the current one.
> 4. `Channel.watch()` watches the channel, since channel search doesn't watch its results. A failed watch publishes an error notification, as the default does.
> 5. `ChannelManager.ingestChannel()` adds the channel to the registered channel lists that match it, so it shows in the list without a new query.
>

Steps 3 to 5 are what the default channel result does, so the compact result opens a channel the same way.

## Related

- [Search](https://getstream.io/chat/docs/sdk/react/features/channels/search/): how search works across the layers, and what each result does when clicked.
- [Search sources](https://getstream.io/chat/docs/sdk/react/state/loading-data/search-sources/): the search controller and its sources.
- [Search components](https://getstream.io/chat/docs/sdk/react/component-reference/channel-list/search-components/): the props of `Search`, `SearchSourceResultList` and the result items.
- [Overriding components](https://getstream.io/chat/docs/sdk/react/ui/components/overriding-components/#override-keys-by-area): the search keys of `WithComponents`.
- [Workspace with ChatView](https://getstream.io/chat/docs/sdk/react/ui/layout/workspace-with-chatview/): `layouts`, views and slots.
- [Search dialog](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/search/search-dialog/): replaces the search bar with a dialog of your own.
- [Preview UI](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/channel-list/preview-ui/): a custom channel row, which channel results in search use too.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/ui-cookbooks/search/search-customization/](https://getstream.io/chat/docs/sdk/react/ui-cookbooks/search/search-customization/).