diff --git a/web/input/input-state-container.react.js b/web/input/input-state-container.react.js
--- a/web/input/input-state-container.react.js
+++ b/web/input/input-state-container.react.js
@@ -80,7 +80,12 @@
 import InvalidUploadModal from '../modals/chat/invalid-upload.react';
 import { useSelector } from '../redux/redux-utils';
 import { nonThreadCalendarQuery } from '../selectors/nav-selectors';
-import { type PendingMultimediaUpload, InputStateContext } from './input-state';
+import {
+  type PendingMultimediaUpload,
+  type InputState,
+  type TypeaheadState,
+  InputStateContext,
+} from './input-state';
 
 const browser = detectBrowser();
 const exifRotate =
@@ -126,6 +131,7 @@
     [threadID: string]: { [localUploadID: string]: PendingMultimediaUpload },
   },
   +textCursorPositions: { [threadID: string]: number },
+  +typeaheadState: TypeaheadState,
 };
 
 type PropsAndState = {
@@ -136,6 +142,13 @@
   state: State = {
     pendingUploads: {},
     textCursorPositions: {},
+    typeaheadState: {
+      canBeVisible: false,
+      moveChoiceUp: null,
+      moveChoiceDown: null,
+      close: null,
+      accept: null,
+    },
   };
   replyCallbacks: Array<(message: string) => void> = [];
   pendingThreadCreations = new Map<string, Promise<string>>();
@@ -460,7 +473,7 @@
     return threadCreationPromise;
   }
 
-  inputStateSelector = _memoize((threadID: string) =>
+  inputBaseStateSelector = _memoize((threadID: string) =>
     createSelector(
       (propsAndState: PropsAndState) => propsAndState.pendingUploads[threadID],
       (propsAndState: PropsAndState) =>
@@ -526,6 +539,23 @@
     ),
   );
 
+  typeaheadStateSelector = createSelector(
+    (propsAndState: PropsAndState) => propsAndState.typeaheadState,
+    (typeaheadState: TypeaheadState) => ({
+      typeaheadState,
+      setTypeaheadState: this.setTypeaheadState,
+    }),
+  );
+
+  inputStateSelector = createSelector(
+    state => state.inputBaseState,
+    state => state.typeaheadState,
+    (inputBaseState, typeaheadState) => ({
+      ...inputBaseState,
+      ...typeaheadState,
+    }),
+  );
+
   getRealizedOrPendingThreadID(threadID: string): string {
     return this.props.pendingRealizedThreadIDs.get(threadID) ?? threadID;
   }
@@ -1097,6 +1127,15 @@
     });
   }
 
+  setTypeaheadState = (newState: $Shape<TypeaheadState>) => {
+    this.setState(prevState => ({
+      typeaheadState: {
+        ...prevState.typeaheadState,
+        ...newState,
+      },
+    }));
+  };
+
   setProgress(
     threadID: string,
     localUploadID: string,
@@ -1242,12 +1281,27 @@
 
   render() {
     const { activeChatThreadID } = this.props;
-    const inputState = activeChatThreadID
-      ? this.inputStateSelector(activeChatThreadID)({
-          ...this.state,
-          ...this.props,
-        })
-      : null;
+
+    // we're going with two selectors as we want to avoid
+    // recreation of chat state setter functions on typeahead state updates
+    let inputState: ?InputState = null;
+    if (activeChatThreadID) {
+      const inputBaseState = this.inputBaseStateSelector(activeChatThreadID)({
+        ...this.state,
+        ...this.props,
+      });
+
+      const typeaheadState = this.typeaheadStateSelector({
+        ...this.state,
+        ...this.props,
+      });
+
+      inputState = this.inputStateSelector({
+        inputBaseState,
+        typeaheadState,
+      });
+    }
+
     return (
       <InputStateContext.Provider value={inputState}>
         {this.props.children}
diff --git a/web/input/input-state.js b/web/input/input-state.js
--- a/web/input/input-state.js
+++ b/web/input/input-state.js
@@ -34,6 +34,14 @@
   selectTime: number,
 };
 
+export type TypeaheadState = {
+  +canBeVisible: boolean,
+  +moveChoiceUp: ?() => void,
+  +moveChoiceDown: ?() => void,
+  +close: ?() => void,
+  +accept: ?() => void,
+};
+
 // This type represents the input state for a particular thread
 export type InputState = {
   +pendingUploads: $ReadOnlyArray<PendingMultimediaUpload>,
@@ -42,6 +50,7 @@
   },
   +draft: string,
   +textCursorPosition: number,
+  +typeaheadState: TypeaheadState,
   +appendFiles: (files: $ReadOnlyArray<File>) => Promise<boolean>,
   +cancelPendingUpload: (localUploadID: string) => void,
   +sendTextMessage: (
@@ -51,6 +60,7 @@
   +createMultimediaMessage: (localID: number, threadInfo: ThreadInfo) => void,
   +setDraft: (draft: string) => void,
   +setTextCursorPosition: (newPosition: number) => void,
+  +setTypeaheadState: ($Shape<TypeaheadState>) => void,
   +messageHasUploadFailure: (localMessageID: string) => boolean,
   +retryMultimediaMessage: (
     localMessageID: string,