diff --git a/web/chat/thread-menu.css b/web/chat/thread-menu.css
new file mode 100644
--- /dev/null
+++ b/web/chat/thread-menu.css
@@ -0,0 +1,18 @@
+button.topBarMenuButton {
+  background-color: transparent;
+  border: none;
+  cursor: pointer;
+  color: var(--thread-top-bar-menu-color);
+}
+
+div.topBarMenuActionList {
+  position: absolute;
+  right: 10px;
+  top: 55px;
+  z-index: 1;
+  display: flex;
+  flex-direction: column;
+  background-color: var(--thread-menu-bg);
+  border-radius: 4px;
+  padding: 4px 0;
+}
diff --git a/web/chat/thread-menu.react.js b/web/chat/thread-menu.react.js
new file mode 100644
--- /dev/null
+++ b/web/chat/thread-menu.react.js
@@ -0,0 +1,62 @@
+// @flow
+
+import * as React from 'react';
+
+import { type ThreadInfo } from 'lib/types/thread-types';
+
+import SWMansionIcon from '../SWMansionIcon.react';
+import css from './thread-menu.css';
+
+type ThreadMenuProps = {
+  +threadInfo: ThreadInfo,
+};
+
+function ThreadMenu(props: ThreadMenuProps): React.Node {
+  const [isOpen, setIsOpen] = React.useState(false);
+
+  // eslint-disable-next-line no-unused-vars
+  const { threadInfo } = props;
+
+  const menuItems = [];
+
+  const closeMenuCallback = React.useCallback(() => {
+    document.removeEventListener('click', closeMenuCallback);
+    if (isOpen) {
+      setIsOpen(false);
+    }
+  }, [isOpen]);
+
+  React.useEffect(() => {
+    if (!document || !isOpen) {
+      return undefined;
+    }
+    document.addEventListener('click', closeMenuCallback);
+    return () => document.removeEventListener('click', closeMenuCallback);
+  }, [closeMenuCallback, isOpen]);
+
+  const switchMenuCallback = React.useCallback(() => {
+    setIsOpen(isMenuOpen => !isMenuOpen);
+  }, []);
+
+  if (menuItems.length === 0) {
+    return null;
+  }
+
+  let menuActionList = null;
+  if (isOpen) {
+    menuActionList = (
+      <div className={css.topBarMenuActionList}>{menuItems}</div>
+    );
+  }
+
+  return (
+    <div>
+      <button className={css.topBarMenuButton} onClick={switchMenuCallback}>
+        <SWMansionIcon icon="menu-vertical" size={20} />
+      </button>
+      {menuActionList}
+    </div>
+  );
+}
+
+export default ThreadMenu;
diff --git a/web/chat/thread-top-bar.react.js b/web/chat/thread-top-bar.react.js
--- a/web/chat/thread-top-bar.react.js
+++ b/web/chat/thread-top-bar.react.js
@@ -4,8 +4,8 @@
 
 import type { ThreadInfo } from 'lib/types/thread-types';
 
-import SWMansionIcon from '../SWMansionIcon.react';
 import ThreadAncestors from './chat-thread-ancestors.react';
+import ThreadMenu from './thread-menu.react';
 import css from './thread-top-bar.css';
 
 type threadTopBarProps = {
@@ -30,9 +30,7 @@
         <p className={css.threadTitle}>{threadInfo.uiName}</p>
         <ThreadAncestors threadInfo={threadInfo} />
       </div>
-      <button className={css.topBarMenu}>
-        <SWMansionIcon icon="menu-vertical" size={20} />
-      </button>
+      <ThreadMenu threadInfo={threadInfo} />
     </div>
   );
 }
diff --git a/web/theme.css b/web/theme.css
--- a/web/theme.css
+++ b/web/theme.css
@@ -101,4 +101,5 @@
   --thread-ancestor-color-dark: var(--shades-black-100);
   --text-message-default-background: var(--shades-black-80);
   --message-action-tooltip-bg: var(--shades-black-90);
+  --thread-menu-bg: var(--shades-black-90);
 }