diff --git a/src/app.css b/src/app.css
index 1a37f161..7848f645 100644
--- a/src/app.css
+++ b/src/app.css
@@ -1609,6 +1609,47 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
     bottom: calc(16px + env(safe-area-inset-bottom) + 52px);
   }
 }
+#compose-button {
+  &.min {
+    outline: 2px solid var(--button-text-color);
+
+    &:after {
+      content: '';
+      display: block;
+      position: absolute;
+      top: 0;
+      right: 0;
+      width: 14px;
+      height: 14px;
+      border-radius: 50%;
+      background-color: var(--button-bg-color);
+      border: 2px solid var(--button-text-color);
+      box-shadow: 0 2px 8px var(--drop-shadow-color);
+      opacity: 0;
+      transition: opacity 0.2s ease-out 0.5s;
+      opacity: 1;
+    }
+  }
+
+  &.loading {
+    outline-color: var(--button-bg-blur-color);
+
+    &:before {
+      position: absolute;
+      inset: 0;
+      content: '';
+      border-radius: 50%;
+      animation: spin 5s linear infinite;
+      border: 2px dashed var(--button-text-color);
+    }
+  }
+
+  &.error {
+    &:after {
+      background-color: var(--red-color);
+    }
+  }
+}
 
 /* SHEET */
 
diff --git a/src/components/ICONS.jsx b/src/components/ICONS.jsx
index 4ab8b8d3..0fa7880f 100644
--- a/src/components/ICONS.jsx
+++ b/src/components/ICONS.jsx
@@ -108,4 +108,5 @@ export const ICONS = {
   settings: () => import('@iconify-icons/mingcute/settings-6-line'),
   'heart-break': () => import('@iconify-icons/mingcute/heart-crack-line'),
   'user-x': () => import('@iconify-icons/mingcute/user-x-line'),
+  minimize: () => import('@iconify-icons/mingcute/arrows-down-line'),
 };
diff --git a/src/components/account-info.jsx b/src/components/account-info.jsx
index c585c450..fbb1debb 100644
--- a/src/components/account-info.jsx
+++ b/src/components/account-info.jsx
@@ -19,6 +19,7 @@ import { getLists } from '../utils/lists';
 import niceDateTime from '../utils/nice-date-time';
 import pmem from '../utils/pmem';
 import shortenNumber from '../utils/shorten-number';
+import showCompose from '../utils/show-compose';
 import showToast from '../utils/show-toast';
 import states, { hideAllModals } from '../utils/states';
 import store from '../utils/store';
