diff --git a/web/modals/threads/thread-picker-modal.css b/web/modals/threads/thread-picker-modal.css
new file mode 100644
--- /dev/null
+++ b/web/modals/threads/thread-picker-modal.css
@@ -0,0 +1,41 @@
+div.container {
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  margin: 16px;
+}
+
+div.contentContainer {
+  overflow: scroll;
+  height: 448px;
+}
+
+div.threadPickerOptionContainer {
+  display: flex;
+  align-items: center;
+  padding: 12px 16px;
+  cursor: pointer;
+}
+
+div.threadPickerOptionContainer:hover {
+  background-color: var(--thread-hover-bg);
+  border-radius: 8px;
+}
+
+div.threadSwatch {
+  min-width: 40px;
+  height: 40px;
+  border-radius: 10px;
+}
+
+div.threadNameText {
+  color: var(--shades-white-100);
+  margin-left: 16px;
+}
+
+div.noResultsText {
+  text-align: center;
+  color: var(--shades-white-100);
+  margin-top: 24px;
+  font-weight: 500;
+}
diff --git a/web/modals/threads/thread-picker-modal.react.js b/web/modals/threads/thread-picker-modal.react.js
new file mode 100644
--- /dev/null
+++ b/web/modals/threads/thread-picker-modal.react.js
@@ -0,0 +1,133 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+import { createSelector } from 'reselect';
+
+import { threadSearchIndex } from 'lib/selectors/nav-selectors';
+import { onScreenEntryEditableThreadInfos } from 'lib/selectors/thread-selectors';
+import type { ThreadInfo } from 'lib/types/thread-types';
+
+import Search from '../../components/search.react';
+import { useSelector } from '../../redux/redux-utils';
+import Modal, { type ModalOverridableProps } from '../modal.react';
+import css from './thread-picker-modal.css';
+
+type OptionProps = {
+  +threadInfo: ThreadInfo,
+  +createNewEntry: (threadID: string) => void,
+  +onCloseModal: () => void,
+};
+
+function ThreadPickerOption(props: OptionProps) {
+  const { threadInfo, createNewEntry, onCloseModal } = props;
+  const onClick = React.useCallback(() => {
+    onCloseModal();
+    createNewEntry(threadInfo.id);
+  }, [threadInfo.id, createNewEntry, onCloseModal]);
+
+  const swatchColorStyle = React.useMemo(
+    () => ({
+      backgroundColor: `#${threadInfo.color}`,
+    }),
+    [threadInfo.color],
+  );
+
+  return (
+    <div
+      key={threadInfo.id}
+      className={css.threadPickerOptionContainer}
+      onClick={onClick}
+    >
+      <div style={swatchColorStyle} className={css.threadSwatch} />
+      <div className={css.threadNameText}>{threadInfo.uiName}</div>
+    </div>
+  );
+}
+
+type Props = {
+  ...ModalOverridableProps,
+  +createNewEntry: (threadID: string) => void,
+};
+
+function ThreadPickerModal(props: Props): React.Node {
+  const { createNewEntry, ...modalProps } = props;
+
+  const onScreenThreadInfos = useSelector(onScreenEntryEditableThreadInfos);
+  const searchIndex = useSelector(state => threadSearchIndex(state));
+
+  invariant(
+    onScreenThreadInfos.length > 0,
+    "ThreadPicker can't be open when onScreenThreadInfos is empty",
+  );
+
+  const [searchText, setSearchText] = React.useState<string>('');
+  const [searchResults, setSearchResults] = React.useState<Set<string>>(
+    new Set(),
+  );
+
+  const onChangeSearchText = React.useCallback(
+    (text: string) => {
+      const results = searchIndex.getSearchResults(text);
+      setSearchText(text);
+      setSearchResults(new Set(results));
+    },
+    [searchIndex],
+  );
+
+  const listDataSelector = createSelector(
+    state => state.onScreenThreadInfos,
+    state => state.searchText,
+    state => state.searchResults,
+    (
+      threadInfos: $ReadOnlyArray<ThreadInfo>,
+      text: string,
+      results: Set<string>,
+    ) =>
+      text
+        ? threadInfos.filter(threadInfo => results.has(threadInfo.id))
+        : [...threadInfos],
+  );
+
+  const threads = useSelector(() =>
+    listDataSelector({
+      onScreenThreadInfos,
+      searchText,
+      searchResults,
+    }),
+  );
+
+  const threadPickerContent = React.useMemo(() => {
+    const options = threads.map(threadInfo => (
+      <ThreadPickerOption
+        threadInfo={threadInfo}
+        createNewEntry={createNewEntry}
+        key={threadInfo.id}
+        onCloseModal={modalProps.onClose}
+      />
+    ));
+
+    if (options.length === 0 && searchText.length > 0) {
+      return (
+        <div className={css.noResultsText}>No results for {searchText}</div>
+      );
+    } else {
+      return options;
+    }
+  }, [threads, createNewEntry, modalProps.onClose, searchText]);
+
+  return (
+    <Modal {...modalProps} size="large">
+      <div className={css.container}>
+        <Search
+          onChangeText={onChangeSearchText}
+          searchText={searchText}
+          placeholder="Search chats"
+        />
+        <div className={css.contentContainer}>{threadPickerContent}</div>
+      </div>
+    </Modal>
+  );
+}
+
+export default ThreadPickerModal;