diff --git a/web/modals/threads/subchannels/subchannel.react.js b/web/modals/threads/subchannels/subchannel.react.js
new file mode 100644
--- /dev/null
+++ b/web/modals/threads/subchannels/subchannel.react.js
@@ -0,0 +1,75 @@
+// @flow
+
+import * as React from 'react';
+
+import { type ChatThreadItem } from 'lib/selectors/chat-selectors';
+import { getMessagePreview } from 'lib/shared/message-utils';
+import { shortAbsoluteDate } from 'lib/utils/date-utils';
+
+import { getDefaultTextMessageRules } from '../../../markdown/rules.react';
+import { useSelector } from '../../../redux/redux-utils';
+import { useOnClickThread } from '../../../selectors/nav-selectors';
+import SWMansionIcon from '../../../SWMansionIcon.react';
+import { useModalContext } from '../../modal-provider.react';
+import css from './subchannels-modal.css';
+
+type Props = {
+  +chatThreadItem: ChatThreadItem,
+};
+
+function Subchannel(props: Props): React.Node {
+  const { chatThreadItem } = props;
+  const {
+    threadInfo,
+    mostRecentMessageInfo,
+    lastUpdatedTimeIncludingSidebars,
+  } = chatThreadItem;
+
+  const timeZone = useSelector(state => state.timeZone);
+  const { clearModal } = useModalContext();
+
+  const navigateToThread = useOnClickThread(threadInfo);
+
+  const onClickThread = React.useCallback(
+    event => {
+      clearModal();
+      navigateToThread(event);
+    },
+    [clearModal, navigateToThread],
+  );
+
+  const lastActivity = React.useMemo(
+    () => shortAbsoluteDate(lastUpdatedTimeIncludingSidebars, timeZone),
+    [lastUpdatedTimeIncludingSidebars, timeZone],
+  );
+
+  const lastMessage = React.useMemo(() => {
+    if (!mostRecentMessageInfo) {
+      return <div className={css.noMessage}>No messages</div>;
+    }
+    const { message, username } = getMessagePreview(
+      mostRecentMessageInfo,
+      threadInfo,
+      getDefaultTextMessageRules().simpleMarkdownRules,
+    );
+    const previewText = username ? `${username}: ${message}` : message;
+    return (
+      <>
+        <div className={css.longTextEllipsis}>{previewText}</div>
+        <div className={css.lastActivity}>{lastActivity}</div>
+      </>
+    );
+  }, [lastActivity, mostRecentMessageInfo, threadInfo]);
+
+  return (
+    <div className={css.subchannelContainer} onClick={onClickThread}>
+      <SWMansionIcon icon="message-square" size={22} />
+      <div className={css.subchannelInfo}>
+        <div className={css.longTextEllipsis}>{threadInfo.name}</div>
+        <div className={css.lastMessage}>{lastMessage}</div>
+      </div>
+    </div>
+  );
+}
+
+export default Subchannel;
diff --git a/web/modals/threads/subchannels/subchannels-modal.css b/web/modals/threads/subchannels/subchannels-modal.css
new file mode 100644
--- /dev/null
+++ b/web/modals/threads/subchannels/subchannels-modal.css
@@ -0,0 +1,39 @@
+div.subchannelContainer {
+  cursor: pointer;
+  display: flex;
+  padding: 8px 16px;
+  column-gap: 8px;
+}
+
+div.subchannelContainer:hover {
+  color: var(--subchannels-modal-color-hover);
+}
+
+div.subchannelInfo {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  row-gap: 8px;
+  overflow: hidden;
+}
+
+div.longTextEllipsis {
+  text-overflow: ellipsis;
+  overflow: hidden;
+  white-space: nowrap;
+}
+
+div.lastMessage {
+  display: flex;
+  justify-content: space-between;
+  column-gap: 14px;
+}
+
+div.noMessage {
+  text-align: center;
+  font-style: italic;
+}
+
+div.lastActivity {
+  white-space: nowrap;
+}
diff --git a/web/theme.css b/web/theme.css
--- a/web/theme.css
+++ b/web/theme.css
@@ -136,4 +136,6 @@
   --members-modal-member-text-hover: var(--shades-white-100);
   --label-default-bg: var(--violet-dark-80);
   --label-default-color: var(--shades-white-80);
+  --subchannels-modal-color: var(--shades-black-60);
+  --subchannels-modal-color-hover: var(--shades-white-100);
 }