diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss
index cca1789904..546327ff40 100644
--- a/res/css/views/dialogs/_InviteDialog.scss
+++ b/res/css/views/dialogs/_InviteDialog.scss
@@ -130,12 +130,8 @@ limitations under the License.
         text-transform: uppercase;
     }
 
-    .mx_InviteDialog_footer_link {
-        display: flex;
-        justify-content: space-between;
-        border-radius: 4px;
-        border: solid 1px $light-fg-color;
-        padding: 8px;
+    .mx_CopyableText {
+        width: unset; // full width
 
         > a {
             text-decoration: none;
@@ -144,22 +140,6 @@ limitations under the License.
             text-overflow: ellipsis;
         }
     }
-
-    .mx_InviteDialog_footer_link_copy {
-        flex-shrink: 0;
-        cursor: pointer;
-        margin-left: 20px;
-        display: inherit;
-
-        > div {
-            mask-image: url($copy-button-url);
-            background-color: $message-action-bar-fg-color;
-            margin-left: 5px;
-            width: 20px;
-            height: 20px;
-            background-repeat: no-repeat;
-        }
-    }
 }
 
 .mx_InviteDialog_roomTile {
diff --git a/res/css/views/dialogs/_ShareDialog.scss b/res/css/views/dialogs/_ShareDialog.scss
index 4d5e1409db..a55849ea08 100644
--- a/res/css/views/dialogs/_ShareDialog.scss
+++ b/res/css/views/dialogs/_ShareDialog.scss
@@ -20,44 +20,20 @@ limitations under the License.
     border-color: $light-fg-color;
 }
 
