diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md
index 76106803e8..5e821e9b9b 100644
--- a/docs/docs/developer/setup.md
+++ b/docs/docs/developer/setup.md
@@ -75,11 +75,12 @@ npm run dev
 To see local changes to `@immich/ui` in Immich, do the following:
 
 1. Install `@immich/ui` as a sibling to `immich/`, for example `/home/user/immich` and `/home/user/ui`
-1. Build the `@immich/ui` project via `npm run build`
-1. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yaml` file (`../../ui:/usr/ui`)
-1. Uncomment the corresponding alias in the `web/vite.config.js` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui')`)
-1. Start up the stack via `make dev`
-1. After making changes in `@immich/ui`, rebuild it (`npm run build`)
+2. Build the `@immich/ui` project via `npm run build`
+3. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yaml` file (`../../ui:/usr/ui`)
+4. Uncomment the corresponding alias in the `web/vite.config.js` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui')`)
+5. Uncomment the import statement in `web/src/app.css` file `@import '/usr/ui/dist/theme/default.css';` and comment out `@import '@immich/ui/theme/default.css';`
+6. Start up the stack via `make dev`
+7. After making changes in `@immich/ui`, rebuild it (`npm run build`)
 
 ### Mobile app
 
diff --git a/web/package-lock.json b/web/package-lock.json
index cc29dd6856..bde201c176 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -11,7 +11,7 @@
       "dependencies": {
         "@formatjs/icu-messageformat-parser": "^2.9.8",
         "@immich/sdk": "file:../open-api/typescript-sdk",
-        "@immich/ui": "^0.22.0",
+        "@immich/ui": "^0.22.1",
         "@mapbox/mapbox-gl-rtl-text": "0.2.3",
         "@mdi/js": "^7.4.47",
         "@photo-sphere-viewer/core": "^5.11.5",
@@ -1341,15 +1341,15 @@
       "link": true
     },
     "node_modules/@immich/ui": {
-      "version": "0.22.0",
-      "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.22.0.tgz",
-      "integrity": "sha512-bBx9hPy7/VECZPcEiBGty6Lu9jmD4vJf6VL2ud+LHLQcpZebv4FVFZzzVFf7ctBwooYJWTEfWZTPNgAo0rbQtQ==",
+      "version": "0.22.1",
+      "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.22.1.tgz",
+      "integrity": "sha512-/QdqctBit+eX8QZgTL4PlgS7l6/NCGXeDjR6kQNLOVBPhbjkmtwqsvZ+RymYClcHAEhutXOKRhnlQU9mNLC/SA==",
       "license": "GNU Affero General Public License version 3",
       "dependencies": {
         "@mdi/js": "^7.4.47",
         "bits-ui": "^1.0.0-next.46",
         "tailwind-merge": "^2.5.4",
-        "tailwind-variants": "^0.3.0"
+        "tailwind-variants": "^1.0.0"
       },
       "peerDependencies": {
         "svelte": "^5.0.0"
@@ -9479,12 +9479,12 @@
       }
     },
     "node_modules/tailwind-variants": {
-      "version": "0.3.1",
-      "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.1.tgz",
-      "integrity": "sha512-krn67M3FpPwElg4FsZrOQd0U26o7UDH/QOkK8RNaiCCrr052f6YJPBUfNKnPo/s/xRzNPtv1Mldlxsg8Tb46BQ==",
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-1.0.0.tgz",
+      "integrity": "sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA==",
       "license": "MIT",
       "dependencies": {
-        "tailwind-merge": "2.5.4"
+        "tailwind-merge": "3.0.2"
       },
       "engines": {
         "node": ">=16.x",
@@ -9495,9 +9495,9 @@
       }
     },
     "node_modules/tailwind-variants/node_modules/tailwind-merge": {
-      "version": "2.5.4",
-      "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz",
-      "integrity": "sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==",
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.2.tgz",
+      "integrity": "sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==",
       "license": "MIT",
       "funding": {
         "type": "github",
diff --git a/web/package.json b/web/package.json
index d352afe45b..99df56b7f0 100644
--- a/web/package.json
+++ b/web/package.json
@@ -28,7 +28,7 @@
   "dependencies": {
     "@formatjs/icu-messageformat-parser": "^2.9.8",
     "@immich/sdk": "file:../open-api/typescript-sdk",
-    "@immich/ui": "^0.22.0",
+    "@immich/ui": "^0.22.1",
     "@mapbox/mapbox-gl-rtl-text": "0.2.3",
     "@mdi/js": "^7.4.47",
     "@photo-sphere-viewer/core": "^5.11.5",
diff --git a/web/src/app.css b/web/src/app.css
index 6160af1b8e..96639845b5 100644
--- a/web/src/app.css
+++ b/web/src/app.css
@@ -1,5 +1,6 @@
 @import 'tailwindcss';
 @import '@immich/ui/theme/default.css';
+/* @import '/usr/ui/dist/theme/default.css'; */
 
 @config '../tailwind.config.js';
 
diff --git a/web/src/lib/components/admin-page/server-stats/stats-card.svelte b/web/src/lib/components/admin-page/server-stats/stats-card.svelte
index aa18c6f3ae..6250a8ef7f 100644
--- a/web/src/lib/components/admin-page/server-stats/stats-card.svelte
+++ b/web/src/lib/components/admin-page/server-stats/stats-card.svelte
@@ -30,7 +30,7 @@
   <div class="relative mx-auto font-mono text-2xl font-semibold">
     <span class="text-gray-400 dark:text-gray-600">{zeros()}</span><span>{value}</span>
     {#if unit}
-      <Code color="muted" class="absolute -top-5 end-2 font-light">{unit}</Code>
+      <Code color="muted" class="absolute -top-5 end-1 font-light">{unit}</Code>
     {/if}
   </div>
 </div>
diff --git a/web/src/lib/components/admin-page/settings/template-settings/template-settings.svelte b/web/src/lib/components/admin-page/settings/template-settings/template-settings.svelte
index a294eb1768..8e85664750 100644
--- a/web/src/lib/components/admin-page/settings/template-settings/template-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/template-settings/template-settings.svelte
@@ -1,13 +1,12 @@
 <script lang="ts">
   import Icon from '$lib/components/elements/icon.svelte';
   import FormatMessage from '$lib/components/i18n/format-message.svelte';
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
   import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
   import SettingTextarea from '$lib/components/shared-components/settings/setting-textarea.svelte';
   import { handleError } from '$lib/utils/handle-error';
   import { type SystemConfigDto, type SystemConfigTemplateEmailsDto, getNotificationTemplateAdmin } from '@immich/sdk';
-  import { Button } from '@immich/ui';
+  import { Button, Modal, ModalBody } from '@immich/ui';
   import { mdiEyeOutline } from '@mdi/js';
   import { t } from 'svelte-i18n';
   import { fade } from 'svelte/transition';
@@ -112,15 +111,17 @@
       </div>
 
       {#if htmlPreview}
-        <FullScreenModal title={$t('admin.template_email_preview')} onClose={closePreviewModal} width="wide">
-          <div style="position:relative; width:100%; height:90vh; overflow: hidden">
-            <iframe
-              title={$t('admin.template_email_preview')}
-              srcdoc={htmlPreview}
-              style="width: 100%; height: 100%; border: none; overflow:hidden; position: absolute; top: 0; left: 0;"
-            ></iframe>
-          </div>
-        </FullScreenModal>
+        <Modal title={$t('admin.template_email_preview')} onClose={closePreviewModal} size="medium">
+          <ModalBody>
+            <div style="position:relative; width:100%; height:90vh; overflow: hidden">
+              <iframe
+                title={$t('admin.template_email_preview')}
+                srcdoc={htmlPreview}
+                style="width: 100%; height: 100%; border: none; overflow:hidden; position: absolute; top: 0; left: 0;"
+              ></iframe>
+            </div>
+          </ModalBody>
+        </Modal>
       {/if}
     </form>
   </div>
diff --git a/web/src/lib/components/album-page/album-map.svelte b/web/src/lib/components/album-page/album-map.svelte
index 497422daa2..fd96cf8b64 100644
--- a/web/src/lib/components/album-page/album-map.svelte
+++ b/web/src/lib/components/album-page/album-map.svelte
@@ -1,7 +1,6 @@
 <script lang="ts">
   import { clickOutside } from '$lib/actions/click-outside';
   import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import type Map from '$lib/components/shared-components/map/map.svelte';
   import Portal from '$lib/components/shared-components/portal/portal.svelte';
   import { timeToLoadTheMap } from '$lib/constants';
@@ -11,7 +10,7 @@
   import { delay } from '$lib/utils/asset-utils';
   import { navigate } from '$lib/utils/navigation';
   import { getAlbumInfo, type AlbumResponseDto, type MapMarkerResponseDto } from '@immich/sdk';
-  import { LoadingSpinner } from '@immich/ui';
+  import { LoadingSpinner, Modal, ModalBody } from '@immich/ui';
   import { mdiMapOutline } from '@mdi/js';
   import { onDestroy, onMount } from 'svelte';
   import { t } from 'svelte-i18n';
@@ -112,31 +111,33 @@
 
 {#if albumMapViewManager.isInMapView}
   <div use:clickOutside={{ onOutclick: closeMap }}>
-    <FullScreenModal title={$t('map')} width="wide" onClose={closeMap}>
-      <div class="flex flex-col w-full h-full gap-2">
-        <div class="h-[500px] min-h-[300px] w-full">
-          {#await import('../shared-components/map/map.svelte')}
-            {#await delay(timeToLoadTheMap) then}
-              <!-- show the loading spinner only if loading the map takes too much time -->
-              <div class="flex items-center justify-center h-full w-full">
-                <LoadingSpinner />
-              </div>
+    <Modal title={$t('map')} size="medium" onClose={closeMap}>
+      <ModalBody>
+        <div class="flex flex-col w-full h-full gap-2 border border-gray-300 dark:border-light rounded-2xl">
+          <div class="h-[500px] min-h-[300px] w-full">
+            {#await import('../shared-components/map/map.svelte')}
+              {#await delay(timeToLoadTheMap) then}
+                <!-- show the loading spinner only if loading the map takes too much time -->
+                <div class="flex items-center justify-center h-full w-full">
+                  <LoadingSpinner />
+                </div>
+              {/await}
+            {:then { default: Map }}
+              <Map
+                bind:this={mapElement}
+                center={undefined}
+                {zoom}
+                clickable={false}
+                bind:mapMarkers
+                onSelect={onViewAssets}
+                showSettings={false}
+                rounded
+              />
             {/await}
-          {:then { default: Map }}
-            <Map
-              bind:this={mapElement}
-              center={undefined}
-              {zoom}
-              clickable={false}
-              bind:mapMarkers
-              onSelect={onViewAssets}
-              showSettings={false}
-              rounded
-            />
-          {/await}
+          </div>
         </div>
-      </div>
-    </FullScreenModal>
+      </ModalBody>
+    </Modal>
   </div>
 
   <Portal target="body">
diff --git a/web/src/lib/components/album-page/album-options.svelte b/web/src/lib/components/album-page/album-options.svelte
index d63de9bdee..9fbcf4e2ad 100644
--- a/web/src/lib/components/album-page/album-options.svelte
+++ b/web/src/lib/components/album-page/album-options.svelte
@@ -2,7 +2,6 @@
   import Icon from '$lib/components/elements/icon.svelte';
   import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
   import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
   import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
   import ConfirmModal from '$lib/modals/ConfirmModal.svelte';
@@ -16,6 +15,7 @@
     type AlbumResponseDto,
     type UserResponseDto,
   } from '@immich/sdk';
+  import { Modal, ModalBody } from '@immich/ui';
   import { mdiArrowDownThin, mdiArrowUpThin, mdiDotsVertical, mdiPlus } from '@mdi/js';
   import { findKey } from 'lodash-es';
   import { t } from 'svelte-i18n';
@@ -115,79 +115,81 @@
 </script>
 
 {#if !selectedRemoveUser}
-  <FullScreenModal title={$t('options')} {onClose}>
-    <div class="items-center justify-center">
-      <div class="py-2">
-        <h2 class="text-gray text-sm mb-2">{$t('settings').toUpperCase()}</h2>
-        <div class="grid p-2 gap-y-2">
-          {#if order}
-            <SettingDropdown
-              title={$t('display_order')}
-              options={Object.values(options)}
-              selectedOption={options[order]}
-              onToggle={handleToggle}
+  <Modal title={$t('options')} {onClose} size="small">
+    <ModalBody>
+      <div class="items-center justify-center">
+        <div class="py-2">
+          <h2 class="text-gray text-sm mb-2">{$t('settings').toUpperCase()}</h2>
+          <div class="grid p-2 gap-y-2">
+            {#if order}
+              <SettingDropdown
+                title={$t('display_order')}
+                options={Object.values(options)}
+                selectedOption={options[order]}
+                onToggle={handleToggle}
+              />
+            {/if}
+            <SettingSwitch
+              title={$t('comments_and_likes')}
+              subtitle={$t('let_others_respond')}
+              checked={album.isActivityEnabled}
+              onToggle={onToggleEnabledActivity}
             />
-          {/if}
-          <SettingSwitch
-            title={$t('comments_and_likes')}
-            subtitle={$t('let_others_respond')}
-            checked={album.isActivityEnabled}
-            onToggle={onToggleEnabledActivity}
-          />
-        </div>
-      </div>
-      <div class="py-2">
-        <div class="text-gray text-sm mb-3">{$t('people').toUpperCase()}</div>
-        <div class="p-2">
-          <button type="button" class="flex items-center gap-2" onclick={onShowSelectSharedUser}>
-            <div class="rounded-full w-10 h-10 border border-gray-500 flex items-center justify-center">
-              <div><Icon path={mdiPlus} size="25" /></div>
-            </div>
-            <div>{$t('invite_people')}</div>
-          </button>
-
-          <div class="flex items-center gap-2 py-2 mt-2">
-            <div>
-              <UserAvatar {user} size="md" />
-            </div>
-            <div class="w-full">{user.name}</div>
-            <div>{$t('owner')}</div>
           </div>
+        </div>
+        <div class="py-2">
+          <div class="text-gray text-sm mb-3">{$t('people').toUpperCase()}</div>
+          <div class="p-2">
+            <button type="button" class="flex items-center gap-2" onclick={onShowSelectSharedUser}>
+              <div class="rounded-full w-10 h-10 border border-gray-500 flex items-center justify-center">
+                <div><Icon path={mdiPlus} size="25" /></div>
+              </div>
+              <div>{$t('invite_people')}</div>
+            </button>
 
-          {#each album.albumUsers as { user, role } (user.id)}
-            <div class="flex items-center gap-2 py-2">
+            <div class="flex items-center gap-2 py-2 mt-2">
               <div>
                 <UserAvatar {user} size="md" />
               </div>
               <div class="w-full">{user.name}</div>
-              {#if role === AlbumUserRole.Viewer}
-                {$t('role_viewer')}
-              {:else}
-                {$t('role_editor')}
-              {/if}
-              {#if user.id !== album.ownerId}
-                <ButtonContextMenu icon={mdiDotsVertical} size="20" title={$t('options')}>
-                  {#if role === AlbumUserRole.Viewer}
-                    <MenuOption
-                      onClick={() => handleUpdateSharedUserRole(user, AlbumUserRole.Editor)}
-                      text={$t('allow_edits')}
-                    />
-                  {:else}
-                    <MenuOption
-                      onClick={() => handleUpdateSharedUserRole(user, AlbumUserRole.Viewer)}
-                      text={$t('disallow_edits')}
-                    />
-                  {/if}
-                  <!-- Allow deletion for non-owners -->
-                  <MenuOption onClick={() => handleMenuRemove(user)} text={$t('remove')} />
-                </ButtonContextMenu>
-              {/if}
+              <div>{$t('owner')}</div>
             </div>
-          {/each}
+
+            {#each album.albumUsers as { user, role } (user.id)}
+              <div class="flex items-center gap-2 py-2">
+                <div>
+                  <UserAvatar {user} size="md" />
+                </div>
+                <div class="w-full">{user.name}</div>
+                {#if role === AlbumUserRole.Viewer}
+                  {$t('role_viewer')}
+                {:else}
+                  {$t('role_editor')}
+                {/if}
+                {#if user.id !== album.ownerId}
+                  <ButtonContextMenu icon={mdiDotsVertical} size="20" title={$t('options')}>
+                    {#if role === AlbumUserRole.Viewer}
+                      <MenuOption
+                        onClick={() => handleUpdateSharedUserRole(user, AlbumUserRole.Editor)}
+                        text={$t('allow_edits')}
+                      />
+                    {:else}
+                      <MenuOption
+                        onClick={() => handleUpdateSharedUserRole(user, AlbumUserRole.Viewer)}
+                        text={$t('disallow_edits')}
+                      />
+                    {/if}
+                    <!-- Allow deletion for non-owners -->
+                    <MenuOption onClick={() => handleMenuRemove(user)} text={$t('remove')} />
+                  </ButtonContextMenu>
+                {/if}
+              </div>
+            {/each}
+          </div>
         </div>
       </div>
-    </div>
-  </FullScreenModal>
+    </ModalBody>
+  </Modal>
 {/if}
 
 {#if selectedRemoveUser}
diff --git a/web/src/lib/components/album-page/album-title.svelte b/web/src/lib/components/album-page/album-title.svelte
index f5b1a4fa1e..f2559c86e5 100644
--- a/web/src/lib/components/album-page/album-title.svelte
+++ b/web/src/lib/components/album-page/album-title.svelte
@@ -40,7 +40,7 @@
   onblur={handleUpdateName}
   class="w-[99%] mb-2 border-b-2 border-transparent text-2xl md:text-4xl lg:text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary {isOwned
     ? 'hover:border-gray-400'
-    : 'hover:border-transparent'} focus:border-b-2 focus:border-immich-primary focus:outline-none bg-light dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray"
+    : 'hover:border-transparent'} focus:border-b-2 focus:border-immich-primary focus:outline-none bg-light dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray placeholder:text-primary/90"
   type="text"
   bind:value={newAlbumName}
   disabled={!isOwned}
diff --git a/web/src/lib/components/asset-viewer/detail-panel-tags.svelte b/web/src/lib/components/asset-viewer/detail-panel-tags.svelte
index eee7a7c0b6..5da6324528 100644
--- a/web/src/lib/components/asset-viewer/detail-panel-tags.svelte
+++ b/web/src/lib/components/asset-viewer/detail-panel-tags.svelte
@@ -50,7 +50,7 @@
       {#each tags as tag (tag.id)}
         <div class="flex group transition-all">
           <a
-            class="inline-block h-min whitespace-nowrap ps-3 pe-1 group-hover:ps-3 py-1 text-center align-baseline leading-none text-gray-100 dark:text-immich-dark-gray bg-immich-primary dark:bg-immich-dark-primary roudned-s-full hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
+            class="inline-block h-min whitespace-nowrap ps-3 pe-1 group-hover:ps-3 py-1 text-center align-baseline leading-none text-gray-100 dark:text-immich-dark-gray bg-immich-primary dark:bg-immich-dark-primary rounded-s-full hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
             href={encodeURI(`${AppRoute.TAGS}/?path=${tag.value}`)}
           >
             <p class="text-sm">
diff --git a/web/src/lib/components/forms/edit-album-form.svelte b/web/src/lib/components/forms/edit-album-form.svelte
index e269f0d8c0..32e1e422e7 100644
--- a/web/src/lib/components/forms/edit-album-form.svelte
+++ b/web/src/lib/components/forms/edit-album-form.svelte
@@ -1,9 +1,8 @@
 <script lang="ts">
   import AlbumCover from '$lib/components/album-page/album-cover.svelte';
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import { handleError } from '$lib/utils/handle-error';
   import { updateAlbumInfo, type AlbumResponseDto } from '@immich/sdk';
-  import { Button } from '@immich/ui';
+  import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
   import { mdiRenameOutline } from '@mdi/js';
   import { t } from 'svelte-i18n';
 
@@ -47,29 +46,33 @@
   };
 </script>
 
-<FullScreenModal icon={mdiRenameOutline} title={$t('edit_album')} width="wide" {onClose}>
-  <form {onsubmit} autocomplete="off" id="edit-album-form">
-    <div class="flex items-center">
-      <div class="hidden sm:flex">
-        <AlbumCover {album} class="h-[200px] w-[200px] m-4 shadow-lg" />
-      </div>
-
-      <div class="grow">
-        <div class="m-4 flex flex-col gap-2">
-          <label class="immich-form-label" for="name">{$t('name')}</label>
-          <input class="immich-form-input" id="name" type="text" bind:value={albumName} />
+<Modal icon={mdiRenameOutline} title={$t('edit_album')} size="medium" {onClose}>
+  <ModalBody>
+    <form {onsubmit} autocomplete="off" id="edit-album-form">
+      <div class="flex items-center">
+        <div class="hidden sm:flex">
+          <AlbumCover {album} class="h-[200px] w-[200px] m-4 shadow-lg" />
         </div>
 
-        <div class="m-4 flex flex-col gap-2">
-          <label class="immich-form-label" for="description">{$t('description')}</label>
-          <textarea class="immich-form-input" id="description" bind:value={description}></textarea>
+        <div class="grow">
+          <div class="m-4 flex flex-col gap-2">
+            <label class="immich-form-label" for="name">{$t('name')}</label>
+            <input class="immich-form-input" id="name" type="text" bind:value={albumName} />
+          </div>
+
+          <div class="m-4 flex flex-col gap-2">
+            <label class="immich-form-label" for="description">{$t('description')}</label>
+            <textarea class="immich-form-input" id="description" bind:value={description}></textarea>
+          </div>
         </div>
       </div>
+    </form>
+  </ModalBody>
+
+  <ModalFooter>
+    <div class="flex gap-2 w-full">
+      <Button shape="round" color="secondary" fullWidth onclick={() => onCancel?.()}>{$t('cancel')}</Button>
+      <Button shape="round" type="submit" fullWidth disabled={isSubmitting} form="edit-album-form">{$t('save')}</Button>
     </div>
-  </form>
-
-  {#snippet stickyBottom()}
-    <Button shape="round" color="secondary" fullWidth onclick={() => onCancel?.()}>{$t('cancel')}</Button>
-    <Button shape="round" type="submit" fullWidth disabled={isSubmitting} form="edit-album-form">{$t('save')}</Button>
-  {/snippet}
-</FullScreenModal>
+  </ModalFooter>
+</Modal>
diff --git a/web/src/lib/components/forms/library-exclusion-pattern-form.svelte b/web/src/lib/components/forms/library-exclusion-pattern-form.svelte
index d6ce4bcda0..e069e5c7a2 100644
--- a/web/src/lib/components/forms/library-exclusion-pattern-form.svelte
+++ b/web/src/lib/components/forms/library-exclusion-pattern-form.svelte
@@ -1,9 +1,8 @@
 <script lang="ts">
-  import { Button } from '@immich/ui';
+  import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
   import { mdiFolderRemove } from '@mdi/js';
   import { onMount } from 'svelte';
   import { t } from 'svelte-i18n';
-  import FullScreenModal from '../shared-components/full-screen-modal.svelte';
 
   interface Props {
     exclusionPattern: string;
@@ -42,37 +41,40 @@
   };
 </script>
 
-<FullScreenModal title={$t('add_exclusion_pattern')} icon={mdiFolderRemove} onClose={onCancel}>
-  <form {onsubmit} autocomplete="off" id="add-exclusion-pattern-form">
-    <p class="py-5 text-sm">
-      {$t('admin.exclusion_pattern_description')}
-      <br /><br />
-      {$t('admin.add_exclusion_pattern_description')}
-    </p>
-    <div class="my-4 flex flex-col gap-2">
-      <label class="immich-form-label" for="exclusionPattern">{$t('pattern')}</label>
-      <input
-        class="immich-form-input"
-        id="exclusionPattern"
-        name="exclusionPattern"
-        type="text"
-        bind:value={exclusionPattern}
-      />
-    </div>
-    <div class="mt-8 flex w-full gap-4">
-      {#if isDuplicate}
-        <p class="text-red-500 text-sm">{$t('errors.exclusion_pattern_already_exists')}</p>
+<Modal size="small" title={$t('add_exclusion_pattern')} icon={mdiFolderRemove} onClose={onCancel}>
+  <ModalBody>
+    <form {onsubmit} autocomplete="off" id="add-exclusion-pattern-form">
+      <p class="py-5 text-sm">
+        {$t('admin.exclusion_pattern_description')}
+        <br /><br />
+        {$t('admin.add_exclusion_pattern_description')}
+      </p>
+      <div class="my-4 flex flex-col gap-2">
+        <label class="immich-form-label" for="exclusionPattern">{$t('pattern')}</label>
+        <input
+          class="immich-form-input"
+          id="exclusionPattern"
+          name="exclusionPattern"
+          type="text"
+          bind:value={exclusionPattern}
+        />
+      </div>
+      <div class="mt-8 flex w-full gap-4">
+        {#if isDuplicate}
+          <p class="text-red-500 text-sm">{$t('errors.exclusion_pattern_already_exists')}</p>
+        {/if}
+      </div>
+    </form>
+  </ModalBody>
+  <ModalFooter>
+    <div class="flex gap-2 w-full">
+      <Button shape="round" color="secondary" fullWidth onclick={onCancel}>{$t('cancel')}</Button>
+      {#if isEditing}
+        <Button shape="round" color="danger" fullWidth onclick={onDelete}>{$t('delete')}</Button>
       {/if}
+      <Button shape="round" type="submit" disabled={!canSubmit} fullWidth form="add-exclusion-pattern-form"
+        >{submitText}</Button
+      >
     </div>
-  </form>
-
-  {#snippet stickyBottom()}
-    <Button shape="round" color="secondary" fullWidth onclick={onCancel}>{$t('cancel')}</Button>
-    {#if isEditing}
-      <Button shape="round" color="danger" fullWidth onclick={onDelete}>{$t('delete')}</Button>
-    {/if}
-    <Button shape="round" type="submit" disabled={!canSubmit} fullWidth form="add-exclusion-pattern-form"
-      >{submitText}</Button
-    >
-  {/snippet}
-</FullScreenModal>
+  </ModalFooter>
+</Modal>
diff --git a/web/src/lib/components/forms/library-import-path-form.svelte b/web/src/lib/components/forms/library-import-path-form.svelte
index 512b8919e3..ee2a273708 100644
--- a/web/src/lib/components/forms/library-import-path-form.svelte
+++ b/web/src/lib/components/forms/library-import-path-form.svelte
@@ -1,6 +1,5 @@
 <script lang="ts">
-  import { Button } from '@immich/ui';
-  import FullScreenModal from '../shared-components/full-screen-modal.svelte';
+  import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
   import { mdiFolderSync } from '@mdi/js';
   import { onMount } from 'svelte';
   import { t } from 'svelte-i18n';
@@ -46,29 +45,33 @@
   };
 </script>
 
-<FullScreenModal {title} icon={mdiFolderSync} onClose={onCancel}>
-  <form {onsubmit} autocomplete="off" id="library-import-path-form">
-    <p class="py-5 text-sm">{$t('admin.library_import_path_description')}</p>
+<Modal {title} icon={mdiFolderSync} onClose={onCancel} size="small">
+  <ModalBody>
+    <form {onsubmit} autocomplete="off" id="library-import-path-form">
+      <p class="py-5 text-sm">{$t('admin.library_import_path_description')}</p>
 
-    <div class="my-4 flex flex-col gap-2">
-      <label class="immich-form-label" for="path">{$t('path')}</label>
-      <input class="immich-form-input" id="path" name="path" type="text" bind:value={importPath} />
-    </div>
+      <div class="my-4 flex flex-col gap-2">
+        <label class="immich-form-label" for="path">{$t('path')}</label>
+        <input class="immich-form-input" id="path" name="path" type="text" bind:value={importPath} />
+      </div>
 
-    <div class="mt-8 flex w-full gap-4">
-      {#if isDuplicate}
-        <p class="text-red-500 text-sm">{$t('errors.import_path_already_exists')}</p>
+      <div class="mt-8 flex w-full gap-4">
+        {#if isDuplicate}
+          <p class="text-red-500 text-sm">{$t('errors.import_path_already_exists')}</p>
+        {/if}
+      </div>
+    </form>
+  </ModalBody>
+
+  <ModalFooter>
+    <div class="flex gap-2 w-full">
+      <Button shape="round" color="secondary" fullWidth onclick={onCancel}>{cancelText}</Button>
+      {#if isEditing}
+        <Button shape="round" color="danger" fullWidth onclick={onDelete}>{$t('delete')}</Button>
       {/if}
+      <Button shape="round" type="submit" disabled={!canSubmit} fullWidth form="library-import-path-form"
+        >{submitText}</Button
+      >
     </div>
-  </form>
-
-  {#snippet stickyBottom()}
-    <Button shape="round" color="secondary" fullWidth onclick={onCancel}>{cancelText}</Button>
-    {#if isEditing}
-      <Button shape="round" color="danger" fullWidth onclick={onDelete}>{$t('delete')}</Button>
-    {/if}
-    <Button shape="round" type="submit" disabled={!canSubmit} fullWidth form="library-import-path-form"
-      >{submitText}</Button
-    >
-  {/snippet}
-</FullScreenModal>
+  </ModalFooter>
+</Modal>
diff --git a/web/src/lib/components/forms/library-rename-form.svelte b/web/src/lib/components/forms/library-rename-form.svelte
index af60bdc2be..0c04c77da1 100644
--- a/web/src/lib/components/forms/library-rename-form.svelte
+++ b/web/src/lib/components/forms/library-rename-form.svelte
@@ -1,7 +1,6 @@
 <script lang="ts">
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import type { LibraryResponseDto } from '@immich/sdk';
-  import { Button, Field, Input } from '@immich/ui';
+  import { Button, Field, Input, Modal, ModalBody, ModalFooter } from '@immich/ui';
   import { mdiRenameOutline } from '@mdi/js';
   import { t } from 'svelte-i18n';
 
@@ -21,15 +20,19 @@
   };
 </script>
 
-<form {onsubmit} autocomplete="off">
-  <FullScreenModal icon={mdiRenameOutline} title={$t('rename')} onClose={onCancel}>
-    <Field label={$t('name')}>
-      <Input bind:value={newName} />
-    </Field>
+<Modal icon={mdiRenameOutline} title={$t('rename')} onClose={onCancel} size="small">
+  <ModalBody>
+    <form {onsubmit} autocomplete="off" id="rename-library-form">
+      <Field label={$t('name')}>
+        <Input bind:value={newName} />
+      </Field>
+    </form>
+  </ModalBody>
 
-    {#snippet stickyBottom()}
+  <ModalFooter>
+    <div class="flex gap-2 w-full">
       <Button shape="round" fullWidth color="secondary" onclick={onCancel}>{$t('cancel')}</Button>
-      <Button shape="round" fullWidth type="submit">{$t('save')}</Button>
-    {/snippet}
-  </FullScreenModal>
-</form>
+      <Button shape="round" fullWidth type="submit" form="rename-library-form">{$t('save')}</Button>
+    </div>
+  </ModalFooter>
+</Modal>
diff --git a/web/src/lib/components/forms/library-user-picker-form.svelte b/web/src/lib/components/forms/library-user-picker-form.svelte
index 673fe7d5e9..43b3eb69f1 100644
--- a/web/src/lib/components/forms/library-user-picker-form.svelte
+++ b/web/src/lib/components/forms/library-user-picker-form.svelte
@@ -2,11 +2,10 @@
   import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
   import { user } from '$lib/stores/user.store';
   import { searchUsersAdmin } from '@immich/sdk';
-  import { Button } from '@immich/ui';
+  import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
   import { mdiFolderSync } from '@mdi/js';
   import { onMount } from 'svelte';
   import { t } from 'svelte-i18n';
-  import FullScreenModal from '../shared-components/full-screen-modal.svelte';
 
   interface Props {
     onCancel: () => void;
@@ -30,15 +29,19 @@
   };
 </script>
 
-<FullScreenModal title={$t('select_library_owner')} icon={mdiFolderSync} onClose={onCancel}>
-  <form {onsubmit} autocomplete="off" id="select-library-owner-form">
-    <p class="p-5 text-sm">{$t('admin.note_cannot_be_changed_later')}</p>
+<Modal title={$t('select_library_owner')} icon={mdiFolderSync} onClose={onCancel} size="small">
+  <ModalBody>
+    <form {onsubmit} autocomplete="off" id="select-library-owner-form">
+      <p class="p-5 text-sm">{$t('admin.note_cannot_be_changed_later')}</p>
 
-    <SettingSelect bind:value={ownerId} options={userOptions} name="user" />
-  </form>
+      <SettingSelect bind:value={ownerId} options={userOptions} name="user" />
+    </form>
+  </ModalBody>
 
-  {#snippet stickyBottom()}
-    <Button shape="round" color="secondary" fullWidth onclick={onCancel}>{$t('cancel')}</Button>
-    <Button shape="round" type="submit" fullWidth form="select-library-owner-form">{$t('create')}</Button>
-  {/snippet}
-</FullScreenModal>
+  <ModalFooter>
+    <div class="flex gap-2 w-full">
+      <Button shape="round" color="secondary" fullWidth onclick={onCancel}>{$t('cancel')}</Button>
+      <Button shape="round" type="submit" fullWidth form="select-library-owner-form">{$t('create')}</Button>
+    </div>
+  </ModalFooter>
+</Modal>
diff --git a/web/src/lib/components/forms/tag-asset-form.svelte b/web/src/lib/components/forms/tag-asset-form.svelte
index c757ff5335..a6e0928ece 100644
--- a/web/src/lib/components/forms/tag-asset-form.svelte
+++ b/web/src/lib/components/forms/tag-asset-form.svelte
@@ -1,13 +1,12 @@
 <script lang="ts">
   import Icon from '$lib/components/elements/icon.svelte';
   import { getAllTags, upsertTags, type TagResponseDto } from '@immich/sdk';
-  import { Button } from '@immich/ui';
+  import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
   import { mdiClose, mdiTag } from '@mdi/js';
   import { onMount } from 'svelte';
   import { t } from 'svelte-i18n';
   import { SvelteSet } from 'svelte/reactivity';
   import Combobox, { type ComboBoxOption } from '../shared-components/combobox.svelte';
-  import FullScreenModal from '../shared-components/full-screen-modal.svelte';
 
   interface Props {
     onTag: (tagIds: string[]) => void;
@@ -52,48 +51,52 @@
   };
 </script>
 
-<FullScreenModal title={$t('tag_assets')} icon={mdiTag} onClose={onCancel}>
-  <form {onsubmit} autocomplete="off" id="create-tag-form">
-    <div class="my-4 flex flex-col gap-2">
-      <Combobox
-        onSelect={handleSelect}
-        label={$t('tag')}
-        {allowCreate}
-        defaultFirstOption
-        options={allTags.map((tag) => ({ id: tag.id, label: tag.value, value: tag.id }))}
-        placeholder={$t('search_tags')}
-      />
+<Modal size="small" title={$t('tag_assets')} icon={mdiTag} onClose={onCancel}>
+  <ModalBody>
+    <form {onsubmit} autocomplete="off" id="create-tag-form">
+      <div class="my-4 flex flex-col gap-2">
+        <Combobox
+          onSelect={handleSelect}
+          label={$t('tag')}
+          {allowCreate}
+          defaultFirstOption
+          options={allTags.map((tag) => ({ id: tag.id, label: tag.value, value: tag.id }))}
+          placeholder={$t('search_tags')}
+        />
+      </div>
+    </form>
+
+    <section class="flex flex-wrap pt-2 gap-1">
+      {#each selectedIds as tagId (tagId)}
+        {@const tag = tagMap[tagId]}
+        {#if tag}
+          <div class="flex group transition-all">
+            <span
+              class="inline-block h-min whitespace-nowrap ps-3 pe-1 group-hover:ps-3 py-1 text-center align-baseline leading-none text-gray-100 dark:text-immich-dark-gray bg-immich-primary dark:bg-immich-dark-primary roudned-s-full hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
+            >
+              <p class="text-sm">
+                {tag.value}
+              </p>
+            </span>
+
+            <button
+              type="button"
+              class="text-gray-100 dark:text-immich-dark-gray bg-immich-primary/95 dark:bg-immich-dark-primary/95 rounded-e-full place-items-center place-content-center pe-2 ps-1 py-1 hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
+              title="Remove tag"
+              onclick={() => handleRemove(tagId)}
+            >
+              <Icon path={mdiClose} />
+            </button>
+          </div>
+        {/if}
+      {/each}
+    </section>
+  </ModalBody>
+
+  <ModalFooter>
+    <div class="flex w-full gap-2">
+      <Button shape="round" fullWidth color="secondary" onclick={onCancel}>{$t('cancel')}</Button>
+      <Button type="submit" shape="round" fullWidth form="create-tag-form" {disabled}>{$t('tag_assets')}</Button>
     </div>
-  </form>
-
-  <section class="flex flex-wrap pt-2 gap-1">
-    {#each selectedIds as tagId (tagId)}
-      {@const tag = tagMap[tagId]}
-      {#if tag}
-        <div class="flex group transition-all">
-          <span
-            class="inline-block h-min whitespace-nowrap ps-3 pe-1 group-hover:ps-3 py-1 text-center align-baseline leading-none text-gray-100 dark:text-immich-dark-gray bg-immich-primary dark:bg-immich-dark-primary roudned-s-full hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
-          >
-            <p class="text-sm">
-              {tag.value}
-            </p>
-          </span>
-
-          <button
-            type="button"
-            class="text-gray-100 dark:text-immich-dark-gray bg-immich-primary/95 dark:bg-immich-dark-primary/95 rounded-e-full place-items-center place-content-center pe-2 ps-1 py-1 hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
-            title="Remove tag"
-            onclick={() => handleRemove(tagId)}
-          >
-            <Icon path={mdiClose} />
-          </button>
-        </div>
-      {/if}
-    {/each}
-  </section>
-
-  {#snippet stickyBottom()}
-    <Button shape="round" fullWidth color="secondary" onclick={onCancel}>{$t('cancel')}</Button>
-    <Button type="submit" shape="round" fullWidth form="create-tag-form" {disabled}>{$t('tag_assets')}</Button>
-  {/snippet}
-</FullScreenModal>
+  </ModalFooter>
+</Modal>
diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte
index cace59e9d6..2dd25491a8 100644
--- a/web/src/lib/components/memory-page/memory-viewer.svelte
+++ b/web/src/lib/components/memory-page/memory-viewer.svelte
@@ -314,8 +314,9 @@
 />
 
 {#if assetInteraction.selectionActive}
-  <div class="sticky top-0">
+  <div class="sticky top-0 z-1">
     <AssetSelectControlBar
+      forceDark
       assets={assetInteraction.selectedAssets}
       clearSelect={() => cancelMultiselect(assetInteraction)}
     >
@@ -605,6 +606,7 @@
     </section>
   {/if}
 </section>
+
 {#if current}
   <!-- GALLERY VIEWER -->
   <section class="bg-immich-dark-gray p-4">
diff --git a/web/src/lib/components/photos-page/asset-select-control-bar.svelte b/web/src/lib/components/photos-page/asset-select-control-bar.svelte
index 17b06c0e7e..b21693bc51 100644
--- a/web/src/lib/components/photos-page/asset-select-control-bar.svelte
+++ b/web/src/lib/components/photos-page/asset-select-control-bar.svelte
@@ -24,9 +24,10 @@
     clearSelect: () => void;
     ownerId?: string | undefined;
     children?: Snippet;
+    forceDark?: boolean;
   }
 
-  let { assets, clearSelect, ownerId = undefined, children }: Props = $props();
+  let { assets, clearSelect, ownerId = undefined, children, forceDark }: Props = $props();
 
   setContext({
     getAssets: () => assets,
@@ -35,9 +36,11 @@
   });
 </script>
 
-<ControlAppBar onClose={clearSelect} backIcon={mdiClose} tailwindClasses="bg-white shadow-md">
+<ControlAppBar onClose={clearSelect} {forceDark} backIcon={mdiClose} tailwindClasses="bg-white shadow-md">
   {#snippet leading()}
-    <div class="font-medium text-immich-primary dark:text-immich-dark-primary">
+    <div
+      class="font-medium {forceDark ? 'text-immich-dark-primary' : 'text-immich-primary dark:text-immich-dark-primary'}"
+    >
       <p class="block sm:hidden">{assets.length}</p>
       <p class="hidden sm:block">{$t('selected_count', { values: { count: assets.length } })}</p>
     </div>
diff --git a/web/src/lib/components/shared-components/album-selection/album-selection-modal.svelte b/web/src/lib/components/shared-components/album-selection/album-selection-modal.svelte
index 3bc0161236..ab763546af 100644
--- a/web/src/lib/components/shared-components/album-selection/album-selection-modal.svelte
+++ b/web/src/lib/components/shared-components/album-selection/album-selection-modal.svelte
@@ -5,9 +5,9 @@
     AlbumModalRowType,
     isSelectableRowType,
   } from '$lib/components/shared-components/album-selection/album-selection-utils';
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import { albumViewSettings } from '$lib/stores/preferences.store';
   import { type AlbumResponseDto, getAllAlbums } from '@immich/sdk';
+  import { Modal, ModalBody } from '@immich/ui';
   import { onMount } from 'svelte';
   import { t } from 'svelte-i18n';
   import AlbumListItem from '../../asset-viewer/album-list-item.svelte';
@@ -80,49 +80,51 @@
   const handleAlbumClick = (album: AlbumResponseDto) => () => onAlbumClick(album);
 </script>
 
-<FullScreenModal title={shared ? $t('add_to_shared_album') : $t('add_to_album')} {onClose}>
-  <div class="mb-2 flex max-h-[400px] flex-col">
-    {#if loading}
-      <!-- eslint-disable-next-line svelte/require-each-key -->
-      {#each { length: 3 } as _}
-        <div class="flex animate-pulse gap-4 px-6 py-2">
-          <div class="h-12 w-12 rounded-xl bg-slate-200"></div>
-          <div class="flex flex-col items-start justify-center gap-2">
-            <span class="h-4 w-36 animate-pulse bg-slate-200"></span>
-            <div class="flex animate-pulse gap-1">
-              <span class="h-3 w-8 bg-slate-200"></span>
-              <span class="h-3 w-20 bg-slate-200"></span>
+<Modal title={shared ? $t('add_to_shared_album') : $t('add_to_album')} {onClose} size="small">
+  <ModalBody>
+    <div class="mb-2 flex max-h-[400px] flex-col">
+      {#if loading}
+        <!-- eslint-disable-next-line svelte/require-each-key -->
+        {#each { length: 3 } as _}
+          <div class="flex animate-pulse gap-4 px-6 py-2">
+            <div class="h-12 w-12 rounded-xl bg-slate-200"></div>
+            <div class="flex flex-col items-start justify-center gap-2">
+              <span class="h-4 w-36 animate-pulse bg-slate-200"></span>
+              <div class="flex animate-pulse gap-1">
+                <span class="h-3 w-8 bg-slate-200"></span>
+                <span class="h-3 w-20 bg-slate-200"></span>
+              </div>
             </div>
           </div>
-        </div>
-      {/each}
-    {:else}
-      <input
-        class="border-b-4 border-immich-bg px-6 py-2 text-2xl focus:border-immich-primary dark:border-immich-dark-gray dark:focus:border-immich-dark-primary"
-        placeholder={$t('search')}
-        {onkeydown}
-        bind:value={search}
-        use:initInput
-      />
-      <div class="immich-scrollbar overflow-y-auto">
-        <!-- eslint-disable-next-line svelte/require-each-key -->
-        {#each albumModalRows as row}
-          {#if row.type === AlbumModalRowType.NEW_ALBUM}
-            <NewAlbumListItem selected={row.selected || false} {onNewAlbum} searchQuery={search} />
-          {:else if row.type === AlbumModalRowType.SECTION}
-            <p class="px-5 py-3 text-xs">{row.text}</p>
-          {:else if row.type === AlbumModalRowType.MESSAGE}
-            <p class="px-5 py-1 text-sm">{row.text}</p>
-          {:else if row.type === AlbumModalRowType.ALBUM_ITEM && row.album}
-            <AlbumListItem
-              album={row.album}
-              selected={row.selected || false}
-              searchQuery={search}
-              onAlbumClick={handleAlbumClick(row.album)}
-            />
-          {/if}
         {/each}
-      </div>
-    {/if}
-  </div>
-</FullScreenModal>
+      {:else}
+        <input
+          class="border-b-4 border-immich-bg px-6 py-2 text-2xl focus:border-immich-primary dark:border-immich-dark-gray dark:focus:border-immich-dark-primary"
+          placeholder={$t('search')}
+          {onkeydown}
+          bind:value={search}
+          use:initInput
+        />
+        <div class="immich-scrollbar overflow-y-auto">
+          <!-- eslint-disable-next-line svelte/require-each-key -->
+          {#each albumModalRows as row}
+            {#if row.type === AlbumModalRowType.NEW_ALBUM}
+              <NewAlbumListItem selected={row.selected || false} {onNewAlbum} searchQuery={search} />
+            {:else if row.type === AlbumModalRowType.SECTION}
+              <p class="px-5 py-3 text-xs">{row.text}</p>
+            {:else if row.type === AlbumModalRowType.MESSAGE}
+              <p class="px-5 py-1 text-sm">{row.text}</p>
+            {:else if row.type === AlbumModalRowType.ALBUM_ITEM && row.album}
+              <AlbumListItem
+                album={row.album}
+                selected={row.selected || false}
+                searchQuery={search}
+                onAlbumClick={handleAlbumClick(row.album)}
+              />
+            {/if}
+          {/each}
+        </div>
+      {/if}
+    </div>
+  </ModalBody>
+</Modal>
diff --git a/web/src/lib/components/shared-components/context-menu/button-context-menu.svelte b/web/src/lib/components/shared-components/context-menu/button-context-menu.svelte
index 593baafc7c..9e5d5d2391 100644
--- a/web/src/lib/components/shared-components/context-menu/button-context-menu.svelte
+++ b/web/src/lib/components/shared-components/context-menu/button-context-menu.svelte
@@ -38,6 +38,10 @@
     buttonClass?: string | undefined;
     hideContent?: boolean;
     children?: Snippet;
+    offset?: {
+      x: number;
+      y: number;
+    };
   } & HTMLAttributes<HTMLDivElement>;
 
   let {
@@ -51,6 +55,7 @@
     buttonClass = undefined,
     hideContent = false,
     children,
+    offset,
     ...restProps
   }: Props = $props();
 
@@ -186,13 +191,14 @@
       ]}
     >
       <ContextMenu
-        {...contextMenuPosition}
         {direction}
         ariaActiveDescendant={$selectedIdStore}
         ariaLabelledBy={buttonId}
         bind:menuElement={menuContainer}
         id={menuId}
         isVisible={isOpen}
+        x={contextMenuPosition.x - (offset?.x ?? 0)}
+        y={contextMenuPosition.y + (offset?.y ?? 0)}
       >
         {@render children?.()}
       </ContextMenu>
diff --git a/web/src/lib/components/shared-components/control-app-bar.svelte b/web/src/lib/components/shared-components/control-app-bar.svelte
index 0476ba6bfd..6830dfefa0 100644
--- a/web/src/lib/components/shared-components/control-app-bar.svelte
+++ b/web/src/lib/components/shared-components/control-app-bar.svelte
@@ -66,7 +66,7 @@
   let buttonClass = $derived(forceDark ? 'hover:text-immich-dark-gray' : undefined);
 </script>
 
-<div in:fly={{ y: 10, duration: 200 }} class="absolute top-0 w-full bg-transparent z-1">
+<div in:fly={{ y: 10, duration: 200 }} class="absolute top-0 w-full bg-transparent">
   <nav
     id="asset-selection-app-bar"
     class={[
@@ -77,7 +77,7 @@
       appBarBorder,
       'mx-2 my-2 place-items-center rounded-lg p-2 max-md:p-0 transition-all',
       tailwindClasses,
-      forceDark ? 'bg-immich-dark-gray text-white' : 'bg-subtle dark:bg-immich-dark-gray',
+      forceDark ? 'bg-immich-dark-gray! text-white' : 'bg-subtle dark:bg-immich-dark-gray',
     ]}
   >
     <div class="flex place-items-center sm:gap-6 justify-self-start dark:text-immich-dark-fg">
diff --git a/web/src/lib/components/shared-components/empty-placeholder.svelte b/web/src/lib/components/shared-components/empty-placeholder.svelte
index 63c30a0c4a..ae7f9aab6a 100644
--- a/web/src/lib/components/shared-components/empty-placeholder.svelte
+++ b/web/src/lib/components/shared-components/empty-placeholder.svelte
@@ -29,5 +29,5 @@
   {#if title}
     <h2 class="text-xl font-medium my-4">{title}</h2>
   {/if}
-  <p class="text-immich-text-gray-500 dark:text-immich-dark-fg font-light">{text}</p>
+  <p class="text-immich-text-gray-500 dark:text-immich-dark-fg font-light text-center">{text}</p>
 </svelte:element>
diff --git a/web/src/lib/components/shared-components/full-screen-modal.svelte b/web/src/lib/components/shared-components/full-screen-modal.svelte
deleted file mode 100644
index 39ba62ea80..0000000000
--- a/web/src/lib/components/shared-components/full-screen-modal.svelte
+++ /dev/null
@@ -1,107 +0,0 @@
-<script lang="ts">
-  import { clickOutside } from '$lib/actions/click-outside';
-  import { focusTrap } from '$lib/actions/focus-trap';
-  import ModalHeader from '$lib/components/shared-components/modal-header.svelte';
-  import { generateId } from '$lib/utils/generate-id';
-  import type { Snippet } from 'svelte';
-  import { fade } from 'svelte/transition';
-
-  interface Props {
-    onClose: () => void;
-    title: string;
-    /**
-     * If true, the logo will be displayed next to the modal title.
-     */
-    showLogo?: boolean;
-    /**
-     * Optional icon to display next to the modal title, if `showLogo` is false.
-     */
-    icon?: string | undefined;
-    /**
-     * Sets the width of the modal.
-     *
-     * - `wide`: 48rem
-     * - `narrow`: 28rem
-     * - `auto`: fits the width of the modal content, up to a maximum of 32rem
-     */
-    width?: 'extra-wide' | 'wide' | 'narrow' | 'auto';
-    stickyBottom?: Snippet;
-    children?: Snippet;
-  }
-
-  let {
-    onClose,
-    title,
-    showLogo = false,
-    icon = undefined,
-    width = 'narrow',
-    stickyBottom,
-    children,
-  }: Props = $props();
-
-  /**
-   * Unique identifier for the modal.
-   */
-  let id: string = generateId();
-
-  let titleId = $derived(`${id}-title`);
-  let isStickyBottom = $derived(!!stickyBottom);
-
-  let modalWidth = $state<string>();
-
-  $effect(() => {
-    switch (width) {
-      case 'extra-wide': {
-        modalWidth = 'w-4xl';
-        break;
-      }
-
-      case 'wide': {
-        modalWidth = 'w-3xl';
-        break;
-      }
-
-      case 'narrow': {
-        modalWidth = 'w-md';
-        break;
-      }
-
-      default: {
-        modalWidth = 'sm:max-w-4xl';
-      }
-    }
-  });
-</script>
-
-<section
-  role="presentation"
-  in:fade={{ duration: 100 }}
-  out:fade={{ duration: 100 }}
-  class="fixed start-0 top-0 flex h-dvh w-dvw place-content-center place-items-center bg-black/40"
-  onkeydown={(event) => {
-    event.stopPropagation();
-  }}
-  use:focusTrap
->
-  <div
-    class="flex flex-col max-h-[min(95dvh,60rem)] max-w-[95vw] {modalWidth} overflow-hidden rounded-3xl bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg pt-3 pb-4"
-    use:clickOutside={{ onOutclick: onClose, onEscape: onClose }}
-    tabindex="-1"
-    aria-modal="true"
-    aria-labelledby={titleId}
-  >
-    <div class="immich-scrollbar overflow-y-auto pt-1" class:pb-4={isStickyBottom}>
-      <ModalHeader id={titleId} {title} {showLogo} {icon} {onClose} />
-      <div class="px-5 pt-0 mb-5">
-        {@render children?.()}
-      </div>
-    </div>
-    {#if isStickyBottom}
-      <div
-        class="flex flex-col sm:flex-row justify-end w-full gap-2 sm:gap-4 sticky pt-4 px-5 bg-immich-bg dark:bg-immich-dark-gray border-t border-gray-200 dark:border-gray-500"
-      >
-        {@render stickyBottom?.()}
-      </div>
-    {/if}
-  </div>
-</section>
diff --git a/web/src/lib/components/shared-components/immich-logo.svelte b/web/src/lib/components/shared-components/immich-logo.svelte
index 4a96cfd632..a57f367964 100644
--- a/web/src/lib/components/shared-components/immich-logo.svelte
+++ b/web/src/lib/components/shared-components/immich-logo.svelte
@@ -14,7 +14,7 @@
 <svg {viewBox} class={cssClass}>
   <title>{$t('immich_logo')}</title>
   {#if !noText}
-    <g class="st0 dark:fill-[#accbfa]">
+    <g class="st0 dark:fill-[#accbfa] fill-[#4251b0]">
       <path
         d="M268.73,63.18c6.34,0,11.52,5.18,11.52,11.35c0,6.34-5.18,11.35-11.52,11.35s-11.69-5.01-11.69-11.35
 		C257.04,68.36,262.39,63.18,268.73,63.18z M258.88,122.45c0-3.01-0.67-7.85-0.67-10.68c0-6.01,4.67-10.68,10.52-10.68
@@ -94,9 +94,6 @@
 </svg>
 
 <style>
-  .st0 {
-    fill: #4251b0;
-  }
   .st1 {
     fill: #fa2921;
   }
diff --git a/web/src/lib/components/shared-components/navigation-bar/notification-panel.svelte b/web/src/lib/components/shared-components/navigation-bar/notification-panel.svelte
index 04ce019d9f..f76f187ad9 100644
--- a/web/src/lib/components/shared-components/navigation-bar/notification-panel.svelte
+++ b/web/src/lib/components/shared-components/navigation-bar/notification-panel.svelte
@@ -39,7 +39,7 @@
   in:fade={{ duration: 100 }}
   out:fade={{ duration: 100 }}
   id="notification-panel"
-  class="absolute right-[25px] top-[70px] z-1 w-[min(360px,100vw-50px)] rounded-3xl bg-gray-100 border border-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray text-light"
+  class="absolute right-[25px] top-[70px] z-1 w-[min(360px,100vw-50px)] rounded-3xl bg-gray-100 border border-gray-200 shadow-lg dark:border dark:border-light dark:bg-immich-dark-gray text-light px-2"
   use:focusTrap
 >
   <Stack class="max-h-[500px]">
diff --git a/web/src/lib/components/shared-components/profile-image-cropper.svelte b/web/src/lib/components/shared-components/profile-image-cropper.svelte
index c2a66d1565..6e55d94f7f 100644
--- a/web/src/lib/components/shared-components/profile-image-cropper.svelte
+++ b/web/src/lib/components/shared-components/profile-image-cropper.svelte
@@ -1,9 +1,8 @@
 <script lang="ts">
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import { user } from '$lib/stores/user.store';
   import { handleError } from '$lib/utils/handle-error';
   import { createProfileImage, type AssetResponseDto } from '@immich/sdk';
-  import { Button } from '@immich/ui';
+  import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
   import domtoimage from 'dom-to-image';
   import { onMount } from 'svelte';
   import { t } from 'svelte-i18n';
@@ -89,16 +88,17 @@
   };
 </script>
 
-<FullScreenModal title={$t('set_profile_picture')} width="auto" {onClose}>
-  <div class="flex place-items-center items-center justify-center">
-    <div
-      class="relative flex aspect-square w-[250px] overflow-hidden rounded-full border-4 border-immich-primary bg-immich-dark-primary dark:border-immich-dark-primary dark:bg-immich-primary"
-    >
-      <PhotoViewer bind:element={imgElement} {asset} />
+<Modal size="small" title={$t('set_profile_picture')} {onClose}>
+  <ModalBody>
+    <div class="flex place-items-center items-center justify-center">
+      <div
+        class="relative flex aspect-square w-[250px] overflow-hidden rounded-full border-4 border-immich-primary bg-immich-dark-primary dark:border-immich-dark-primary dark:bg-immich-primary"
+      >
+        <PhotoViewer bind:element={imgElement} {asset} />
+      </div>
     </div>
-  </div>
-
-  {#snippet stickyBottom()}
+  </ModalBody>
+  <ModalFooter>
     <Button fullWidth shape="round" onclick={handleSetProfilePicture}>{$t('set_as_profile_picture')}</Button>
-  {/snippet}
-</FullScreenModal>
+  </ModalFooter>
+</Modal>
diff --git a/web/src/lib/components/shared-components/scrubber/scrubber.svelte b/web/src/lib/components/shared-components/scrubber/scrubber.svelte
index e36e07c4b0..26a2e3f143 100644
--- a/web/src/lib/components/shared-components/scrubber/scrubber.svelte
+++ b/web/src/lib/components/shared-components/scrubber/scrubber.svelte
@@ -464,7 +464,7 @@
       class={[
         { 'border-b-2': isDragging },
         { 'rounded-bl-md': !isDragging },
-        'bg-light truncate opacity-85 pointer-events-none absolute end-0 min-w-20 max-w-64 w-fit rounded-ss-md  border-immich-primary py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:text-immich-dark-fg',
+        'bg-light truncate opacity-85 pointer-events-none absolute end-0 min-w-20 max-w-64 w-fit rounded-ss-md  border-immich-primary py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:text-immich-dark-fg z-1',
       ]}
       style:top="{hoverY + 2}px"
     >
@@ -506,7 +506,7 @@
       {#if assetStore.scrolling && scrollHoverLabel && !isHover}
         <p
           transition:fade={{ duration: 200 }}
-          class="truncate pointer-events-none absolute end-0 bottom-0 min-w-20 max-w-64 w-fit rounded-tl-md border-b-2 border-immich-primary bg-subtle/70 py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:text-immich-dark-fg"
+          class="truncate pointer-events-none absolute end-0 bottom-0 min-w-20 max-w-64 w-fit rounded-tl-md border-b-2 border-immich-primary bg-subtle/90 z-1 py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:text-immich-dark-fg"
         >
           {scrollHoverLabel}
         </p>
diff --git a/web/src/lib/components/shared-components/settings/setting-accordion.svelte b/web/src/lib/components/shared-components/settings/setting-accordion.svelte
index f48d14ea30..422f4bdded 100755
--- a/web/src/lib/components/shared-components/settings/setting-accordion.svelte
+++ b/web/src/lib/components/shared-components/settings/setting-accordion.svelte
@@ -64,8 +64,8 @@
 </script>
 
 <div
-  class="border rounded-2xl my-4 px-6 py-4 transition-all {isOpen
-    ? 'border-primary/40 dark:border-primary/50 shadow-md'
+  class="border-2 rounded-2xl border-primary/20 my-4 px-6 py-4 transition-all {isOpen
+    ? 'border-primary/60 shadow-md'
     : ''}"
   bind:this={accordionElement}
 >
diff --git a/web/src/lib/components/shared-components/version-announcement-box.svelte b/web/src/lib/components/shared-components/version-announcement-box.svelte
index 7586328490..236c3ed559 100644
--- a/web/src/lib/components/shared-components/version-announcement-box.svelte
+++ b/web/src/lib/components/shared-components/version-announcement-box.svelte
@@ -2,9 +2,8 @@
   import FormatMessage from '$lib/components/i18n/format-message.svelte';
   import { websocketStore } from '$lib/stores/websocket';
   import type { ServerVersionResponseDto } from '@immich/sdk';
-  import { Button } from '@immich/ui';
+  import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
   import { t } from 'svelte-i18n';
-  import FullScreenModal from './full-screen-modal.svelte';
 
   let showModal = $state(false);
 
@@ -39,33 +38,38 @@
 </script>
 
 {#if showModal}
-  <FullScreenModal title="🎉 {$t('new_version_available')}" onClose={() => (showModal = false)}>
-    <div>
-      <FormatMessage key="version_announcement_message">
-        {#snippet children({ tag, message })}
-          {#if tag === 'link'}
-            <span class="font-medium underline">
-              <a href="https://github.com/immich-app/immich/releases/latest" target="_blank" rel="noopener noreferrer">
-                {message}
-              </a>
-            </span>
-          {:else if tag === 'code'}
-            <code>{message}</code>
-          {/if}
-        {/snippet}
-      </FormatMessage>
-    </div>
+  <Modal size="small" title="🎉 {$t('new_version_available')}" onClose={() => (showModal = false)} icon={false}>
+    <ModalBody>
+      <div>
+        <FormatMessage key="version_announcement_message">
+          {#snippet children({ tag, message })}
+            {#if tag === 'link'}
+              <span class="font-medium underline">
+                <a
+                  href="https://github.com/immich-app/immich/releases/latest"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                >
+                  {message}
+                </a>
+              </span>
+            {:else if tag === 'code'}
+              <code>{message}</code>
+            {/if}
+          {/snippet}
+        </FormatMessage>
+      </div>
 
-    <div class="mt-4 font-medium">{$t('version_announcement_closing')}</div>
+      <div class="mt-4 font-medium">{$t('version_announcement_closing')}</div>
 
-    <div class="font-sm mt-8">
-      <code>{$t('server_version')}: {serverVersion}</code>
-      <br />
-      <code>{$t('latest_version')}: {releaseVersion}</code>
-    </div>
-
-    {#snippet stickyBottom()}
+      <div class="font-sm mt-8">
+        <code>{$t('server_version')}: {serverVersion}</code>
+        <br />
+        <code>{$t('latest_version')}: {releaseVersion}</code>
+      </div>
+    </ModalBody>
+    <ModalFooter>
       <Button fullWidth shape="round" onclick={onAcknowledge}>{$t('acknowledge')}</Button>
-    {/snippet}
-  </FullScreenModal>
+    </ModalFooter>
+  </Modal>
 {/if}
diff --git a/web/src/lib/components/slideshow-settings.svelte b/web/src/lib/components/slideshow-settings.svelte
index c30d2cfb09..0e0019e3eb 100644
--- a/web/src/lib/components/slideshow-settings.svelte
+++ b/web/src/lib/components/slideshow-settings.svelte
@@ -1,9 +1,8 @@
 <script lang="ts">
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
   import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
   import { SettingInputFieldType } from '$lib/constants';
-  import { Button } from '@immich/ui';
+  import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
   import {
     mdiArrowDownThin,
     mdiArrowUpThin,
@@ -65,37 +64,40 @@
   };
 </script>
 
-<FullScreenModal title={$t('slideshow_settings')} onClose={() => onClose()}>
-  <div class="flex flex-col gap-4 text-immich-primary dark:text-immich-dark-primary">
-    <SettingDropdown
-      title={$t('direction')}
-      options={Object.values(navigationOptions)}
-      selectedOption={navigationOptions[tempSlideshowNavigation]}
-      onToggle={(option) => {
-        tempSlideshowNavigation = handleToggle(option, navigationOptions) || tempSlideshowNavigation;
-      }}
-    />
-    <SettingDropdown
-      title={$t('look')}
-      options={Object.values(lookOptions)}
-      selectedOption={lookOptions[tempSlideshowLook]}
-      onToggle={(option) => {
-        tempSlideshowLook = handleToggle(option, lookOptions) || tempSlideshowLook;
-      }}
-    />
-    <SettingSwitch title={$t('show_progress_bar')} bind:checked={tempShowProgressBar} />
-    <SettingSwitch title={$t('show_slideshow_transition')} bind:checked={tempSlideshowTransition} />
-    <SettingInputField
-      inputType={SettingInputFieldType.NUMBER}
-      label={$t('duration')}
-      description={$t('admin.slideshow_duration_description')}
-      min={1}
-      bind:value={tempSlideshowDelay}
-    />
-  </div>
-
-  {#snippet stickyBottom()}
-    <Button color="secondary" shape="round" fullWidth onclick={() => onClose()}>{$t('cancel')}</Button>
-    <Button fullWidth color="primary" shape="round" onclick={applyChanges}>{$t('confirm')}</Button>
-  {/snippet}
-</FullScreenModal>
+<Modal size="small" title={$t('slideshow_settings')} onClose={() => onClose()}>
+  <ModalBody>
+    <div class="flex flex-col gap-4 text-immich-primary dark:text-immich-dark-primary">
+      <SettingDropdown
+        title={$t('direction')}
+        options={Object.values(navigationOptions)}
+        selectedOption={navigationOptions[tempSlideshowNavigation]}
+        onToggle={(option) => {
+          tempSlideshowNavigation = handleToggle(option, navigationOptions) || tempSlideshowNavigation;
+        }}
+      />
+      <SettingDropdown
+        title={$t('look')}
+        options={Object.values(lookOptions)}
+        selectedOption={lookOptions[tempSlideshowLook]}
+        onToggle={(option) => {
+          tempSlideshowLook = handleToggle(option, lookOptions) || tempSlideshowLook;
+        }}
+      />
+      <SettingSwitch title={$t('show_progress_bar')} bind:checked={tempShowProgressBar} />
+      <SettingSwitch title={$t('show_slideshow_transition')} bind:checked={tempSlideshowTransition} />
+      <SettingInputField
+        inputType={SettingInputFieldType.NUMBER}
+        label={$t('duration')}
+        description={$t('admin.slideshow_duration_description')}
+        min={1}
+        bind:value={tempSlideshowDelay}
+      />
+    </div>
+  </ModalBody>
+  <ModalFooter>
+    <div class="flex gap-2 w-full">
+      <Button color="secondary" shape="round" fullWidth onclick={() => onClose()}>{$t('cancel')}</Button>
+      <Button fullWidth color="primary" shape="round" onclick={applyChanges}>{$t('confirm')}</Button>
+    </div>
+  </ModalFooter>
+</Modal>
diff --git a/web/src/lib/components/user-settings-page/PinCodeInput.svelte b/web/src/lib/components/user-settings-page/PinCodeInput.svelte
index 01de7b3563..0b0e499445 100644
--- a/web/src/lib/components/user-settings-page/PinCodeInput.svelte
+++ b/web/src/lib/components/user-settings-page/PinCodeInput.svelte
@@ -126,7 +126,7 @@
         maxlength="1"
         bind:this={pinCodeInputElements[index]}
         id="pin-code-{index}"
-        class="h-12 w-10 rounded-xl border-2 border-suble dark:border-gray-700 bg-transparent text-center text-lg font-medium focus:border-immich-primary focus:ring-primary dark:focus:border-primary font-mono bg-white dark:bg-light"
+        class="h-12 w-10 rounded-xl border-2 border-suble dark:border-gray-700 text-center text-lg font-medium focus:border-immich-primary focus:ring-primary dark:focus:border-primary font-mono bg-white dark:bg-light"
         bind:value={pinValues[index]}
         onkeydown={handleKeydown}
         oninput={(event) => handleInput(event, index)}
diff --git a/web/src/lib/modals/AlbumShareModal.svelte b/web/src/lib/modals/AlbumShareModal.svelte
index 30777e7430..5bff5ab560 100644
--- a/web/src/lib/modals/AlbumShareModal.svelte
+++ b/web/src/lib/modals/AlbumShareModal.svelte
@@ -2,7 +2,6 @@
   import AlbumSharedLink from '$lib/components/album-page/album-shared-link.svelte';
   import Dropdown from '$lib/components/elements/dropdown.svelte';
   import Icon from '$lib/components/elements/icon.svelte';
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import { AppRoute } from '$lib/constants';
   import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
   import { makeSharedLinkUrl } from '$lib/utils';
@@ -15,7 +14,7 @@
     type SharedLinkResponseDto,
     type UserResponseDto,
   } from '@immich/sdk';
-  import { Button, Link, Stack, Text } from '@immich/ui';
+  import { Button, Link, Modal, ModalBody, Stack, Text } from '@immich/ui';
   import { mdiCheck, mdiEye, mdiLink, mdiPencil } from '@mdi/js';
   import { onMount } from 'svelte';
   import { t } from 'svelte-i18n';
@@ -76,63 +75,22 @@
 {#if sharedLinkUrl}
   <QrCodeModal title={$t('view_link')} onClose={() => (sharedLinkUrl = '')} value={sharedLinkUrl} />
 {:else}
-  <FullScreenModal title={$t('share')} showLogo {onClose}>
-    {#if Object.keys(selectedUsers).length > 0}
-      <div class="mb-2 py-2 sticky">
-        <p class="text-xs font-medium">{$t('selected')}</p>
-        <div class="my-2">
-          {#each Object.values(selectedUsers) as { user } (user.id)}
-            {#key user.id}
-              <div class="flex place-items-center gap-4 p-4">
-                <div
-                  class="flex h-10 w-10 items-center justify-center rounded-full border bg-immich-dark-success text-3xl text-white dark:border-immich-dark-gray dark:bg-immich-dark-success"
-                >
-                  <Icon path={mdiCheck} size={24} />
-                </div>
+  <Modal size="small" title={$t('share')} {onClose}>
+    <ModalBody>
+      {#if Object.keys(selectedUsers).length > 0}
+        <div class="mb-2 py-2 sticky">
+          <p class="text-xs font-medium">{$t('selected')}</p>
+          <div class="my-2">
+            {#each Object.values(selectedUsers) as { user } (user.id)}
+              {#key user.id}
+                <div class="flex place-items-center gap-4 p-4">
+                  <div
+                    class="flex h-10 w-10 items-center justify-center rounded-full border bg-immich-dark-success text-3xl text-white dark:border-immich-dark-gray dark:bg-immich-dark-success"
+                  >
+                    <Icon path={mdiCheck} size={24} />
+                  </div>
 
-                <!-- <UserAvatar {user} size="md" /> -->
-                <div class="text-start grow">
-                  <p class="text-immich-fg dark:text-immich-dark-fg">
-                    {user.name}
-                  </p>
-                  <p class="text-xs">
-                    {user.email}
-                  </p>
-                </div>
-
-                <Dropdown
-                  title={$t('role')}
-                  options={roleOptions}
-                  render={({ title, icon }) => ({ title, icon })}
-                  onSelect={({ value }) => handleChangeRole(user, value)}
-                />
-              </div>
-            {/key}
-          {/each}
-        </div>
-      </div>
-    {/if}
-
-    {#if users.length + Object.keys(selectedUsers).length === 0}
-      <p class="p-5 text-sm">
-        {$t('album_share_no_users')}
-      </p>
-    {/if}
-
-    <div class="immich-scrollbar max-h-[500px] overflow-y-auto">
-      {#if users.length > 0 && users.length !== Object.keys(selectedUsers).length}
-        <Text>{$t('users')}</Text>
-
-        <div class="my-2">
-          {#each users as user (user.id)}
-            {#if !Object.keys(selectedUsers).includes(user.id)}
-              <div class="flex place-items-center transition-all hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl">
-                <button
-                  type="button"
-                  onclick={() => handleToggle(user)}
-                  class="flex w-full place-items-center gap-4 p-4"
-                >
-                  <UserAvatar {user} size="md" />
+                  <!-- <UserAvatar {user} size="md" /> -->
                   <div class="text-start grow">
                     <p class="text-immich-fg dark:text-immich-dark-fg">
                       {user.name}
@@ -141,53 +99,96 @@
                       {user.email}
                     </p>
                   </div>
-                </button>
-              </div>
-            {/if}
-          {/each}
+
+                  <Dropdown
+                    title={$t('role')}
+                    options={roleOptions}
+                    render={({ title, icon }) => ({ title, icon })}
+                    onSelect={({ value }) => handleChangeRole(user, value)}
+                  />
+                </div>
+              {/key}
+            {/each}
+          </div>
         </div>
       {/if}
-    </div>
 
-    {#if users.length > 0}
-      <div class="py-3">
-        <Button
-          size="small"
-          fullWidth
-          shape="round"
-          disabled={Object.keys(selectedUsers).length === 0}
-          onclick={() =>
-            onClose({
-              action: 'sharedUsers',
-              data: Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })),
-            })}>{$t('add')}</Button
-        >
+      {#if users.length + Object.keys(selectedUsers).length === 0}
+        <p class="p-5 text-sm">
+          {$t('album_share_no_users')}
+        </p>
+      {/if}
+
+      <div class="immich-scrollbar max-h-[500px] overflow-y-auto">
+        {#if users.length > 0 && users.length !== Object.keys(selectedUsers).length}
+          <Text>{$t('users')}</Text>
+
+          <div class="my-2">
+            {#each users as user (user.id)}
+              {#if !Object.keys(selectedUsers).includes(user.id)}
+                <div class="flex place-items-center transition-all hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl">
+                  <button
+                    type="button"
+                    onclick={() => handleToggle(user)}
+                    class="flex w-full place-items-center gap-4 p-4"
+                  >
+                    <UserAvatar {user} size="md" />
+                    <div class="text-start grow">
+                      <p class="text-immich-fg dark:text-immich-dark-fg">
+                        {user.name}
+                      </p>
+                      <p class="text-xs">
+                        {user.email}
+                      </p>
+                    </div>
+                  </button>
+                </div>
+              {/if}
+            {/each}
+          </div>
+        {/if}
       </div>
-    {/if}
 
-    <hr class="my-4" />
-
-    <Stack gap={6}>
-      {#if sharedLinks.length > 0}
-        <div class="flex justify-between items-center">
-          <Text>{$t('shared_links')}</Text>
-          <Link href={AppRoute.SHARED_LINKS} class="text-sm">{$t('view_all')}</Link>
+      {#if users.length > 0}
+        <div class="py-3">
+          <Button
+            size="small"
+            fullWidth
+            shape="round"
+            disabled={Object.keys(selectedUsers).length === 0}
+            onclick={() =>
+              onClose({
+                action: 'sharedUsers',
+                data: Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })),
+              })}>{$t('add')}</Button
+          >
         </div>
-
-        <Stack gap={4}>
-          {#each sharedLinks as sharedLink (sharedLink.id)}
-            <AlbumSharedLink {album} {sharedLink} onViewQrCode={() => handleViewQrCode(sharedLink)} />
-          {/each}
-        </Stack>
       {/if}
 
-      <Button
-        leadingIcon={mdiLink}
-        size="small"
-        shape="round"
-        fullWidth
-        onclick={() => onClose({ action: 'sharedLink' })}>{$t('create_link')}</Button
-      >
-    </Stack>
-  </FullScreenModal>
+      <hr class="my-4" />
+
+      <Stack gap={6}>
+        {#if sharedLinks.length > 0}
+          <div class="flex justify-between items-center">
+            <Text>{$t('shared_links')}</Text>
+            <Link href={AppRoute.SHARED_LINKS} class="text-sm">{$t('view_all')}</Link>
+          </div>
+
+          <Stack gap={4}>
+            {#each sharedLinks as sharedLink (sharedLink.id)}
+              <AlbumSharedLink {album} {sharedLink} onViewQrCode={() => handleViewQrCode(sharedLink)} />
+            {/each}
+          </Stack>
+        {/if}
+
+        <Button
+          leadingIcon={mdiLink}
+          size="small"
+          shape="round"
+          fullWidth
+          onclick={() => onClose({ action: 'sharedLink' })}>{$t('create_link')}</Button
+        >
+      </Stack>
+    </ModalBody>
+  </Modal>
 {/if}
diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 8eba11fcfd..7fcc70ae25 100644
--- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -453,150 +453,6 @@
 
 <div class="flex overflow-hidden" use:scrollMemoryClearer={{ routeStartsWith: AppRoute.ALBUMS }}>
   <div class="relative w-full shrink">
-    {#if assetInteraction.selectionActive}
-      <AssetSelectControlBar
-        assets={assetInteraction.selectedAssets}
-        clearSelect={() => assetInteraction.clearMultiselect()}
-      >
-        <CreateSharedLink />
-        <SelectAllAssets {assetStore} {assetInteraction} />
-        <ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
-          <AddToAlbum />
-          <AddToAlbum shared />
-        </ButtonContextMenu>
-        {#if assetInteraction.isAllUserOwned}
-          <FavoriteAction
-            removeFavorite={assetInteraction.isAllFavorite}
-            onFavorite={(ids, isFavorite) =>
-              assetStore.updateAssetOperation(ids, (asset) => {
-                asset.isFavorite = isFavorite;
-                return { remove: false };
-              })}
-          ></FavoriteAction>
-        {/if}
-        <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
-          <DownloadAction menuItem filename="{album.albumName}.zip" />
-          {#if assetInteraction.isAllUserOwned}
-            <ChangeDate menuItem />
-            <ChangeDescription menuItem />
-            <ChangeLocation menuItem />
-            {#if assetInteraction.selectedAssets.length === 1}
-              <MenuOption
-                text={$t('set_as_album_cover')}
-                icon={mdiImageOutline}
-                onClick={() => updateThumbnailUsingCurrentSelection()}
-              />
-            {/if}
-            <ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} />
-          {/if}
-
-          {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
-            <TagAction menuItem />
-          {/if}
-
-          {#if isOwned || assetInteraction.isAllUserOwned}
-            <RemoveFromAlbum menuItem bind:album onRemove={handleRemoveAssets} />
-          {/if}
-          {#if assetInteraction.isAllUserOwned}
-            <DeleteAssets menuItem onAssetDelete={handleRemoveAssets} />
-          {/if}
-        </ButtonContextMenu>
-      </AssetSelectControlBar>
-    {:else}
-      {#if viewMode === AlbumPageViewMode.VIEW}
-        <ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(backUrl)}>
-          {#snippet trailing()}
-            {#if isEditor}
-              <CircleIconButton
-                title={$t('add_photos')}
-                onclick={async () => {
-                  assetStore.suspendTransitions = true;
-                  viewMode = AlbumPageViewMode.SELECT_ASSETS;
-                  oldAt = { at: $gridScrollTarget?.at };
-                  await navigate(
-                    { targetRoute: 'current', assetId: null, assetGridRouteSearchParams: { at: null } },
-                    { replaceState: true },
-                  );
-                }}
-                icon={mdiImagePlusOutline}
-              />
-            {/if}
-
-            {#if isOwned}
-              <CircleIconButton title={$t('share')} onclick={handleShare} icon={mdiShareVariantOutline} />
-            {/if}
-
-            <AlbumMap {album} />
-
-            {#if album.assetCount > 0}
-              <CircleIconButton title={$t('slideshow')} onclick={handleStartSlideshow} icon={mdiPresentationPlay} />
-              <CircleIconButton title={$t('download')} onclick={handleDownloadAlbum} icon={mdiFolderDownloadOutline} />
-            {/if}
-
-            {#if isOwned}
-              <ButtonContextMenu icon={mdiDotsVertical} title={$t('album_options')}>
-                {#if album.assetCount > 0}
-                  <MenuOption
-                    icon={mdiImageOutline}
-                    text={$t('select_album_cover')}
-                    onClick={() => (viewMode = AlbumPageViewMode.SELECT_THUMBNAIL)}
-                  />
-                  <MenuOption
-                    icon={mdiCogOutline}
-                    text={$t('options')}
-                    onClick={() => (viewMode = AlbumPageViewMode.OPTIONS)}
-                  />
-                {/if}
-
-                <MenuOption icon={mdiDeleteOutline} text={$t('delete_album')} onClick={() => handleRemoveAlbum()} />
-              </ButtonContextMenu>
-            {/if}
-
-            {#if isCreatingSharedAlbum && album.albumUsers.length === 0}
-              <Button size="small" disabled={album.assetCount === 0} onclick={handleShare}>
-                {$t('share')}
-              </Button>
-            {/if}
-          {/snippet}
-        </ControlAppBar>
-      {/if}
-
-      {#if viewMode === AlbumPageViewMode.SELECT_ASSETS}
-        <ControlAppBar onClose={handleCloseSelectAssets}>
-          {#snippet leading()}
-            <p class="text-lg dark:text-immich-dark-fg">
-              {#if !timelineInteraction.selectionActive}
-                {$t('add_to_album')}
-              {:else}
-                {$t('selected_count', { values: { count: timelineInteraction.selectedAssets.length } })}
-              {/if}
-            </p>
-          {/snippet}
-
-          {#snippet trailing()}
-            <button
-              type="button"
-              onclick={handleSelectFromComputer}
-              class="rounded-lg px-6 py-2 text-sm font-medium text-immich-primary transition-all hover:bg-immich-primary/10 dark:text-immich-dark-primary dark:hover:bg-immich-dark-primary/25"
-            >
-              {$t('select_from_computer')}
-            </button>
-            <Button size="small" disabled={!timelineInteraction.selectionActive} onclick={handleAddAssets}
-              >{$t('done')}</Button
-            >
-          {/snippet}
-        </ControlAppBar>
-      {/if}
-
-      {#if viewMode === AlbumPageViewMode.SELECT_THUMBNAIL}
-        <ControlAppBar onClose={() => (viewMode = AlbumPageViewMode.VIEW)}>
-          {#snippet leading()}
-            {$t('select_album_cover')}
-          {/snippet}
-        </ControlAppBar>
-      {/if}
-    {/if}
-
     <main class="relative h-dvh overflow-hidden px-6 max-md:pt-(--navbar-height-md) pt-(--navbar-height)">
       <AssetGrid
         enableRouting={viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : true}
@@ -685,7 +541,7 @@
                 <button
                   type="button"
                   onclick={() => (viewMode = AlbumPageViewMode.SELECT_ASSETS)}
-                  class="mt-5 bg-subtle flex w-full place-items-center gap-6 rounded-md border px-8 py-8 text-immich-fg transition-all hover:bg-gray-100 hover:text-immich-primary dark:border-none dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
+                  class="mt-5 bg-subtle flex w-full place-items-center gap-6 rounded-2xl border px-8 py-8 text-immich-fg transition-all hover:bg-gray-100 dark:hover:bg-gray-500/20 hover:text-immich-primary dark:border-none dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
                 >
                   <span class="text-text-immich-primary dark:text-immich-dark-primary"
                     ><Icon path={mdiPlus} size="24" />
@@ -710,6 +566,150 @@
         </div>
       {/if}
     </main>
+
+    {#if assetInteraction.selectionActive}
+      <AssetSelectControlBar
+        assets={assetInteraction.selectedAssets}
+        clearSelect={() => assetInteraction.clearMultiselect()}
+      >
+        <CreateSharedLink />
+        <SelectAllAssets {assetStore} {assetInteraction} />
+        <ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
+          <AddToAlbum />
+          <AddToAlbum shared />
+        </ButtonContextMenu>
+        {#if assetInteraction.isAllUserOwned}
+          <FavoriteAction
+            removeFavorite={assetInteraction.isAllFavorite}
+            onFavorite={(ids, isFavorite) =>
+              assetStore.updateAssetOperation(ids, (asset) => {
+                asset.isFavorite = isFavorite;
+                return { remove: false };
+              })}
+          ></FavoriteAction>
+        {/if}
+        <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')} offset={{ x: 175, y: 25 }}>
+          <DownloadAction menuItem filename="{album.albumName}.zip" />
+          {#if assetInteraction.isAllUserOwned}
+            <ChangeDate menuItem />
+            <ChangeDescription menuItem />
+            <ChangeLocation menuItem />
+            {#if assetInteraction.selectedAssets.length === 1}
+              <MenuOption
+                text={$t('set_as_album_cover')}
+                icon={mdiImageOutline}
+                onClick={() => updateThumbnailUsingCurrentSelection()}
+              />
+            {/if}
+            <ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} />
+          {/if}
+
+          {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
+            <TagAction menuItem />
+          {/if}
+
+          {#if isOwned || assetInteraction.isAllUserOwned}
+            <RemoveFromAlbum menuItem bind:album onRemove={handleRemoveAssets} />
+          {/if}
+          {#if assetInteraction.isAllUserOwned}
+            <DeleteAssets menuItem onAssetDelete={handleRemoveAssets} />
+          {/if}
+        </ButtonContextMenu>
+      </AssetSelectControlBar>
+    {:else}
+      {#if viewMode === AlbumPageViewMode.VIEW}
+        <ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(backUrl)}>
+          {#snippet trailing()}
+            {#if isEditor}
+              <CircleIconButton
+                title={$t('add_photos')}
+                onclick={async () => {
+                  assetStore.suspendTransitions = true;
+                  viewMode = AlbumPageViewMode.SELECT_ASSETS;
+                  oldAt = { at: $gridScrollTarget?.at };
+                  await navigate(
+                    { targetRoute: 'current', assetId: null, assetGridRouteSearchParams: { at: null } },
+                    { replaceState: true },
+                  );
+                }}
+                icon={mdiImagePlusOutline}
+              />
+            {/if}
+
+            {#if isOwned}
+              <CircleIconButton title={$t('share')} onclick={handleShare} icon={mdiShareVariantOutline} />
+            {/if}
+
+            <AlbumMap {album} />
+
+            {#if album.assetCount > 0}
+              <CircleIconButton title={$t('slideshow')} onclick={handleStartSlideshow} icon={mdiPresentationPlay} />
+              <CircleIconButton title={$t('download')} onclick={handleDownloadAlbum} icon={mdiFolderDownloadOutline} />
+            {/if}
+
+            {#if isOwned}
+              <ButtonContextMenu icon={mdiDotsVertical} title={$t('album_options')} offset={{ x: 175, y: 25 }}>
+                {#if album.assetCount > 0}
+                  <MenuOption
+                    icon={mdiImageOutline}
+                    text={$t('select_album_cover')}
+                    onClick={() => (viewMode = AlbumPageViewMode.SELECT_THUMBNAIL)}
+                  />
+                  <MenuOption
+                    icon={mdiCogOutline}
+                    text={$t('options')}
+                    onClick={() => (viewMode = AlbumPageViewMode.OPTIONS)}
+                  />
+                {/if}
+
+                <MenuOption icon={mdiDeleteOutline} text={$t('delete_album')} onClick={() => handleRemoveAlbum()} />
+              </ButtonContextMenu>
+            {/if}
+
+            {#if isCreatingSharedAlbum && album.albumUsers.length === 0}
+              <Button size="small" disabled={album.assetCount === 0} onclick={handleShare}>
+                {$t('share')}
+              </Button>
+            {/if}
+          {/snippet}
+        </ControlAppBar>
+      {/if}
+
+      {#if viewMode === AlbumPageViewMode.SELECT_ASSETS}
+        <ControlAppBar onClose={handleCloseSelectAssets}>
+          {#snippet leading()}
+            <p class="text-lg dark:text-immich-dark-fg">
+              {#if !timelineInteraction.selectionActive}
+                {$t('add_to_album')}
+              {:else}
+                {$t('selected_count', { values: { count: timelineInteraction.selectedAssets.length } })}
+              {/if}
+            </p>
+          {/snippet}
+
+          {#snippet trailing()}
+            <button
+              type="button"
+              onclick={handleSelectFromComputer}
+              class="rounded-lg px-6 py-2 text-sm font-medium text-immich-primary transition-all hover:bg-immich-primary/10 dark:text-immich-dark-primary dark:hover:bg-immich-dark-primary/25"
+            >
+              {$t('select_from_computer')}
+            </button>
+            <Button size="small" disabled={!timelineInteraction.selectionActive} onclick={handleAddAssets}
+              >{$t('done')}</Button
+            >
+          {/snippet}
+        </ControlAppBar>
+      {/if}
+
+      {#if viewMode === AlbumPageViewMode.SELECT_THUMBNAIL}
+        <ControlAppBar onClose={() => (viewMode = AlbumPageViewMode.VIEW)}>
+          {#snippet leading()}
+            {$t('select_album_cover')}
+          {/snippet}
+        </ControlAppBar>
+      {/if}
+    {/if}
   </div>
   {#if album.albumUsers.length > 0 && album && isShowActivity && $user && !$showAssetViewer}
     <div class="flex">
diff --git a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 5353724591..0ee796ee0a 100644
--- a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -40,6 +40,20 @@
   };
 </script>
 
+<UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
+  <AssetGrid
+    enableRouting={true}
+    {assetStore}
+    {assetInteraction}
+    removeAction={AssetAction.UNARCHIVE}
+    onEscape={handleEscape}
+  >
+    {#snippet empty()}
+      <EmptyPlaceholder text={$t('no_archived_assets_message')} />
+    {/snippet}
+  </AssetGrid>
+</UserPageLayout>
+
 {#if assetInteraction.selectionActive}
   <AssetSelectControlBar
     assets={assetInteraction.selectedAssets}
@@ -73,17 +87,3 @@
     </ButtonContextMenu>
   </AssetSelectControlBar>
 {/if}
-
-<UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
-  <AssetGrid
-    enableRouting={true}
-    {assetStore}
-    {assetInteraction}
-    removeAction={AssetAction.UNARCHIVE}
-    onEscape={handleEscape}
-  >
-    {#snippet empty()}
-      <EmptyPlaceholder text={$t('no_archived_assets_message')} />
-    {/snippet}
-  </AssetGrid>
-</UserPageLayout>
diff --git a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 7f02bff8e0..b67c4fb6b7 100644
--- a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -44,6 +44,21 @@
   };
 </script>
 
+<UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
+  <AssetGrid
+    enableRouting={true}
+    withStacked={true}
+    {assetStore}
+    {assetInteraction}
+    removeAction={AssetAction.UNFAVORITE}
+    onEscape={handleEscape}
+  >
+    {#snippet empty()}
+      <EmptyPlaceholder text={$t('no_favorites_message')} />
+    {/snippet}
+  </AssetGrid>
+</UserPageLayout>
+
 <!-- Multiselection mode app bar -->
 {#if assetInteraction.selectionActive}
   <AssetSelectControlBar
@@ -74,18 +89,3 @@
     </ButtonContextMenu>
   </AssetSelectControlBar>
 {/if}
-
-<UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
-  <AssetGrid
-    enableRouting={true}
-    withStacked={true}
-    {assetStore}
-    {assetInteraction}
-    removeAction={AssetAction.UNFAVORITE}
-    onEscape={handleEscape}
-  >
-    {#snippet empty()}
-      <EmptyPlaceholder text={$t('no_favorites_message')} />
-    {/snippet}
-  </AssetGrid>
-</UserPageLayout>
diff --git a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 0de8206193..a0ec649fea 100644
--- a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -90,6 +90,44 @@
   };
 </script>
 
+<UserPageLayout title={data.meta.title}>
+  {#snippet sidebar()}
+    <Sidebar>
+      <SkipLink target={`#${headerId}`} text={$t('skip_to_folders')} breakpoint="md" />
+      <section>
+        <div class="text-xs ps-4 mb-2 dark:text-white">{$t('explorer').toUpperCase()}</div>
+        <div class="h-full">
+          <TreeItems
+            icons={{ default: mdiFolderOutline, active: mdiFolder }}
+            items={tree}
+            active={currentPath}
+            getLink={getLinkForPath}
+          />
+        </div>
+      </section>
+    </Sidebar>
+  {/snippet}
+
+  <Breadcrumbs {pathSegments} icon={mdiFolderHome} title={$t('folders')} getLink={getLinkForPath} />
+
+  <section class="mt-2 h-[calc(100%-(--spacing(20)))] overflow-auto immich-scrollbar">
+    <TreeItemThumbnails items={currentTreeItems} icon={mdiFolder} onClick={handleNavigateToFolder} />
+
+    <!-- Assets -->
+    {#if data.pathAssets && data.pathAssets.length > 0}
+      <div bind:clientHeight={viewport.height} bind:clientWidth={viewport.width} class="mt-2">
+        <GalleryViewer
+          assets={data.pathAssets}
+          {assetInteraction}
+          {viewport}
+          showAssetName={true}
+          pageHeaderOffset={54}
+        />
+      </div>
+    {/if}
+  </section>
+</UserPageLayout>
+
 {#if assetInteraction.selectionActive}
   <div class="fixed top-0 start-0 w-full">
     <AssetSelectControlBar
@@ -132,41 +170,3 @@
     </AssetSelectControlBar>
   </div>
 {/if}
-
-<UserPageLayout title={data.meta.title}>
-  {#snippet sidebar()}
-    <Sidebar>
-      <SkipLink target={`#${headerId}`} text={$t('skip_to_folders')} breakpoint="md" />
-      <section>
-        <div class="text-xs ps-4 mb-2 dark:text-white">{$t('explorer').toUpperCase()}</div>
-        <div class="h-full">
-          <TreeItems
-            icons={{ default: mdiFolderOutline, active: mdiFolder }}
-            items={tree}
-            active={currentPath}
-            getLink={getLinkForPath}
-          />
-        </div>
-      </section>
-    </Sidebar>
-  {/snippet}
-
-  <Breadcrumbs {pathSegments} icon={mdiFolderHome} title={$t('folders')} getLink={getLinkForPath} />
-
-  <section class="mt-2 h-[calc(100%-(--spacing(20)))] overflow-auto immich-scrollbar">
-    <TreeItemThumbnails items={currentTreeItems} icon={mdiFolder} onClick={handleNavigateToFolder} />
-
-    <!-- Assets -->
-    {#if data.pathAssets && data.pathAssets.length > 0}
-      <div bind:clientHeight={viewport.height} bind:clientWidth={viewport.width} class="mt-2">
-        <GalleryViewer
-          assets={data.pathAssets}
-          {assetInteraction}
-          {viewport}
-          showAssetName={true}
-          pageHeaderOffset={54}
-        />
-      </div>
-    {/if}
-  </section>
-</UserPageLayout>
diff --git a/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 9c41a7fe59..32b20ab1dd 100644
--- a/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -51,26 +51,9 @@
   };
 </script>
 
-<!-- Multi-selection mode app bar -->
-{#if assetInteraction.selectionActive}
-  <AssetSelectControlBar
-    assets={assetInteraction.selectedAssets}
-    clearSelect={() => assetInteraction.clearMultiselect()}
-  >
-    <SelectAllAssets withText {assetStore} {assetInteraction} />
-    <SetVisibilityAction unlock onVisibilitySet={handleMoveOffLockedFolder} />
-    <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
-      <DownloadAction menuItem />
-      <ChangeDate menuItem />
-      <ChangeLocation menuItem />
-      <DeleteAssets menuItem force onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
-    </ButtonContextMenu>
-  </AssetSelectControlBar>
-{/if}
-
 <UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
   {#snippet buttons()}
-    <Button size="small" variant="filled" color="warning" leadingIcon={mdiLockOutline} onclick={handleLock}>
+    <Button size="small" variant="ghost" color="primary" leadingIcon={mdiLockOutline} onclick={handleLock}>
       {$t('lock')}
     </Button>
   {/snippet}
@@ -87,3 +70,20 @@
     {/snippet}
   </AssetGrid>
 </UserPageLayout>
+
+<!-- Multi-selection mode app bar -->
+{#if assetInteraction.selectionActive}
+  <AssetSelectControlBar
+    assets={assetInteraction.selectedAssets}
+    clearSelect={() => assetInteraction.clearMultiselect()}
+  >
+    <SelectAllAssets withText {assetStore} {assetInteraction} />
+    <SetVisibilityAction unlock onVisibilitySet={handleMoveOffLockedFolder} />
+    <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
+      <DownloadAction menuItem />
+      <ChangeDate menuItem />
+      <ChangeLocation menuItem />
+      <DeleteAssets menuItem force onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
+    </ButtonContextMenu>
+  </AssetSelectControlBar>
+{/if}
diff --git a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index c921d6a7e9..48d830d78c 100644
--- a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -43,6 +43,8 @@
 </script>
 
 <main class="grid h-dvh pt-18">
+  <AssetGrid enableRouting={true} {assetStore} {assetInteraction} onEscape={handleEscape} />
+
   {#if assetInteraction.selectionActive}
     <AssetSelectControlBar
       assets={assetInteraction.selectedAssets}
@@ -64,5 +66,4 @@
       {/snippet}
     </ControlAppBar>
   {/if}
-  <AssetGrid enableRouting={true} {assetStore} {assetInteraction} onEscape={handleEscape} />
 </main>
diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 66c8ef0af4..6e47d74413 100644
--- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -421,11 +421,11 @@
                     class="flex flex-col justify-center text-start px-4 text-immich-primary dark:text-immich-dark-primary"
                   >
                     <p class="w-40 sm:w-72 font-medium truncate">{person.name || $t('add_a_name')}</p>
-                    <p class="text-sm text-gray-500 dark:text-immich-gray">
+                    <p class="text-sm text-gray-500 dark:text-gray-400">
                       {$t('assets_count', { values: { count: numberOfAssets } })}
                     </p>
                     {#if person.birthDate}
-                      <p class="text-sm text-gray-500 dark:text-immich-gray">
+                      <p class="text-sm text-gray-500 dark:text-gray-400">
                         {$t('person_birthdate', {
                           values: {
                             date: DateTime.fromISO(person.birthDate).toLocaleString(
diff --git a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte
index c8329dee32..c897ed4791 100644
--- a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -4,7 +4,6 @@
   import SkipLink from '$lib/components/elements/buttons/skip-link.svelte';
   import UserPageLayout, { headerId } from '$lib/components/layouts/user-page-layout.svelte';
   import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
-  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
   import {
     notificationController,
     NotificationType,
@@ -20,7 +19,7 @@
   import { AssetStore } from '$lib/stores/assets-store.svelte';
   import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils';
   import { deleteTag, getAllTags, updateTag, upsertTags, type TagResponseDto } from '@immich/sdk';
-  import { Button, HStack, Text } from '@immich/ui';
+  import { Button, HStack, Modal, ModalBody, ModalFooter, Text } from '@immich/ui';
   import { mdiPencil, mdiPlus, mdiTag, mdiTagMultiple, mdiTrashCanOutline } from '@mdi/js';
   import { onDestroy } from 'svelte';
   import { t } from 'svelte-i18n';
@@ -192,47 +191,55 @@
 </UserPageLayout>
 
 {#if isNewOpen}
-  <FullScreenModal title={$t('create_tag')} icon={mdiTag} onClose={handleCancel}>
-    <div class="text-immich-primary dark:text-immich-dark-primary">
-      <p class="text-sm dark:text-immich-dark-fg">
-        {$t('create_tag_description')}
-      </p>
-    </div>
-
-    <form {onsubmit} autocomplete="off" id="create-tag-form">
-      <div class="my-4 flex flex-col gap-2">
-        <SettingInputField
-          inputType={SettingInputFieldType.TEXT}
-          label={$t('tag').toUpperCase()}
-          bind:value={newTagValue}
-          required={true}
-          autofocus={true}
-        />
+  <Modal size="small" title={$t('create_tag')} icon={mdiTag} onClose={handleCancel}>
+    <ModalBody>
+      <div class="text-immich-primary dark:text-immich-dark-primary">
+        <p class="text-sm dark:text-immich-dark-fg">
+          {$t('create_tag_description')}
+        </p>
       </div>
-    </form>
 
-    {#snippet stickyBottom()}
-      <Button color="secondary" fullWidth shape="round" onclick={() => handleCancel()}>{$t('cancel')}</Button>
-      <Button type="submit" fullWidth shape="round" form="create-tag-form">{$t('create')}</Button>
-    {/snippet}
-  </FullScreenModal>
+      <form {onsubmit} autocomplete="off" id="create-tag-form">
+        <div class="my-4 flex flex-col gap-2">
+          <SettingInputField
+            inputType={SettingInputFieldType.TEXT}
+            label={$t('tag').toUpperCase()}
+            bind:value={newTagValue}
+            required={true}
+            autofocus={true}
+          />
+        </div>
+      </form>
+    </ModalBody>
+
+    <ModalFooter>
+      <div class="flex w-full gap-2">
+        <Button color="secondary" fullWidth shape="round" onclick={() => handleCancel()}>{$t('cancel')}</Button>
+        <Button type="submit" fullWidth shape="round" form="create-tag-form">{$t('create')}</Button>
+      </div>
+    </ModalFooter>
+  </Modal>
 {/if}
 
 {#if isEditOpen}
-  <FullScreenModal title={$t('edit_tag')} icon={mdiTag} onClose={handleCancel}>
-    <form {onsubmit} autocomplete="off" id="edit-tag-form">
-      <div class="my-4 flex flex-col gap-2">
-        <SettingInputField
-          inputType={SettingInputFieldType.COLOR}
-          label={$t('color').toUpperCase()}
-          bind:value={newTagColor}
-        />
-      </div>
-    </form>
+  <Modal title={$t('edit_tag')} icon={mdiTag} onClose={handleCancel}>
+    <ModalBody>
+      <form {onsubmit} autocomplete="off" id="edit-tag-form">
+        <div class="my-4 flex flex-col gap-2">
+          <SettingInputField
+            inputType={SettingInputFieldType.COLOR}
+            label={$t('color').toUpperCase()}
+            bind:value={newTagColor}
+          />
+        </div>
+      </form>
+    </ModalBody>
 
-    {#snippet stickyBottom()}
-      <Button color="secondary" fullWidth shape="round" onclick={() => handleCancel()}>{$t('cancel')}</Button>
-      <Button type="submit" fullWidth shape="round" form="edit-tag-form">{$t('save')}</Button>
-    {/snippet}
-  </FullScreenModal>
+    <ModalFooter>
+      <div class="flex w-full gap-2">
+        <Button color="secondary" fullWidth shape="round" onclick={() => handleCancel()}>{$t('cancel')}</Button>
+        <Button type="submit" fullWidth shape="round" form="edit-tag-form">{$t('save')}</Button>
+      </div>
+    </ModalFooter>
+  </Modal>
 {/if}
diff --git a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 3750b239d5..36ff2ec266 100644
--- a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -90,17 +90,6 @@
   };
 </script>
 
-{#if assetInteraction.selectionActive}
-  <AssetSelectControlBar
-    assets={assetInteraction.selectedAssets}
-    clearSelect={() => assetInteraction.clearMultiselect()}
-  >
-    <SelectAllAssets {assetStore} {assetInteraction} />
-    <DeleteAssets force onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
-    <RestoreAssets onRestore={(assetIds) => assetStore.removeAssets(assetIds)} />
-  </AssetSelectControlBar>
-{/if}
-
 {#if $featureFlags.loaded && $featureFlags.trash}
   <UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
     {#snippet buttons()}
@@ -138,3 +127,14 @@
     </AssetGrid>
   </UserPageLayout>
 {/if}
+
+{#if assetInteraction.selectionActive}
+  <AssetSelectControlBar
+    assets={assetInteraction.selectedAssets}
+    clearSelect={() => assetInteraction.clearMultiselect()}
+  >
+    <SelectAllAssets {assetStore} {assetInteraction} />
+    <DeleteAssets force onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
+    <RestoreAssets onRestore={(assetIds) => assetStore.removeAssets(assetIds)} />
+  </AssetSelectControlBar>
+{/if}
diff --git a/web/src/routes/admin/users/[id]/+page.svelte b/web/src/routes/admin/users/[id]/+page.svelte
index 6b0b0234fb..a33f2e9ec5 100644
--- a/web/src/routes/admin/users/[id]/+page.svelte
+++ b/web/src/routes/admin/users/[id]/+page.svelte
@@ -249,47 +249,49 @@
         <div>
           <Card color="secondary">
             <CardHeader>
-              <div class="flex items-center gap-2">
+              <div class="flex items-center gap-2 px-4 py-2 text-primary">
                 <Icon icon={mdiAccountOutline} size="1.5rem" />
                 <CardTitle>{$t('profile')}</CardTitle>
               </div>
             </CardHeader>
             <CardBody>
-              <Stack gap={2}>
-                <div>
-                  <Heading tag="h3" size="tiny">{$t('name')}</Heading>
-                  <Text>{user.name}</Text>
-                </div>
-                <div>
-                  <Heading tag="h3" size="tiny">{$t('email')}</Heading>
-                  <Text>{user.email}</Text>
-                </div>
-                <div>
-                  <Heading tag="h3" size="tiny">{$t('created_at')}</Heading>
-                  <Text>{user.createdAt}</Text>
-                </div>
-                <div>
-                  <Heading tag="h3" size="tiny">{$t('updated_at')}</Heading>
-                  <Text>{user.updatedAt}</Text>
-                </div>
-                <div>
-                  <Heading tag="h3" size="tiny">{$t('id')}</Heading>
-                  <Code>{user.id}</Code>
-                </div>
-              </Stack>
+              <div class="px-4 pb-7">
+                <Stack gap={2}>
+                  <div>
+                    <Heading tag="h3" size="tiny">{$t('name')}</Heading>
+                    <Text>{user.name}</Text>
+                  </div>
+                  <div>
+                    <Heading tag="h3" size="tiny">{$t('email')}</Heading>
+                    <Text>{user.email}</Text>
+                  </div>
+                  <div>
+                    <Heading tag="h3" size="tiny">{$t('created_at')}</Heading>
+                    <Text>{user.createdAt}</Text>
+                  </div>
+                  <div>
+                    <Heading tag="h3" size="tiny">{$t('updated_at')}</Heading>
+                    <Text>{user.updatedAt}</Text>
+                  </div>
+                  <div>
+                    <Heading tag="h3" size="tiny">{$t('id')}</Heading>
+                    <Code>{user.id}</Code>
+                  </div>
+                </Stack>
+              </div>
             </CardBody>
           </Card>
         </div>
         <Card color="secondary">
           <CardHeader>
-            <div class="flex items-center gap-2">
+            <div class="flex items-center gap-2 px-4 py-2 text-primary">
               <Icon icon={mdiFeatureSearchOutline} size="1.5rem" />
               <CardTitle>{$t('features')}</CardTitle>
             </div>
           </CardHeader>
           <CardBody>
-            <div>
-              <Stack gap={2}>
+            <div class="px-4 pb-4">
+              <Stack gap={3}>
                 <Field readOnly label={$t('email_notifications')}>
                   <Switch checked={userPreferences.emailNotifications.enabled} color="primary" />
                 </Field>
@@ -320,13 +322,13 @@
         </Card>
         <Card color="secondary">
           <CardHeader>
-            <div class="flex items-center gap-2">
+            <div class="flex items-center gap-2 px-4 py-2 text-primary">
               <Icon icon={mdiChartPieOutline} size="1.5rem" />
               <CardTitle>{$t('storage_quota')}</CardTitle>
             </div>
           </CardHeader>
           <CardBody>
-            <div>
+            <div class="px-4 pb-4">
               {#if user.quotaSizeInBytes !== null && user.quotaSizeInBytes >= 0}
                 <Text>
                   {$t('storage_usage', {