diff --git a/external/lang b/external/lang
index 4f3c6a8d77f2977c8d5c69690ba4fbe477610f09..2a0b524d892349cb956ee49af873fc87999e2206 160000
--- a/external/lang
+++ b/external/lang
@@ -1 +1 @@
-Subproject commit 4f3c6a8d77f2977c8d5c69690ba4fbe477610f09
+Subproject commit 2a0b524d892349cb956ee49af873fc87999e2206
diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx
index 67009c7c19a6ed2fd240065ff65c25100de00e17..d9c9a1004b184479de26c66a68569f79bd19cc82 100644
--- a/src/components/ui/Modal.tsx
+++ b/src/components/ui/Modal.tsx
@@ -1,9 +1,10 @@
 import styled, { css, keyframes } from "styled-components";
 
-import { createPortal, useEffect } from "preact/compat";
+import { createPortal, useEffect, useState } from "preact/compat";
 
 import { Children } from "../../types/Preact";
 import Button, { ButtonProps } from "./Button";
+import { internalSubscribe } from "../../lib/eventEmitter";
 
 const open = keyframes`
     0% {opacity: 0;}
@@ -11,12 +12,23 @@ const open = keyframes`
     100% {opacity: 1;}
 `;
 
+const close = keyframes`
+    0% {opacity: 1;}
+    70% {opacity: 0;}
+    100% {opacity: 0;}
+`;
+
 const zoomIn = keyframes`
     0% {transform: scale(0.5);}
     98% {transform: scale(1.01);}
     100% {transform: scale(1);}
 `;
 
+const zoomOut = keyframes`
+    0% {transform: scale(1);}
+    100% {transform: scale(0.5);}
+`;
+
 const ModalBase = styled.div`
     top: 0;
     left: 0;
@@ -36,6 +48,14 @@ const ModalBase = styled.div`
 
     color: var(--foreground);
     background: rgba(0, 0, 0, 0.8);
+
+    &.closing {
+        animation-name: ${close};
+    }
+    
+    &.closing > div {
+        animation-name: ${zoomOut};
+    }
 `;
 
 const ModalContainer = styled.div`
@@ -120,6 +140,8 @@ interface Props {
     visible: boolean;
 }
 
+export let isModalClosing = false;
+
 export default function Modal(props: Props) {
     if (!props.visible) return null;
 
@@ -138,12 +160,21 @@ export default function Modal(props: Props) {
         return content;
     }
 
+    const [animateClose, setAnimateClose] = useState(false);
+    isModalClosing = animateClose;
+    function onClose() {
+        setAnimateClose(true);
+        setTimeout(() => props.onClose(), 2e2);
+    }
+
+    useEffect(() => internalSubscribe('Modal', 'close', onClose), []);
+
     useEffect(() => {
         if (props.disallowClosing) return;
 
         function keyDown(e: KeyboardEvent) {
             if (e.key === "Escape") {
-                props.onClose();
+                onClose();
             }
         }
 
@@ -154,6 +185,7 @@ export default function Modal(props: Props) {
     let confirmationAction = props.actions?.find(
         (action) => action.confirmation,
     );
+
     useEffect(() => {
         if (!confirmationAction) return;
 
@@ -171,7 +203,7 @@ export default function Modal(props: Props) {
     }, [confirmationAction]);
 
     return createPortal(
-        <ModalBase
+        <ModalBase className={animateClose ? 'closing' : undefined}
             onClick={(!props.disallowClosing && props.onClose) || undefined}>
             <ModalContainer onClick={(e) => (e.cancelBubble = true)}>
                 {content}
diff --git a/src/context/intermediate/Modals.tsx b/src/context/intermediate/Modals.tsx
index 4db686dd221dc245a04adb703384ce6e0e601629..9ed2438e153268c0641e4547a699aebbef96efd3 100644
--- a/src/context/intermediate/Modals.tsx
+++ b/src/context/intermediate/Modals.tsx
@@ -1,3 +1,5 @@
+import { isModalClosing } from "../../components/ui/Modal";
+import { internalEmit } from "../../lib/eventEmitter";
 import { Screen } from "./Intermediate";
 import { ClipboardModal } from "./modals/Clipboard";
 import { ErrorModal } from "./modals/Error";
@@ -12,7 +14,7 @@ export interface Props {
 }
 
 export default function Modals({ screen, openScreen }: Props) {
-    const onClose = () => openScreen({ id: "none" });
+    const onClose = () => isModalClosing ? openScreen({ id: "none" }) : internalEmit('Modal', 'close');
 
     switch (screen.id) {
         case "_prompt":
diff --git a/src/context/intermediate/Popovers.tsx b/src/context/intermediate/Popovers.tsx
index f2dc82cc8f54c9e05a2d6a0fab76b0e5610c2b1a..44c0d9ecd8ff030015c14ab13da841ecf57df84c 100644
--- a/src/context/intermediate/Popovers.tsx
+++ b/src/context/intermediate/Popovers.tsx
@@ -1,4 +1,6 @@
 import { useContext } from "preact/hooks";
+import { isModalClosing } from "../../components/ui/Modal";
+import { internalEmit } from "../../lib/eventEmitter";
 
 import { IntermediateContext, useIntermediate } from "./Intermediate";
 import { SpecialInputModal } from "./modals/Input";
@@ -14,7 +16,7 @@ export default function Popovers() {
     const { screen } = useContext(IntermediateContext);
     const { openScreen } = useIntermediate();
 
-    const onClose = () => openScreen({ id: "none" });
+    const onClose = () => isModalClosing ? openScreen({ id: "none" }) : internalEmit('Modal', 'close');
 
     switch (screen.id) {
         case "profile":
diff --git a/src/lib/eventEmitter.ts b/src/lib/eventEmitter.ts
index 192bae462974b8f5ea30132e39f2cb4dc26c0b53..751f52d23e3986b689780ea1dc412590bb52ee7f 100644
--- a/src/lib/eventEmitter.ts
+++ b/src/lib/eventEmitter.ts
@@ -26,4 +26,5 @@ export function internalEmit(ns: string, event: string, ...args: any[]) {
 // - MessageBox/append
 // - TextArea/focus
 // - ReplyBar/add
+// - Modal/close
 // - PWA/update