-.mx_ShareDialog_content {
+.mx_ShareDialog .mx_ShareDialog_content {
     margin: 10px 0;
-}
 
-.mx_ShareDialog_matrixto {
-    display: flex;
-    justify-content: space-between;
-    border-radius: 5px;
-    border: solid 1px $light-fg-color;
-    margin-bottom: 10px;
-    margin-top: 30px;
-    padding: 10px;
-}
+    .mx_CopyableText {
+        width: unset; // full width
 
-.mx_ShareDialog_matrixto a {
-    text-decoration: none;
-}
-
-.mx_ShareDialog_matrixto_link {
-    flex-shrink: 1;
-    overflow: hidden;
-    text-overflow: ellipsis;
-}
-
-.mx_ShareDialog_matrixto_copy {
-    flex-shrink: 0;
-    cursor: pointer;
-    margin-left: 20px;
-    display: inherit;
-}
-.mx_ShareDialog_matrixto_copy::after {
-    content: "";
-    mask-image: url($copy-button-url);
-    background-color: $message-action-bar-fg-color;
-    margin-left: 5px;
-    width: 20px;
-    height: 20px;
-    background-repeat: no-repeat;
+        > a {
+            text-decoration: none;
+            flex-shrink: 1;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+        }
+    }
 }
 
 .mx_ShareDialog_split {
diff --git a/res/css/views/elements/_CopyableText.scss b/res/css/views/elements/_CopyableText.scss
index 690db0e24c..a08306b66a 100644
--- a/res/css/views/elements/_CopyableText.scss
+++ b/res/css/views/elements/_CopyableText.scss
@@ -17,6 +17,7 @@ limitations under the License.
 
 .mx_CopyableText {
     display: flex;
+    justify-content: space-between;
     border-radius: 5px;
     border: solid 1px $light-fg-color;
     margin-bottom: 10px;
diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx
index 8cc04d8628..ee82ae1bea 100644
--- a/src/components/views/dialogs/InviteDialog.tsx
+++ b/src/components/views/dialogs/InviteDialog.tsx
@@ -58,11 +58,7 @@ import { mediaFromMxc } from "../../../customisations/Media";
 import { getAddressType } from "../../../UserAddress";
 import BaseAvatar from '../avatars/BaseAvatar';
 import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
-import { compare, copyPlaintext, selectText } from '../../../utils/strings';
-import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
-import * as ContextMenu from "../../structures/ContextMenu";
-import { toRightOf } from "../../structures/ContextMenu";
-import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
+import { compare, selectText } from '../../../utils/strings';
 import Field from '../elements/Field';
 import TabbedView, { Tab, TabLocation } from '../../structures/TabbedView';
 import Dialpad from '../voip/DialPad';
@@ -73,6 +69,7 @@ import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
 import SpaceStore from "../../../stores/spaces/SpaceStore";
 import CallHandler from "../../../CallHandler";
 import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
+import CopyableText from "../elements/CopyableText";
 
 // we have a number of types defined from the Matrix spec which can't reasonably be altered here.
 /* eslint-disable camelcase */
@@ -1310,20 +1307,6 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
         selectText(e.target);
     }
 
-    private onCopyClick = async e => {
-        e.preventDefault();
-        const target = e.target; // copy target before we go async and React throws it away
-
-        const successful = await copyPlaintext(makeUserPermalink(MatrixClientPeg.get().getUserId()));
-        const buttonRect = target.getBoundingClientRect();
-        const { close } = ContextMenu.createMenu(GenericTextContextMenu, {
-            ...toRightOf(buttonRect, 2),
-            message: successful ? _t("Copied!") : _t("Failed to copy"),
-        });
-        // Drop a reference to this close handler for componentWillUnmount
-        this.closeCopiedTooltip = target.onmouseleave = close;
-    };
-
     render() {
         let spinner = null;
         if (this.state.busy) {
@@ -1409,18 +1392,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
             const link = makeUserPermalink(MatrixClientPeg.get().getUserId());
             footer = <div className="mx_InviteDialog_footer">
                 <h3>{ _t("Or send invite link") }</h3>
-                <div className="mx_InviteDialog_footer_link">
+                <CopyableText getTextToCopy={() => makeUserPermalink(MatrixClientPeg.get().getUserId())}>
                     <a href={link} onClick={this.onLinkClick}>
                         { link }
                     </a>
-                    <AccessibleTooltipButton
-                        title={_t("Copy")}
-                        onClick={this.onCopyClick}
-                        className="mx_InviteDialog_footer_link_copy"
-                    >
-                        <div />
-                    </AccessibleTooltipButton>
-                </div>
+                </CopyableText>
             </div>;
         } else if (this.props.kind === KIND_INVITE) {
             const room = MatrixClientPeg.get()?.getRoom(this.props.roomId);
diff --git a/src/components/views/dialogs/ShareDialog.tsx b/src/components/views/dialogs/ShareDialog.tsx
index 0919163095..3262f61c73 100644
--- a/src/components/views/dialogs/ShareDialog.tsx
+++ b/src/components/views/dialogs/ShareDialog.tsx
@@ -16,7 +16,6 @@ limitations under the License.
 */
 
 import * as React from 'react';
-import * as PropTypes from 'prop-types';
 import { Room } from "matrix-js-sdk/src/models/room";
 import { User } from "matrix-js-sdk/src/models/user";
 import { Group } from "matrix-js-sdk/src/models/group";
@@ -26,17 +25,14 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
 import { _t } from '../../../languageHandler';
 import QRCode from "../elements/QRCode";
 import { RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink } from "../../../utils/permalinks/Permalinks";
-import * as ContextMenu from "../../structures/ContextMenu";
-import { toRightOf } from "../../structures/ContextMenu";
-import { copyPlaintext, selectText } from "../../../utils/strings";
+import { selectText } from "../../../utils/strings";
 import StyledCheckbox from '../elements/StyledCheckbox';
-import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
 import { IDialogProps } from "./IDialogProps";
 import SettingsStore from "../../../settings/SettingsStore";
 import { UIFeature } from "../../../settings/UIFeature";
 import { replaceableComponent } from "../../../utils/replaceableComponent";
 import BaseDialog from "./BaseDialog";
-import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
+import CopyableText from "../elements/CopyableText";
 
 const socials = [
     {
@@ -78,25 +74,11 @@ interface IState {
 
 @replaceableComponent("views.dialogs.ShareDialog")
 export default class ShareDialog extends React.PureComponent<IProps, IState> {
-    static propTypes = {
-        onFinished: PropTypes.func.isRequired,
-        target: PropTypes.oneOfType([
-            PropTypes.instanceOf(Room),
-            PropTypes.instanceOf(User),
-            PropTypes.instanceOf(Group),
-            PropTypes.instanceOf(RoomMember),
-            PropTypes.instanceOf(MatrixEvent),
-        ]).isRequired,
-    };
-
     protected closeCopiedTooltip: () => void;
 
     constructor(props) {
         super(props);
 
-        this.onCopyClick = this.onCopyClick.bind(this);
-        this.onLinkSpecificEventCheckboxClick = this.onLinkSpecificEventCheckboxClick.bind(this);
-
         let permalinkCreator: RoomPermalinkCreator = null;
         if (props.target instanceof Room) {
             permalinkCreator = new RoomPermalinkCreator(props.target);
@@ -115,25 +97,11 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
         selectText(e.target);
     }
 
-    async onCopyClick(e) {
-        e.preventDefault();
-        const target = e.target; // copy target before we go async and React throws it away
-
-        const successful = await copyPlaintext(this.getUrl());
-        const buttonRect = target.getBoundingClientRect();
-        const { close } = ContextMenu.createMenu(GenericTextContextMenu, {
-            ...toRightOf(buttonRect, 2),
-            message: successful ? _t('Copied!') : _t('Failed to copy'),
-        });
-        // Drop a reference to this close handler for componentWillUnmount
-        this.closeCopiedTooltip = target.onmouseleave = close;
-    }
-
-    onLinkSpecificEventCheckboxClick() {
+    private onLinkSpecificEventCheckboxClick = () => {
         this.setState({
             linkSpecificEvent: !this.state.linkSpecificEvent,
         });
-    }
+    };
 
     componentWillUnmount() {
         // if the Copied tooltip is open then get rid of it, there are ways to close the modal which wouldn't close
@@ -141,7 +109,7 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
         if (this.closeCopiedTooltip) this.closeCopiedTooltip();
     }
 
-    getUrl() {
+    private getUrl() {
         let matrixToUrl;
 
         if (this.props.target instanceof Room) {
@@ -238,21 +206,11 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
             onFinished={this.props.onFinished}
         >
             <div className="mx_ShareDialog_content">
-                <div className="mx_ShareDialog_matrixto">
-                    <a
-                        title={_t('Link to room')}
-                        href={matrixToUrl}
-                        onClick={ShareDialog.onLinkClick}
-                        className="mx_ShareDialog_matrixto_link"
-                    >
+                <CopyableText getTextToCopy={() => matrixToUrl}>
+                    <a title={_t('Link to room')} href={matrixToUrl} onClick={ShareDialog.onLinkClick}>
                         { matrixToUrl }
                     </a>
-                    <AccessibleTooltipButton
-                        title={_t("Copy")}
-                        onClick={this.onCopyClick}
-                        className="mx_ShareDialog_matrixto_copy"
-                    />
-                </div>
+                </CopyableText>
                 { checkbox }
                 { qrSocialSection }
             </div>