@@ -1081,11 +1082,11 @@ function RelatedActions({
               <>
                 <MenuItem
                   onClick={() => {
-                    states.showCompose = {
+                    showCompose({
                       draftStatus: {
                         status: `@${currentInfo?.acct || acct} `,
                       },
-                    };
+                    });
                   }}
                 >
                   <Icon icon="at" />
diff --git a/src/components/compose-button.jsx b/src/components/compose-button.jsx
index ef64adf7..66d84ab6 100644
--- a/src/components/compose-button.jsx
+++ b/src/components/compose-button.jsx
@@ -1,4 +1,5 @@
 import { useHotkeys } from 'react-hotkeys-hook';
+import { useSnapshot } from 'valtio';
 
 import openCompose from '../utils/open-compose';
 import openOSK from '../utils/open-osk';
@@ -7,7 +8,15 @@ import states from '../utils/states';
 import Icon from './icon';
 
 export default function ComposeButton() {
+  const snapStates = useSnapshot(states);
+
   function handleButton(e) {
+    if (snapStates.composerState.minimized) {
+      states.composerState.minimized = false;
+      openOSK();
+      return;
+    }
+
     if (e.shiftKey) {
       const newWin = openCompose();
 
@@ -28,7 +37,14 @@ export default function ComposeButton() {
   });
 
   return (
-    <button type="button" id="compose-button" onClick={handleButton}>
+    <button
+      type="button"
+      id="compose-button"
+      onClick={handleButton}
+      class={`${snapStates.composerState.minimized ? 'min' : ''} ${
+        snapStates.composerState.publishing ? 'loading' : ''
+      } ${snapStates.composerState.publishingError ? 'error' : ''}`}
+    >
       <Icon icon="quill" size="xl" alt="Compose" />
     </button>
   );
diff --git a/src/components/compose.jsx b/src/components/compose.jsx
index 4e0710e5..c402eb66 100644
--- a/src/components/compose.jsx
+++ b/src/components/compose.jsx
@@ -514,6 +514,7 @@ function Compose({
     // I don't think this warrant a draft mode for a status that's already posted
     // Maybe it could be a big edit change but it should be rare
     if (editStatus) return;
+    if (states.composerState.minimized) return;
     const key = draftKey();
     const backgroundDraft = {
       key,
@@ -670,6 +671,11 @@ function Compose({
     [replyToStatus],
   );
 
+  const onMinimize = () => {
+    saveUnsavedDraft();
+    states.composerState.minimized = true;
+  };
+
   return (
     <div id="compose-container-outer">
       <div id="compose-container" class={standalone ? 'standalone' : ''}>
@@ -689,7 +695,7 @@ function Compose({
             />
           )}
           {!standalone ? (
-            <span>
+            <span class="button-group">
               <button
                 type="button"
                 class="light pop-button"
@@ -736,6 +742,13 @@ function Compose({
               >
                 <Icon icon="popout" alt="Pop out" />
               </button>{' '}
+              <button
+                type="button"
+                class="light min-button"
+                onClick={onMinimize}
+              >
+                <Icon icon="minimize" alt="Minimize" />
+              </button>{' '}
               <button
                 type="button"
                 class="light close-button"
@@ -810,6 +823,10 @@ function Compose({
                       } else {
                         window.opener.__STATES__.showCompose = true;
                       }
+                      if (window.opener.__STATES__.composerState.minimized) {
+                        // Maximize it
+                        window.opener.__STATES__.composerState.minimized = false;
+                      }
                     },
                   });
                 }}
@@ -915,6 +932,8 @@ function Compose({
             spoilerText = (sensitive && spoilerText) || undefined;
             status = status === '' ? undefined : status;
 
+            // states.composerState.minimized = true;
+            states.composerState.publishing = true;
             setUIState('loading');
             (async () => {
               try {
@@ -948,6 +967,8 @@ function Compose({
                       return result.status === 'rejected' || !result.value?.id;
                     })
                   ) {
+                    states.composerState.publishing = false;
+                    states.composerState.publishingError = true;
                     setUIState('error');
                     // Alert all the reasons
                     results.forEach((result) => {
@@ -1021,6 +1042,8 @@ function Compose({
                     newStatus = await masto.v1.statuses.create(params);
                   }
                 }
+                states.composerState.minimized = false;
+                states.composerState.publishing = false;
                 setUIState('default');
 
                 // Close
@@ -1031,6 +1054,8 @@ function Compose({
                   instance,
                 });
               } catch (e) {
+                states.composerState.publishing = false;
+                states.composerState.publishingError = true;
                 console.error(e);
                 alert(e?.reason || e);
                 setUIState('error');
diff --git a/src/components/modal.css b/src/components/modal.css
index 713ed308..62d6c8b6 100644
--- a/src/components/modal.css
+++ b/src/components/modal.css
@@ -10,17 +10,56 @@
   align-items: center;
   background-color: var(--backdrop-color);
   animation: appear 0.5s var(--timing-function) both;
+  transition: all 0.5s var(--timing-function);
 
   &.solid {
     background-color: var(--backdrop-solid-color);
   }
 
+  --compose-button-dimension: 56px;
+  --compose-button-dimension-half: calc(var(--compose-button-dimension) / 2);
+  --compose-button-dimension-margin: 16px;
+
+  &.min {
+    /* Minimized */
+    pointer-events: none;
+    user-select: none;
+    overflow: hidden;
+    transform: scale(0);
+    --right: max(
+      var(--compose-button-dimension-margin),
+      env(safe-area-inset-right)
+    );
+    --bottom: max(
+      var(--compose-button-dimension-margin),
+      env(safe-area-inset-bottom)
+    );
+    --origin-right: calc(
+      100% - var(--compose-button-dimension-half) - var(--right)
+    );
+    --origin-bottom: calc(
+      100% - var(--compose-button-dimension-half) - var(--bottom)
+    );
+    transform-origin: var(--origin-right) var(--origin-bottom);
+  }
+
   .sheet {
     transition: transform 0.3s var(--timing-function);
-    transform-origin: center bottom;
+    transform-origin: 80% 80%;
   }
 
   &:has(~ div) .sheet {
     transform: scale(0.975);
   }
 }
+
+@media (max-width: calc(40em - 1px)) {
+  #app[data-shortcuts-view-mode='tab-menu-bar'] ~ #modal-container > div.min {
+    border: 2px solid red;
+
+    --bottom: calc(
+      var(--compose-button-dimension-margin) + env(safe-area-inset-bottom) +
+        52px
+    );
+  }
+}
diff --git a/src/components/modal.jsx b/src/components/modal.jsx
index 0587b3ce..daa5a98a 100644
--- a/src/components/modal.jsx
+++ b/src/components/modal.jsx
@@ -8,7 +8,7 @@ import useCloseWatcher from '../utils/useCloseWatcher';
 
 const $modalContainer = document.getElementById('modal-container');
 
-function Modal({ children, onClose, onClick, class: className }) {
+function Modal({ children, onClose, onClick, class: className, minimized }) {
   if (!children) return null;
 
   const modalRef = useRef();
@@ -43,21 +43,30 @@ function Modal({ children, onClose, onClick, class: className }) {
 
   useEffect(() => {
     const $deckContainers = document.querySelectorAll('.deck-container');
-    if (children) {
-      $deckContainers.forEach(($deckContainer) => {
-        $deckContainer.setAttribute('inert', '');
-      });
+    if (minimized) {
+      // Similar to focusDeck in focus-deck.jsx
+      // Focus last deck
+      const page = $deckContainers[$deckContainers.length - 1]; // last one
+      if (page && page.tabIndex === -1) {
+        page.focus();
+      }
     } else {
-      $deckContainers.forEach(($deckContainer) => {
-        $deckContainer.removeAttribute('inert');
-      });
+      if (children) {
+        $deckContainers.forEach(($deckContainer) => {
+          $deckContainer.setAttribute('inert', '');
+        });
+      } else {
+        $deckContainers.forEach(($deckContainer) => {
+          $deckContainer.removeAttribute('inert');
+        });
+      }
     }
     return () => {
       $deckContainers.forEach(($deckContainer) => {
         $deckContainer.removeAttribute('inert');
       });
     };
-  }, [children]);
+  }, [children, minimized]);
 
   const Modal = (
     <div
@@ -72,7 +81,8 @@ function Modal({ children, onClose, onClick, class: className }) {
           onClose?.(e);
         }
       }}
-      tabIndex="-1"
+      tabIndex={minimized ? 0 : '-1'}
+      inert={minimized}
       onFocus={(e) => {
         try {
           if (e.target === e.currentTarget) {
diff --git a/src/components/modals.jsx b/src/components/modals.jsx
index a0ebd4c9..4dcda8ee 100644
--- a/src/components/modals.jsx
+++ b/src/components/modals.jsx
@@ -39,7 +39,10 @@ export default function Modals() {
   return (
     <>
       {!!snapStates.showCompose && (
-        <Modal class="solid">
+        <Modal
+          class={`solid ${snapStates.composerState.minimized ? 'min' : ''}`}
+          minimized={!!snapStates.composerState.minimized}
+        >
           <IntlSegmenterSuspense>
             <Compose
               replyToStatus={
diff --git a/src/components/status.jsx b/src/components/status.jsx
index aa8c7fa9..c59bb263 100644
--- a/src/components/status.jsx
+++ b/src/components/status.jsx
@@ -51,6 +51,7 @@ import openCompose from '../utils/open-compose';
 import pmem from '../utils/pmem';
 import safeBoundingBoxPadding from '../utils/safe-bounding-box-padding';
 import shortenNumber from '../utils/shorten-number';
+import showCompose from '../utils/show-compose';
 import showToast from '../utils/show-toast';
 import { speak, supportsTTS } from '../utils/speech';
 import states, { getStatus, saveStatus, statusKey } from '../utils/states';
@@ -524,9 +525,9 @@ function Status({
       });
       if (newWin) return;
     }
-    states.showCompose = {
+    showCompose({
       replyToStatus: status,
-    };
+    });
   };
 
   // Check if media has no descriptions
@@ -771,11 +772,11 @@ function Status({
               menuExtras={
                 <MenuItem
                   onClick={() => {
-                    states.showCompose = {
+                    showCompose({
                       draftStatus: {
                         status: `\n${url}`,
                       },
-                    };
+                    });
                   }}
                 >
                   <Icon icon="quote" />
@@ -1092,9 +1093,9 @@ function Status({
           {supports('@mastodon/post-edit') && (
             <MenuItem
               onClick={() => {
-                states.showCompose = {
+                showCompose({
                   editStatus: status,
-                };
+                });
               }}
             >
               <Icon icon="pencil" />
@@ -2125,11 +2126,11 @@ function Status({
                   menuExtras={
                     <MenuItem
                       onClick={() => {
-                        states.showCompose = {
+                        showCompose({
                           draftStatus: {
                             status: `\n${url}`,
                           },
-                        };
+                        });
                       }}
                     >
                       <Icon icon="quote" />
diff --git a/src/index.css b/src/index.css
index 10c4380c..a963ee03 100644
--- a/src/index.css
+++ b/src/index.css
@@ -388,6 +388,27 @@ select.plain {
   background-color: transparent;
 }
 
+.button-group {
+  display: flex;
+
+  button,
+  .button {
+    margin-inline: calc(-1 * var(--hairline-width));
+
+    &:first-child:not(:only-child) {
+      border-top-right-radius: 0;
+      border-bottom-right-radius: 0;
+    }
+    &:not(:first-child, :last-child, :only-child) {
+      border-radius: 0;
+    }
+    &:last-child:not(:only-child) {
+      border-top-left-radius: 0;
+      border-bottom-left-radius: 0;
+    }
+  }
+}
+
 pre {
   tab-size: 2;
 }
@@ -547,3 +568,9 @@ kbd {
 .shazam-container-horizontal[hidden] {
   grid-template-columns: 0fr;
 }
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
diff --git a/src/pages/status.css b/src/pages/status.css
index b971c76e..35b26764 100644
--- a/src/pages/status.css
+++ b/src/pages/status.css
@@ -23,12 +23,6 @@
   }
 }
 
-@keyframes spin {
-  to {
-    transform: rotate(360deg);
-  }
-}
-
 .hero-heading {
   font-size: var(--text-size);
   display: inline-block;
diff --git a/src/utils/show-compose.js b/src/utils/show-compose.js
new file mode 100644
index 00000000..c29669f9
--- /dev/null
+++ b/src/utils/show-compose.js
@@ -0,0 +1,27 @@
+import openOSK from './open-osk';
+import showToast from './show-toast';
+import states from './states';
+
+const TOAST_DURATION = 5_000; // 5 seconds
+
+export default function showCompose(opts) {
+  if (!opts) opts = true;
+
+  if (states.showCompose) {
+    if (states.composerState.minimized) {
+      showToast({
+        duration: TOAST_DURATION,
+        text: `A draft post is currently minimized. Post or discard it before creating a new one.`,
+      });
+    } else {
+      showToast({
+        duration: TOAST_DURATION,
+        text: `A post is currently open. Post or discard it before creating a new one.`,
+      });
+    }
+    return;
+  }
+
+  openOSK();
+  states.showCompose = opts;
+}
diff --git a/src/utils/states.js b/src/utils/states.js
index d346bb77..d7381a0f 100644
--- a/src/utils/states.js
+++ b/src/utils/states.js
@@ -40,6 +40,7 @@ const states = proxy({
   statusReply: {},
   accounts: {},
   routeNotification: null,
+  composerState: {},
   // Modals
   showCompose: false,
   showSettings: false,