diff --git a/web/modals/threads/thread-settings-modal.react.js b/web/modals/threads/thread-settings-modal.react.js
--- a/web/modals/threads/thread-settings-modal.react.js
+++ b/web/modals/threads/thread-settings-modal.react.js
@@ -7,7 +7,6 @@
 import {
   deleteThreadActionTypes,
   changeThreadSettingsActionTypes,
-  changeThreadSettings,
 } from 'lib/actions/thread-actions';
 import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors';
 import { threadInfoSelector } from 'lib/selectors/thread-selectors';
@@ -18,12 +17,7 @@
   threadPermissions,
   type ThreadChanges,
 } from 'lib/types/thread-types';
-import {
-  useDispatchActionPromise,
-  useServerCall,
-} from 'lib/utils/action-utils';
 
-import Button from '../../components/button.react';
 import { useModalContext } from '../../modals/modal-provider.react';
 import { useSelector } from '../../redux/redux-utils';
 import Modal from '../modal.react';
@@ -80,8 +74,6 @@
       state => state.currentUserInfo && state.currentUserInfo.id,
     );
     const userInfos = useSelector(state => state.userStore.userInfos);
-    const callChangeThreadSettings = useServerCall(changeThreadSettings);
-    const dispatchActionPromise = useDispatchActionPromise();
     const threadInfo: ?ThreadInfo = useSelector(
       state => threadInfoSelector(state)[props.threadID],
     );
@@ -99,12 +91,6 @@
       return robotextName(threadInfo, viewerID, userInfos);
     }, [threadInfo, userInfos, viewerID]);
 
