diff --git a/web/chat/chat-thread-composer.css b/web/chat/chat-thread-composer.css
new file mode 100644
--- /dev/null
+++ b/web/chat/chat-thread-composer.css
@@ -0,0 +1,72 @@
+div.threadSearchContainer {
+  background-color: var(--thread-creation-search-container-bg);
+  color: var(--fg);
+  display: flex;
+  flex-direction: column;
+  max-height: 50%;
+  overflow: auto;
+  flex-shrink: 0;
+}
+
+div.fullHeight {
+  flex-grow: 1;
+  max-height: 100%;
+}
+
+div.userSelectedTags {
+  display: flex;
+  flex-wrap: wrap;
+  flex-direction: row;
+  align-items: center;
+  gap: 4px;
+  padding: 4px 12px;
+  margin-bottom: 8px;
+}
+
+div.searchRow {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-right: 8px;
+}
+
+div.searchField {
+  flex-grow: 1;
+}
+
+div.closeSearch {
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  color: var(--thread-creation-close-search-color);
+  margin: 0 8px;
+}
+
+ul.searchResultsContainer {
+  display: flex;
+  flex-direction: column;
+  overflow: auto;
+  padding: 0 12px 8px;
+}
+
+li.searchResultsItem {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  padding: 8px 12px;
+  cursor: pointer;
+}
+
+li.searchResultsItem:hover {
+  background-color: var(--thread-creation-search-item-bg-hover);
+}
+
+div.userName {
+  color: var(--fg);
+}
+
+div.userInfo {
+  font-style: italic;
+  color: var(--thread-creation-search-item-info-color);
+}
diff --git a/web/chat/chat-thread-composer.react.js b/web/chat/chat-thread-composer.react.js
new file mode 100644
--- /dev/null
+++ b/web/chat/chat-thread-composer.react.js
@@ -0,0 +1,124 @@
+// @flow
+import classNames from 'classnames';
+import * as React from 'react';
+
+import { userSearchIndexForPotentialMembers } from 'lib/selectors/user-selectors';
+import { getPotentialMemberItems } from 'lib/shared/search-utils';
+import type { AccountUserInfo, UserListItem } from 'lib/types/user-types';
+
+import Label from '../components/label.react';
+import Search from '../components/search.react';
+import type { InputState } from '../input/input-state';
+import { useSelector } from '../redux/redux-utils';
+import SWMansionIcon from '../SWMansionIcon.react';
+import css from './chat-thread-composer.css';
+
+type Props = {
+  +userInfoInputArray: $ReadOnlyArray<AccountUserInfo>,
+  +otherUserInfos: { [id: string]: AccountUserInfo },
+  +threadID: string,
+  +inputState: InputState,
+};
+
+function ChatThreadComposer(props: Props): React.Node {
+  const { userInfoInputArray, otherUserInfos } = props;
+
+  const [usernameInputText, setUsernameInputText] = React.useState('');
+
+  const userSearchIndex = useSelector(userSearchIndexForPotentialMembers);
+
+  const userInfoInputIDs = React.useMemo(
+    () => userInfoInputArray.map(userInfo => userInfo.id),
+    [userInfoInputArray],
+  );
+
+  const userListItems = React.useMemo(
+    () =>
+      getPotentialMemberItems(
+        usernameInputText,
+        otherUserInfos,
+        userSearchIndex,
+        userInfoInputIDs,
+      ),
+    [usernameInputText, otherUserInfos, userSearchIndex, userInfoInputIDs],
+  );
+
+  // eslint-disable-next-line no-unused-vars
+  const onSelectUserFromSearch = React.useCallback((id: string) => {}, []);
+  // eslint-disable-next-line no-unused-vars
+  const onRemoveUserFromSelected = React.useCallback((id: string) => {}, []);
+
+  const userSearchResultList = React.useMemo(() => {
+    if (
+      !userListItems.length ||
+      (!usernameInputText && userInfoInputArray.length)
+    ) {
+      return null;
+    }
+
+    return (
+      <ul className={css.searchResultsContainer}>
+        {userListItems.map((userSearchResult: UserListItem) => (
+          <li
+            className={css.searchResultsItem}
+            key={userSearchResult.id}
+            onClick={() => onSelectUserFromSearch(userSearchResult.id)}
+          >
+            <div className={css.userName}>{userSearchResult.username}</div>
+            <div className={css.userInfo}>{userSearchResult.alertTitle}</div>
+          </li>
+        ))}
+      </ul>
+    );
+  }, [
+    onSelectUserFromSearch,
+    userInfoInputArray.length,
+    userListItems,
+    usernameInputText,
+  ]);
+
+  const hideSearch = React.useCallback(() => {}, []);
+
+  const tagsList = React.useMemo(() => {
+    if (!userInfoInputArray?.length) {
+      return null;
+    }
+    const labels = userInfoInputArray.map(user => {
+      return (
+        <Label key={user.id} onClose={() => onRemoveUserFromSelected(user.id)}>
+          {user.username}
+        </Label>
+      );
+    });
+    return <div className={css.userSelectedTags}>{labels}</div>;
+  }, [userInfoInputArray, onRemoveUserFromSelected]);
+
+  const threadSearchContainerStyles = React.useMemo(
+    () =>
+      classNames(css.threadSearchContainer, {
+        [css.fullHeight]: !userInfoInputArray.length,
+      }),
+    [userInfoInputArray.length],
+  );
+
+  return (
+    <div className={threadSearchContainerStyles}>
+      <div className={css.searchRow}>
+        <div className={css.searchField}>
+          <Search
+            onChangeText={setUsernameInputText}
+            searchText={usernameInputText}
+            placeholder="Select users for chat"
+          />
+        </div>
+        <div className={css.closeSearch} onClick={hideSearch}>
+          <SWMansionIcon size={25} icon="cross" />
+        </div>
+      </div>
+      {tagsList}
+      {userSearchResultList}
+    </div>
+  );
+}
+
+export default ChatThreadComposer;
diff --git a/web/theme.css b/web/theme.css
--- a/web/theme.css
+++ b/web/theme.css
@@ -169,4 +169,8 @@
   --notification-settings-option-invalid-selected-color: var(--shades-black-60);
   --danger-zone-subheading-color: var(--shades-white-60);
   --danger-zone-explanation-color: var(--shades-black-60);
+  --thread-creation-search-container-bg: var(--shades-black-90);
+  --thread-creation-close-search-color: var(--shades-black-60);
+  --thread-creation-search-item-bg-hover: var(--shades-black-80);
+  --thread-creation-search-item-info-color: var(--shades-black-60);
 }