diff --git a/web/modals/threads/members/members-list.react.js b/web/modals/threads/members/members-list.react.js
new file mode 100644
--- /dev/null
+++ b/web/modals/threads/members/members-list.react.js
@@ -0,0 +1,76 @@
+// @flow
+
+import classNames from 'classnames';
+import _groupBy from 'lodash/fp/groupBy';
+import _toPairs from 'lodash/fp/toPairs';
+import * as React from 'react';
+
+import { stringForUser } from 'lib/shared/user-utils';
+import {
+  type ThreadInfo,
+  type RelativeMemberInfo,
+} from 'lib/types/thread-types';
+
+import ThreadMember from './member.react';
+import css from './members-modal.css';
+
+type Props = {
+  +threadInfo: ThreadInfo,
+  +threadMembers: $ReadOnlyArray<RelativeMemberInfo>,
+};
+
+function ThreadMembersList(props: Props): React.Node {
+  const { threadMembers, threadInfo } = props;
+  const [openMenu, setOpenMenu] = React.useState(null);
+  const hasMembers = threadMembers.length > 0;
+
+  const groupedByFirstLetterMembers = React.useMemo(
+    () => _groupBy(member => stringForUser(member)[0])(threadMembers),
+    [threadMembers],
+  );
+
+  const groupedMembersList = React.useMemo(
+    () =>
+      _toPairs(groupedByFirstLetterMembers)
+        .sort((a, b) => a[0].localeCompare(b[0]))
+        .map(([letter, users]) => {
+          const userList = users
+            .sort((a, b) => stringForUser(a).localeCompare(stringForUser(b)))
+            .map((user: RelativeMemberInfo) => (
+              <ThreadMember
+                key={user.id}
+                memberInfo={user}
+                threadInfo={threadInfo}
+                setOpenMenu={setOpenMenu}
+                isMenuOpen={openMenu === user.id}
+              />
+            ));
+          const letterHeader = (
+            <h5 className={css.memberletterHeader} key={letter}>
+              {letter.toUpperCase()}
+            </h5>
+          );
+          return (
+            <React.Fragment key={letter}>
+              {letterHeader}
+              {userList}
+            </React.Fragment>
+          );
+        }),
+    [groupedByFirstLetterMembers, openMenu, threadInfo],
+  );
+  let content = groupedMembersList;
+  if (!hasMembers) {
+    content = (
+      <div className={css.noUsers}>
+        No matching users were found in the thread!
+      </div>
+    );
+  }
+  const membersListClasses = classNames(css.membersList, {
+    [css.noScroll]: !!openMenu,
+  });
+  return <div className={membersListClasses}>{content}</div>;
+}
+
+export default ThreadMembersList;
diff --git a/web/modals/threads/members/members-modal.css b/web/modals/threads/members/members-modal.css
--- a/web/modals/threads/members/members-modal.css
+++ b/web/modals/threads/members/members-modal.css
@@ -1,3 +1,13 @@
+div.membersList {
+  overflow: scroll;
+  padding: 8px 0;
+  color: var(--members-modal-member-text);
+}
+
+div.noScroll {
+  overflow: hidden;
+}
+
 div.memberContainer {
   display: flex;
   flex-direction: row;
@@ -23,3 +33,14 @@
 div.memberAction {
   position: relative;
 }
+
+h5.memberletterHeader {
+  margin: 16px;
+  color: var(--members-modal-member-text);
+  font-size: var(--s-font-14);
+}
+
+div.noUsers {
+  padding-top: 16px;
+  text-align: center;
+}
diff --git a/web/theme.css b/web/theme.css
--- a/web/theme.css
+++ b/web/theme.css
@@ -132,6 +132,8 @@
   --tabs-header-background-border: var(--shades-black-80);
   --tabs-header-background-color-hover: var(--shades-white-80);
   --tabs-header-background-border-hover: var(--shades-black-70);
+  --members-modal-member-text: var(--shades-black-60);
+  --members-modal-member-text-hover: var(--shades-white-100);
   --label-default-bg: var(--violet-dark-80);
   --label-default-color: var(--shades-white-80);
 }