diff --git a/web/invite-links/manage-invite-links-modal.react.js b/web/invite-links/manage-invite-links-modal.react.js
--- a/web/invite-links/manage-invite-links-modal.react.js
+++ b/web/invite-links/manage-invite-links-modal.react.js
@@ -6,6 +6,7 @@
 import { primaryInviteLinksSelector } from 'lib/selectors/invite-links-selectors.js';
 import type { InviteLink } from 'lib/types/link-types.js';
 
+import EditLinkModal from './manage/edit-link-modal.react.js';
 import EmptyLinkContent from './manage/empty-link-content.react.js';
 import ExistingLinkContent from './manage/existing-link-content.react.js';
 import css from './manage/manage-invite-links-modal.css';
@@ -23,11 +24,26 @@
   ];
   const { popModal } = useModalContext();
 
+  const [modalStage, setModalStage] = React.useState('view');
+  const enterEditMode = React.useCallback(() => setModalStage('edit'), []);
+  const enterViewMode = React.useCallback(() => setModalStage('view'), []);
+
+  if (modalStage === 'edit') {
+    return (
+      <EditLinkModal inviteLink={inviteLink} enterViewMode={enterViewMode} />
+    );
+  }
+
   let content;
   if (inviteLink) {
-    content = <ExistingLinkContent inviteLink={inviteLink} />;
+    content = (
+      <ExistingLinkContent
+        inviteLink={inviteLink}
+        enterEditMode={enterEditMode}
+      />
+    );
   } else {
-    content = <EmptyLinkContent />;
+    content = <EmptyLinkContent enterEditMode={enterEditMode} />;
   }
 
   return (
diff --git a/web/invite-links/manage/edit-link-modal.react.js b/web/invite-links/manage/edit-link-modal.react.js
new file mode 100644
--- /dev/null
+++ b/web/invite-links/manage/edit-link-modal.react.js
@@ -0,0 +1,67 @@
+// @flow
+
+import classnames from 'classnames';
+import * as React from 'react';
+
+import { useModalContext } from 'lib/components/modal-provider.react.js';
+import { inviteLinkUrl } from 'lib/facts/links.js';
+import type { InviteLink } from 'lib/types/link-types.js';
+
+import css from './manage-invite-links-modal.css';
+import Button from '../../components/button.react.js';
+import Input from '../../modals/input.react.js';
+import Modal from '../../modals/modal.react.js';
+
+type Props = {
+  +inviteLink: ?InviteLink,
+  +enterViewMode: () => mixed,
+};
+
+function EditLinkModal(props: Props): React.Node {
+  const { inviteLink, enterViewMode } = props;
+  const [name, setName] = React.useState(
+    inviteLink?.name ?? Math.random().toString(36).slice(-9),
+  );
+  const { popModal } = useModalContext();
+
+  const onChangeName = React.useCallback(
+    (event: SyntheticEvent<HTMLInputElement>) => {
+      setName(event.currentTarget.value);
+    },
+    [],
+  );
+
+  return (
+    <Modal name="Public link" onClose={popModal} size="large">
+      <div className={classnames(css.container, css.editLinkContainer)}>
+        <div className={css.editLinkDescription}>
+          <p>
+            Invite links make it easy for your friends to join your community.
+            Anybody who knows your community’s invite link will be able to join
+            it.
+          </p>
+          <p>
+            Note that if you change your public link’s URL, other communities
+            will be able to claim the old URL.
+          </p>
+        </div>
+        <hr className={css.separator} />
+        <div className={css.linkSection}>
+          Invite URL
+          <div className={css.linkRow}>
+            {inviteLinkUrl('')}
+            <Input type="text" value={name} onChange={onChangeName} />
+          </div>
+        </div>
+        <div className={css.buttonRow}>
+          <Button variant="outline" onClick={enterViewMode}>
+            Back
+          </Button>
+          <Button variant="filled">Save & enable public link</Button>
+        </div>
+      </div>
+    </Modal>
+  );
+}
+
+export default EditLinkModal;
diff --git a/web/invite-links/manage/empty-link-content.react.js b/web/invite-links/manage/empty-link-content.react.js
--- a/web/invite-links/manage/empty-link-content.react.js
+++ b/web/invite-links/manage/empty-link-content.react.js
@@ -9,11 +9,16 @@
   color: 'var(--purple-link)',
 };
 
-function EmptyLinkContent(): React.Node {
+type Props = {
+  +enterEditMode: () => mixed,
+};
+
+function EmptyLinkContent(props: Props): React.Node {
+  const { enterEditMode } = props;
   return (
     <div className={css.sectionHeaderRow}>
       <div className={css.sectionHeaderText}>Public link</div>
-      <Button variant="text" buttonColor={buttonColor}>
+      <Button variant="text" buttonColor={buttonColor} onClick={enterEditMode}>
         Enable
       </Button>
     </div>
diff --git a/web/invite-links/manage/existing-link-content.react.js b/web/invite-links/manage/existing-link-content.react.js
--- a/web/invite-links/manage/existing-link-content.react.js
+++ b/web/invite-links/manage/existing-link-content.react.js
@@ -14,9 +14,10 @@
 
 type Props = {
   +inviteLink: InviteLink,
+  +enterEditMode: () => mixed,
 };
 function ExistingLinkContent(props: Props): React.Node {
-  const { inviteLink } = props;
+  const { inviteLink, enterEditMode } = props;
   return (
     <>
       <div className={css.sectionHeaderRow}>
@@ -30,6 +31,7 @@
             variant="text"
             buttonColor={buttonColor}
             className={css.inlineButton}
+            onClick={enterEditMode}
           >
             Edit public link
           </Button>
diff --git a/web/invite-links/manage/manage-invite-links-modal.css b/web/invite-links/manage/manage-invite-links-modal.css
--- a/web/invite-links/manage/manage-invite-links-modal.css
+++ b/web/invite-links/manage/manage-invite-links-modal.css
@@ -25,3 +25,47 @@
   display: inline;
   font-size: var(--s-font-14);
 }
+
+.editLinkContainer {
+  display: flex;
+  flex-direction: column;
+  gap: 32px;
+  color: var(--modal-fg);
+  font-size: var(--s-font-14);
+}
+
+.editLinkDescription {
+  font-weight: var(--semi-bold);
+  color: var(--modal-fg);
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.editLinkDescription p {
+  font-size: var(--s-font-14);
+}
+
+.separator {
+  border-color: var(--border-color);
+  margin: 0;
+}
+
+.linkSection {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.linkRow {
+  display: flex;
+  font-size: var(--m-font-16);
+  align-items: center;
+  gap: 8px;
+}
+
+.buttonRow {
+  display: flex;
+  justify-content: flex-end;
+  gap: 8px;
+}
diff --git a/web/modals/input.react.js b/web/modals/input.react.js
--- a/web/modals/input.react.js
+++ b/web/modals/input.react.js
@@ -18,7 +18,7 @@
 export type InputProps = {
   ...BaseInputProps,
   +type: string,
-  +placeholder: string,
+  +placeholder?: string,
   +maxLength?: number,
 };