diff --git a/web/chat/chat-message-list.react.js b/web/chat/chat-message-list.react.js
--- a/web/chat/chat-message-list.react.js
+++ b/web/chat/chat-message-list.react.js
@@ -32,13 +32,9 @@
 import { useSelector } from '../redux/redux-utils';
 import css from './chat-message-list.css';
 import { MessageListContext } from './message-list-types';
-import MessageTimestampTooltip from './message-timestamp-tooltip.react';
 import Message from './message.react';
-import type {
-  OnMessagePositionWithContainerInfo,
-  MessagePositionInfo,
-} from './position-types';
 import RelationshipPrompt from './relationship-prompt/relationship-prompt';
+import { useTooltipContext } from './tooltip-provider';
 
 type BaseProps = {
   +threadInfo: ThreadInfo,
@@ -64,18 +60,13 @@
   ) => Promise<FetchMessageInfosPayload>,
   // withInputState
   +inputState: ?InputState,
-};
-type State = {
-  +mouseOverMessagePosition: ?OnMessagePositionWithContainerInfo,
+  +clearTooltip: () => mixed,
 };
 type Snapshot = {
   +scrollTop: number,
   +scrollHeight: number,
 };
-class ChatMessageList extends React.PureComponent<Props, State> {
-  state: State = {
-    mouseOverMessagePosition: null,
-  };
+class ChatMessageList extends React.PureComponent<Props> {
   container: ?HTMLDivElement;
   messageContainer: ?HTMLDivElement;
   loadingFromScroll = false;
@@ -110,7 +101,7 @@
     );
   }
 
-  componentDidUpdate(prevProps: Props, prevState: State, snapshot: ?Snapshot) {
+  componentDidUpdate(prevProps: Props, prevState, snapshot: ?Snapshot) {
     const { messageListData } = this.props;
     const prevMessageListData = prevProps.messageListData;
 
@@ -185,51 +176,12 @@
       <Message
         item={item}
         threadInfo={threadInfo}
-        setMouseOverMessagePosition={this.setMouseOverMessagePosition}
-        mouseOverMessagePosition={this.state.mouseOverMessagePosition}
         timeZone={this.props.timeZone}
         key={ChatMessageList.keyExtractor(item)}
       />
     );
   };
 
-  setMouseOverMessagePosition = (messagePositionInfo: MessagePositionInfo) => {
-    if (!this.messageContainer) {
-      return;
-    }
-    if (messagePositionInfo.type === 'off') {
-      this.setState({ mouseOverMessagePosition: null });
-      return;
-    }
-    const {
-      top: containerTop,
-      bottom: containerBottom,
-      left: containerLeft,
-      right: containerRight,
-      height: containerHeight,
-      width: containerWidth,
-    } = this.messageContainer.getBoundingClientRect();
-    const mouseOverMessagePosition = {
-      ...messagePositionInfo,
-      messagePosition: {
-        ...messagePositionInfo.messagePosition,
-        top: messagePositionInfo.messagePosition.top - containerTop,
-        bottom: messagePositionInfo.messagePosition.bottom - containerTop,
-        left: messagePositionInfo.messagePosition.left - containerLeft,
-        right: messagePositionInfo.messagePosition.right - containerLeft,
-      },
-      containerPosition: {
-        top: containerTop,
-        bottom: containerBottom,
-        left: containerLeft,
-        right: containerRight,
-        height: containerHeight,
-        width: containerWidth,
-      },
-    };
-    this.setState({ mouseOverMessagePosition });
-  };
-
   render() {
     const { messageListData, threadInfo, inputState } = this.props;
     if (!messageListData) {
@@ -238,17 +190,6 @@
     invariant(inputState, 'InputState should be set');
     const messages = messageListData.map(this.renderItem);
 
-    let tooltip;
-    if (this.state.mouseOverMessagePosition) {
-      const messagePositionInfo = this.state.mouseOverMessagePosition;
-      tooltip = (
-        <MessageTimestampTooltip
-          messagePositionInfo={messagePositionInfo}
-          timeZone={this.props.timeZone}
-        />
-      );
-    }
-
     let relationshipPrompt;
     if (threadInfo) {
       relationshipPrompt = <RelationshipPrompt threadInfo={threadInfo} />;
@@ -264,7 +205,6 @@
         <div className={messageContainerStyle} ref={this.messageContainerRef}>
           {messages}
         </div>
-        {tooltip}
       </div>
     );
   }
@@ -283,9 +223,7 @@
     if (!this.messageContainer) {
       return;
     }
-    if (this.state.mouseOverMessagePosition) {
-      this.setState({ mouseOverMessagePosition: null });
-    }
+    this.props.clearTooltip();
     this.possiblyLoadMoreMessages();
   };
 
@@ -384,6 +322,8 @@
 
     const inputState = React.useContext(InputStateContext);
 
+    const { clearTooltip } = useTooltipContext();
+
     const getTextMessageMarkdownRules = useTextMessageRulesFunc(threadInfo.id);
     const messageListContext = React.useMemo(() => {
       if (!getTextMessageMarkdownRules) {
@@ -405,6 +345,7 @@
           dispatchActionPromise={dispatchActionPromise}
           fetchMessagesBeforeCursor={callFetchMessagesBeforeCursor}
           fetchMostRecentMessages={callFetchMostRecentMessages}
+          clearTooltip={clearTooltip}
         />
       </MessageListContext.Provider>
     );
diff --git a/web/chat/message.react.js b/web/chat/message.react.js
--- a/web/chat/message.react.js
+++ b/web/chat/message.react.js
@@ -10,20 +10,12 @@
 
 import css from './chat-message-list.css';
 import MultimediaMessage from './multimedia-message.react';
-import {
-  type OnMessagePositionWithContainerInfo,
-  type MessagePositionInfo,
-} from './position-types';
 import RobotextMessage from './robotext-message.react';
 import TextMessage from './text-message.react';
 
 type Props = {
   +item: ChatMessageInfoItem,
   +threadInfo: ThreadInfo,
-  +setMouseOverMessagePosition: (
-    messagePositionInfo: MessagePositionInfo,
-  ) => void,
-  +mouseOverMessagePosition: ?OnMessagePositionWithContainerInfo,
   +timeZone: ?string,
 };
 function Message(props: Props): React.Node {