-    const changeQueued: boolean = React.useMemo(
-      () =>
-        Object.values(queuedChanges).some(v => v !== null && v !== undefined),
-      [queuedChanges],
-    );
-
     const hasPermissionForTab = React.useCallback(
       (thread: ThreadInfo, tab: TabType) => {
         if (tab === 'general') {
@@ -125,37 +111,6 @@
       [],
     );
 
-    const changeThreadSettingsAction = React.useCallback(async () => {
-      invariant(
-        threadInfo,
-        'threadInfo should exist in changeThreadSettingsAction',
-      );
-      try {
-        const response = await callChangeThreadSettings({
-          threadID: threadInfo.id,
-          changes: queuedChanges,
-        });
-        modalContext.popModal();
-        return response;
-      } catch (e) {
-        setErrorMessage('unknown_error');
-        setCurrentTabType('general');
-        setQueuedChanges(Object.freeze({}));
-        throw e;
-      }
-    }, [callChangeThreadSettings, modalContext, queuedChanges, threadInfo]);
-
-    const onSubmit = React.useCallback(
-      (event: SyntheticEvent<HTMLElement>) => {
-        event.preventDefault();
-        dispatchActionPromise(
-          changeThreadSettingsActionTypes,
-          changeThreadSettingsAction(),
-        );
-      },
-      [changeThreadSettingsAction, dispatchActionPromise],
-    );
-
     React.useEffect(() => {
       if (
         threadInfo &&
@@ -198,6 +153,7 @@
           threadInfo={threadInfo}
           queuedChanges={queuedChanges}
           setQueuedChanges={setQueuedChanges}
+          setErrorMessage={setErrorMessage}
         />
       );
     } else if (currentTabType === 'delete') {
@@ -210,20 +166,6 @@
       );
     }
 
-    let buttons;
-    if (currentTabType === 'privacy') {
-      buttons = (
-        <Button
-          type="submit"
-          onClick={onSubmit}
-          disabled={inputDisabled || !changeQueued}
-          className={css.save_button}
-        >
-          Save
-        </Button>
-      );
-    }
-
     const tabs = [
       <Tab
         name="General"
@@ -276,7 +218,6 @@
           <form method="POST">
             {mainContent}
             <div className={css.form_footer}>
-              {buttons}
               <div className={css.modal_form_error}>{errorMessage}</div>
             </div>
           </form>
diff --git a/web/modals/threads/thread-settings-privacy-tab.css b/web/modals/threads/thread-settings-privacy-tab.css
--- a/web/modals/threads/thread-settings-privacy-tab.css
+++ b/web/modals/threads/thread-settings-privacy-tab.css
@@ -35,3 +35,7 @@
   max-width: 260px;
   color: gray;
 }
+
+.save_button {
+  width: 100%;
+}
diff --git a/web/modals/threads/thread-settings-privacy-tab.react.js b/web/modals/threads/thread-settings-privacy-tab.react.js
--- a/web/modals/threads/thread-settings-privacy-tab.react.js
+++ b/web/modals/threads/thread-settings-privacy-tab.react.js
@@ -2,6 +2,10 @@
 
 import * as React from 'react';
 
+import {
+  changeThreadSettings,
+  changeThreadSettingsActionTypes,
+} from 'lib/actions/thread-actions.js';
 import { threadTypeDescriptions } from 'lib/shared/thread-utils';
 import { type SetState } from 'lib/types/hook-types';
 import {
@@ -10,7 +14,13 @@
   assertThreadType,
   threadTypes,
 } from 'lib/types/thread-types';
+import {
+  useDispatchActionPromise,
+  useServerCall,
+} from 'lib/utils/action-utils.js';
 
+import Button from '../../components/button.react.js';
+import { useModalContext } from '../modal-provider.react.js';
 import css from './thread-settings-privacy-tab.css';
 
 const { COMMUNITY_OPEN_SUBTHREAD, COMMUNITY_SECRET_SUBTHREAD } = threadTypes;
@@ -20,11 +30,27 @@
   +threadInfo: ThreadInfo,
   +queuedChanges: ThreadChanges,
   +setQueuedChanges: SetState<ThreadChanges>,
+  +setErrorMessage: SetState<string>,
 };
 function ThreadSettingsPrivacyTab(
   props: ThreadSettingsPrivacyTabProps,
 ): React.Node {
-  const { inputDisabled, threadInfo, queuedChanges, setQueuedChanges } = props;
+  const {
+    inputDisabled,
+    threadInfo,
+    queuedChanges,
+    setQueuedChanges,
+    setErrorMessage,
+  } = props;
+
+  const modalContext = useModalContext();
+  const dispatchActionPromise = useDispatchActionPromise();
+  const callChangeThreadSettings = useServerCall(changeThreadSettings);
+
+  const changeQueued: boolean = React.useMemo(
+    () => Object.values(queuedChanges).some(v => v !== null && v !== undefined),
+    [queuedChanges],
+  );
 
   const onChangeThreadType = React.useCallback(
     (event: SyntheticEvent<HTMLInputElement>) => {
@@ -39,6 +65,39 @@
     [queuedChanges, setQueuedChanges, threadInfo.type],
   );
 
+  const changeThreadSettingsAction = React.useCallback(async () => {
+    try {
+      const response = await callChangeThreadSettings({
+        threadID: threadInfo.id,
+        changes: queuedChanges,
+      });
+      modalContext.popModal();
+      return response;
+    } catch (e) {
+      setErrorMessage('unknown_error');
+      setQueuedChanges(Object.freeze({}));
+      throw e;
+    }
+  }, [
+    callChangeThreadSettings,
+    modalContext,
+    queuedChanges,
+    setErrorMessage,
+    setQueuedChanges,
+    threadInfo.id,
+  ]);
+
+  const onSubmit = React.useCallback(
+    (event: SyntheticEvent<HTMLElement>) => {
+      event.preventDefault();
+      dispatchActionPromise(
+        changeThreadSettingsActionTypes,
+        changeThreadSettingsAction(),
+      );
+    },
+    [changeThreadSettingsAction, dispatchActionPromise],
+  );
+
   return (
     <div className={css.edit_thread_privacy_container}>
       <div className={css['modal-radio-selector']}>
@@ -90,6 +149,14 @@
           </div>
         </div>
       </div>
+      <Button
+        type="submit"
+        onClick={onSubmit}
+        disabled={inputDisabled || !changeQueued}
+        className={css.save_button}
+      >
+        Save
+      </Button>
     </div>
   );
 }