diff --git a/web/package-lock.json b/web/package-lock.json
index 37f944d3bb..37f7faf711 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.17.3",
+        "@immich/ui": "^0.18.1",
         "@mapbox/mapbox-gl-rtl-text": "0.2.3",
         "@mdi/js": "^7.4.47",
         "@photo-sphere-viewer/core": "^5.11.5",
@@ -1320,9 +1320,9 @@
       "link": true
     },
     "node_modules/@immich/ui": {
-      "version": "0.17.4",
-      "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.17.4.tgz",
-      "integrity": "sha512-a6M7Fxno5fwY5A0kxdluS8r+A4L6xZhSTKMW8c8hoFhQHvbBTHAsGFKQF3GOEQLOlUuvsS2Lt7dMevBlAPgo/A==",
+      "version": "0.18.1",
+      "resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.18.1.tgz",
+      "integrity": "sha512-XWWO6OTfH3MektyxCn0hWefZyOGyWwwx/2zHinuShpxTHSyfveJ4mOkFP8DkyMz0dnvJ1EfdkPBMkld3y5R/Hw==",
       "license": "GNU Affero General Public License version 3",
       "dependencies": {
         "@mdi/js": "^7.4.47",
diff --git a/web/package.json b/web/package.json
index c32e7b04a8..4102765f70 100644
--- a/web/package.json
+++ b/web/package.json
@@ -27,7 +27,7 @@
   "dependencies": {
     "@formatjs/icu-messageformat-parser": "^2.9.8",
     "@immich/sdk": "file:../open-api/typescript-sdk",
-    "@immich/ui": "^0.17.3",
+    "@immich/ui": "^0.18.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/lib/components/admin-page/jobs/job-tile.svelte b/web/src/lib/components/admin-page/jobs/job-tile.svelte
index 80dd29e0be..c77ff60f22 100644
--- a/web/src/lib/components/admin-page/jobs/job-tile.svelte
+++ b/web/src/lib/components/admin-page/jobs/job-tile.svelte
@@ -51,7 +51,7 @@
   let isIdle = $derived(!queueStatus.isActive && !queueStatus.isPaused);
   let multipleButtons = $derived(allText || refreshText);
 
-  const commonClasses = 'flex place-items-center justify-between w-full py-2 sm:py-4 pr-4 pl-6';
+  const commonClasses = 'flex place-items-center justify-between w-full py-2 sm:py-4 pe-4 ps-6';
 </script>
 
 <div
@@ -110,7 +110,7 @@
 
       <div class="mt-2 flex w-full max-w-md flex-col sm:flex-row">
         <div
-          class="{commonClasses} rounded-t-lg bg-immich-primary text-white dark:bg-immich-dark-primary dark:text-immich-dark-gray sm:rounded-l-lg sm:rounded-r-none"
+          class="{commonClasses} rounded-t-lg bg-immich-primary text-white dark:bg-immich-dark-primary dark:text-immich-dark-gray sm:rounded-s-lg sm:rounded-e-none"
         >
           <p>{$t('active')}</p>
           <p class="text-2xl">
@@ -119,7 +119,7 @@
         </div>
 
         <div
-          class="{commonClasses} flex-row-reverse rounded-b-lg bg-gray-200 text-immich-dark-bg dark:bg-gray-700 dark:text-immich-gray sm:rounded-l-none sm:rounded-r-lg"
+          class="{commonClasses} flex-row-reverse rounded-b-lg bg-gray-200 text-immich-dark-bg dark:bg-gray-700 dark:text-immich-gray sm:rounded-s-none sm:rounded-e-lg"
         >
           <p class="text-2xl">
             {waitingCount.toLocaleString($locale)}
diff --git a/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte b/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte
index bb288511ac..8bae8fee4b 100644
--- a/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte
+++ b/web/src/lib/components/admin-page/server-stats/server-stats-panel.svelte
@@ -79,7 +79,7 @@
             <span class="text-[#DCDADA] dark:text-[#525252]">{zeros(statsUsage)}</span><span
               class="text-immich-primary dark:text-immich-dark-primary">{statsUsage}</span
             >
-            <span class="my-auto ml-2 text-center text-base font-light text-gray-400">{statsUsageUnit}</span>
+            <span class="my-auto ms-2 text-center text-base font-light text-gray-400">{statsUsageUnit}</span>
           </div>
         </div>
       </div>
@@ -88,7 +88,7 @@
 
   <div>
     <p class="text-sm dark:text-immich-dark-fg">{$t('user_usage_detail').toUpperCase()}</p>
-    <table class="mt-5 w-full text-left">
+    <table class="mt-5 w-full text-start">
       <thead
         class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
       >
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 14d32c055f..b1804427e9 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
@@ -31,7 +31,7 @@
       class="text-immich-primary dark:text-immich-dark-primary">{value}</span
     >
     {#if unit}
-      <span class="absolute -top-5 right-2 text-base font-light text-gray-400">{unit}</span>
+      <span class="absolute -top-5 end-2 text-base font-light text-gray-400">{unit}</span>
     {/if}
   </div>
 </div>
diff --git a/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte b/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
index 5380a76286..67da6bb7f2 100644
--- a/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/auth/auth-settings.svelte
@@ -76,13 +76,13 @@
 <div>
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" onsubmit={(e) => e.preventDefault()}>
-      <div class="ml-4 mt-4 flex flex-col">
+      <div class="ms-4 mt-4 flex flex-col">
         <SettingAccordion
           key="oauth"
           title={$t('admin.oauth_settings')}
           subtitle={$t('admin.oauth_settings_description')}
         >
-          <div class="ml-4 mt-4 flex flex-col gap-4">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
             <p class="text-sm dark:text-immich-dark-fg">
               <FormatMessage key="admin.oauth_settings_more_details">
                 {#snippet children({ message })}
@@ -243,8 +243,8 @@
           title={$t('admin.password_settings')}
           subtitle={$t('admin.password_settings_description')}
         >
-          <div class="ml-4 mt-4 flex flex-col gap-4">
-            <div class="ml-4 mt-4 flex flex-col">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
+            <div class="ms-4 mt-4 flex flex-col">
               <SettingSwitch
                 title={$t('admin.password_enable_description')}
                 {disabled}
diff --git a/web/src/lib/components/admin-page/settings/backup-settings/backup-settings.svelte b/web/src/lib/components/admin-page/settings/backup-settings/backup-settings.svelte
index 3ec477e29c..61f4cafe87 100644
--- a/web/src/lib/components/admin-page/settings/backup-settings/backup-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/backup-settings/backup-settings.svelte
@@ -37,7 +37,7 @@
 <div>
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
         <SettingSwitch
           title={$t('admin.backup_database_enable_description')}
           {disabled}
diff --git a/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte b/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte
index 58c4be0ca0..bb9b41f8de 100644
--- a/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte
@@ -43,7 +43,7 @@
 <div>
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
         <p class="text-sm dark:text-immich-dark-fg">
           <Icon path={mdiHelpCircleOutline} class="inline" size="15" />
           <FormatMessage key="admin.transcoding_codecs_learn_more">
@@ -70,7 +70,7 @@
           title={$t('admin.transcoding_policy')}
           subtitle={$t('admin.transcoding_policy_description')}
         >
-          <div class="ml-4 mt-4 flex flex-col gap-4">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
             <SettingSelect
               label={$t('admin.transcoding_transcode_policy')}
               {disabled}
@@ -159,7 +159,7 @@
           title={$t('admin.transcoding_encoding_options')}
           subtitle={$t('admin.transcoding_encoding_options_description')}
         >
-          <div class="ml-4 mt-4 flex flex-col gap-4">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
             <SettingSelect
               label={$t('admin.transcoding_video_codec')}
               {disabled}
@@ -302,7 +302,7 @@
           title={$t('admin.transcoding_hardware_acceleration')}
           subtitle={$t('admin.transcoding_hardware_acceleration_description')}
         >
-          <div class="ml-4 mt-4 flex flex-col gap-4">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
             <SettingSelect
               label={$t('admin.transcoding_acceleration_api')}
               {disabled}
@@ -376,7 +376,7 @@
           title={$t('advanced')}
           subtitle={$t('admin.transcoding_advanced_options_description')}
         >
-          <div class="ml-4 mt-4 flex flex-col gap-4">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
             <SettingInputField
               inputType={SettingInputFieldType.NUMBER}
               label={$t('admin.transcoding_max_b_frames')}
@@ -407,7 +407,7 @@
         </SettingAccordion>
       </div>
 
-      <div class="ml-4">
+      <div class="ms-4">
         <SettingButtonsRow
           onReset={(options) => onReset({ ...options, configKeys: ['ffmpeg'] })}
           onSave={() => onSave({ ffmpeg: config.ffmpeg })}
diff --git a/web/src/lib/components/admin-page/settings/image/image-settings.svelte b/web/src/lib/components/admin-page/settings/image/image-settings.svelte
index 9a66ad9c97..9a32e8e4e0 100644
--- a/web/src/lib/components/admin-page/settings/image/image-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/image/image-settings.svelte
@@ -40,7 +40,7 @@
 <div>
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4">
+      <div class="ms-4 mt-4">
         <SettingAccordion
           key="thumbnail-settings"
           title={$t('admin.image_thumbnail_title')}
@@ -195,7 +195,7 @@
         </div>
       </div>
 
-      <div class="ml-4 mt-4">
+      <div class="ms-4 mt-4">
         <SettingButtonsRow
           onReset={(options) => onReset({ ...options, configKeys: ['image'] })}
           onSave={() => onSave({ image: config.image })}
diff --git a/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte b/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte
index 5a95dbea30..e9f54e7ee8 100644
--- a/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/job-settings/job-settings.svelte
@@ -47,7 +47,7 @@
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
       {#each jobNames as jobName (jobName)}
-        <div class="ml-4 mt-4 flex flex-col gap-4">
+        <div class="ms-4 mt-4 flex flex-col gap-4">
           {#if isSystemConfigJobDto(jobName)}
             <SettingInputField
               inputType={SettingInputFieldType.NUMBER}
@@ -71,7 +71,7 @@
         </div>
       {/each}
 
-      <div class="ml-4">
+      <div class="ms-4">
         <SettingButtonsRow
           onReset={(options) => onReset({ ...options, configKeys: ['job'] })}
           onSave={() => onSave({ job: config.job })}
diff --git a/web/src/lib/components/admin-page/settings/library-settings/library-settings.svelte b/web/src/lib/components/admin-page/settings/library-settings/library-settings.svelte
index b1012c0287..390b167a54 100644
--- a/web/src/lib/components/admin-page/settings/library-settings/library-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/library-settings/library-settings.svelte
@@ -47,14 +47,14 @@
 <div>
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
         <SettingAccordion
           key="library-watching"
           title={$t('admin.library_watching_settings')}
           subtitle={$t('admin.library_watching_settings_description')}
           isOpen={openByDefault}
         >
-          <div class="ml-4 mt-4 flex flex-col gap-4">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
             <SettingSwitch
               title={$t('admin.library_watching_enable_description')}
               {disabled}
@@ -69,7 +69,7 @@
           subtitle={$t('admin.library_scanning_description')}
           isOpen={openByDefault}
         >
-          <div class="ml-4 mt-4 flex flex-col gap-4">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
             <SettingSwitch
               title={$t('admin.library_scanning_enable_description')}
               {disabled}
diff --git a/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte b/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte
index 29a1c65162..5538183442 100644
--- a/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/logging-settings/logging-settings.svelte
@@ -27,7 +27,7 @@
 <div>
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
         <SettingSwitch
           title={$t('admin.logging_enable_description')}
           {disabled}
diff --git a/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte b/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte
index 59bb798a0b..cd2684a3e3 100644
--- a/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/machine-learning-settings/machine-learning-settings.svelte
@@ -51,7 +51,7 @@
               {#if config.machineLearning.urls.length > 1}
                 <CircleIconButton
                   size="24"
-                  class="ml-2"
+                  class="ms-2"
                   padding="2"
                   color="red"
                   title=""
@@ -88,7 +88,7 @@
         title={$t('admin.machine_learning_smart_search')}
         subtitle={$t('admin.machine_learning_smart_search_description')}
       >
-        <div class="ml-4 mt-4 flex flex-col gap-4">
+        <div class="ms-4 mt-4 flex flex-col gap-4">
           <SettingSwitch
             title={$t('admin.machine_learning_smart_search_enabled')}
             subtitle={$t('admin.machine_learning_smart_search_enabled_description')}
@@ -124,7 +124,7 @@
         title={$t('admin.machine_learning_duplicate_detection')}
         subtitle={$t('admin.machine_learning_duplicate_detection_setting_description')}
       >
-        <div class="ml-4 mt-4 flex flex-col gap-4">
+        <div class="ms-4 mt-4 flex flex-col gap-4">
           <SettingSwitch
             title={$t('admin.machine_learning_duplicate_detection_enabled')}
             subtitle={$t('admin.machine_learning_duplicate_detection_enabled_description')}
@@ -154,7 +154,7 @@
         title={$t('admin.machine_learning_facial_recognition')}
         subtitle={$t('admin.machine_learning_facial_recognition_description')}
       >
-        <div class="ml-4 mt-4 flex flex-col gap-4">
+        <div class="ms-4 mt-4 flex flex-col gap-4">
           <SettingSwitch
             title={$t('admin.machine_learning_facial_recognition_setting')}
             subtitle={$t('admin.machine_learning_facial_recognition_setting_description')}
diff --git a/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte b/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte
index 4a4b23ded2..7046196c31 100644
--- a/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/map-settings/map-settings.svelte
@@ -32,7 +32,7 @@
     <form autocomplete="off" {onsubmit}>
       <div class="flex flex-col gap-4">
         <SettingAccordion key="map" title={$t('admin.map_settings')} subtitle={$t('admin.map_settings_description')}>
-          <div class="ml-4 mt-4 flex flex-col gap-4">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
             <SettingSwitch
               title={$t('admin.map_enable_description')}
               subtitle={$t('admin.map_implications')}
@@ -78,7 +78,7 @@
               </FormatMessage>
             </p>
           {/snippet}
-          <div class="ml-4 mt-4 flex flex-col gap-4">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
             <SettingSwitch
               title={$t('admin.map_reverse_geocoding_enable_description')}
               {disabled}
diff --git a/web/src/lib/components/admin-page/settings/metadata-settings/metadata-settings.svelte b/web/src/lib/components/admin-page/settings/metadata-settings/metadata-settings.svelte
index 1ba82b2eb9..c5d3860ecd 100644
--- a/web/src/lib/components/admin-page/settings/metadata-settings/metadata-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/metadata-settings/metadata-settings.svelte
@@ -26,7 +26,7 @@
 <div class="mt-2">
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit} class="mx-4 mt-4">
-      <div class="ml-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
         <SettingSwitch
           title={$t('admin.metadata_faces_import_setting')}
           subtitle={$t('admin.metadata_faces_import_setting_description')}
diff --git a/web/src/lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte b/web/src/lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte
index 1a6f0ab866..eb032a6115 100644
--- a/web/src/lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/new-version-check-settings/new-version-check-settings.svelte
@@ -26,7 +26,7 @@
 <div>
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4">
+      <div class="ms-4 mt-4">
         <SettingSwitch
           title={$t('admin.version_check_enabled_description')}
           subtitle={$t('admin.version_check_implications')}
diff --git a/web/src/lib/components/admin-page/settings/notification-settings/notification-settings.svelte b/web/src/lib/components/admin-page/settings/notification-settings/notification-settings.svelte
index 24e672607d..e3bd413900 100644
--- a/web/src/lib/components/admin-page/settings/notification-settings/notification-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/notification-settings/notification-settings.svelte
@@ -80,7 +80,7 @@
     <form autocomplete="off" {onsubmit} class="mt-4">
       <div class="flex flex-col gap-4">
         <SettingAccordion key="email" title={$t('email')} subtitle={$t('admin.notification_email_setting_description')}>
-          <div class="ml-4 mt-4 flex flex-col gap-4">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
             <SettingSwitch
               title={$t('admin.notification_enable_email_notifications')}
               {disabled}
diff --git a/web/src/lib/components/admin-page/settings/server/server-settings.svelte b/web/src/lib/components/admin-page/settings/server/server-settings.svelte
index b9134d1e50..8a1ff807f3 100644
--- a/web/src/lib/components/admin-page/settings/server/server-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/server/server-settings.svelte
@@ -28,7 +28,7 @@
 <div>
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="mt-4 ml-4">
+      <div class="mt-4 ms-4">
         <SettingInputField
           inputType={SettingInputFieldType.TEXT}
           label={$t('admin.server_external_domain_settings')}
@@ -52,7 +52,7 @@
           bind:checked={config.server.publicUsers}
         />
 
-        <div class="ml-4">
+        <div class="ms-4">
           <SettingButtonsRow
             onReset={(options) => onReset({ ...options, configKeys: ['server'] })}
             onSave={() => onSave({ server: config.server })}
diff --git a/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte b/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte
index 67299d8f6b..efc42bf8b7 100644
--- a/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/storage-template/storage-template-settings.svelte
@@ -141,7 +141,7 @@
     </p>
   </div>
   {#await getTemplateOptions() then}
-    <div id="directory-path-builder" class="flex flex-col gap-4 {minified ? '' : 'ml-4 mt-4'}">
+    <div id="directory-path-builder" class="flex flex-col gap-4 {minified ? '' : 'ms-4 mt-4'}">
       <SettingSwitch
         title={$t('admin.storage_template_enable_description')}
         {disabled}
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 06d8196935..a294eb1768 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
@@ -76,7 +76,7 @@
           title={$t('admin.template_email_settings')}
           subtitle={$t('admin.template_settings_description')}
         >
-          <div class="ml-4 mt-4 flex flex-col gap-4">
+          <div class="ms-4 mt-4 flex flex-col gap-4">
             <p class="text-sm dark:text-immich-dark-fg">
               <FormatMessage key="admin.template_email_if_empty">
                 {$t('admin.template_email_if_empty')}
@@ -102,7 +102,7 @@
                   onclick={() => getTemplate(templateName, config.templates.email[templateKey])}
                   title={$t('admin.template_email_preview')}
                 >
-                  <Icon path={mdiEyeOutline} class="mr-1" />
+                  <Icon path={mdiEyeOutline} class="me-1" />
                   {$t('admin.template_email_preview')}
                 </Button>
               </div>
diff --git a/web/src/lib/components/admin-page/settings/theme/theme-settings.svelte b/web/src/lib/components/admin-page/settings/theme/theme-settings.svelte
index 79b5f838e3..64b4b92b5e 100644
--- a/web/src/lib/components/admin-page/settings/theme/theme-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/theme/theme-settings.svelte
@@ -26,7 +26,7 @@
 <div>
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
         <SettingTextarea
           {disabled}
           label={$t('admin.theme_custom_css_settings')}
diff --git a/web/src/lib/components/admin-page/settings/trash-settings/trash-settings.svelte b/web/src/lib/components/admin-page/settings/trash-settings/trash-settings.svelte
index 05979bf9f0..f0f18f5d95 100644
--- a/web/src/lib/components/admin-page/settings/trash-settings/trash-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/trash-settings/trash-settings.svelte
@@ -28,7 +28,7 @@
 <div>
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
         <SettingSwitch title={$t('admin.trash_enabled_description')} {disabled} bind:checked={config.trash.enabled} />
 
         <hr />
diff --git a/web/src/lib/components/admin-page/settings/user-settings/user-settings.svelte b/web/src/lib/components/admin-page/settings/user-settings/user-settings.svelte
index f96c3808a8..422e1ebe49 100644
--- a/web/src/lib/components/admin-page/settings/user-settings/user-settings.svelte
+++ b/web/src/lib/components/admin-page/settings/user-settings/user-settings.svelte
@@ -24,7 +24,7 @@
 <div>
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" onsubmit={(e) => e.preventDefault()}>
-      <div class="ml-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
         <SettingInputField
           inputType={SettingInputFieldType.NUMBER}
           min={1}
@@ -35,7 +35,7 @@
         />
       </div>
 
-      <div class="ml-4">
+      <div class="ms-4">
         <SettingButtonsRow
           onReset={(options) => onReset({ ...options, configKeys: ['user'] })}
           onSave={() => onSave({ user: config.user })}
diff --git a/web/src/lib/components/album-page/album-card-group.svelte b/web/src/lib/components/album-page/album-card-group.svelte
index 9b2aa11552..3556d9fea4 100644
--- a/web/src/lib/components/album-page/album-card-group.svelte
+++ b/web/src/lib/components/album-page/album-card-group.svelte
@@ -48,7 +48,7 @@
     <button
       type="button"
       onclick={() => toggleAlbumGroupCollapsing(group.id)}
-      class="w-full text-left mt-2 pt-2 pr-2 pb-2 rounded-md transition-colors cursor-pointer dark:text-immich-dark-fg hover:text-immich-primary dark:hover:text-immich-dark-primary hover:bg-immich-gray dark:hover:bg-immich-dark-gray"
+      class="w-full text-start mt-2 pt-2 pe-2 pb-2 rounded-md transition-colors cursor-pointer dark:text-immich-dark-fg hover:text-immich-primary dark:hover:text-immich-dark-primary hover:bg-immich-gray dark:hover:bg-immich-dark-gray"
       aria-expanded={!isCollapsed}
     >
       <Icon
@@ -57,7 +57,7 @@
         class="inline-block -mt-2.5 transition-all duration-[250ms] {iconRotation}"
       />
       <span class="font-bold text-3xl text-black dark:text-white">{group.name}</span>
-      <span class="ml-1.5">({$t('albums_count', { values: { count: albums.length } })})</span>
+      <span class="ms-1.5">({$t('albums_count', { values: { count: albums.length } })})</span>
     </button>
     <hr class="dark:border-immich-dark-gray" />
   </div>
diff --git a/web/src/lib/components/album-page/album-card.svelte b/web/src/lib/components/album-page/album-card.svelte
index cec4919e4e..06ec030bea 100644
--- a/web/src/lib/components/album-page/album-card.svelte
+++ b/web/src/lib/components/album-page/album-card.svelte
@@ -40,7 +40,7 @@
   {#if onShowContextMenu}
     <div
       id="icon-{album.id}"
-      class="absolute right-6 top-6 z-10 opacity-0 group-hover:opacity-100 focus-within:opacity-100"
+      class="absolute end-6 top-6 z-10 opacity-0 group-hover:opacity-100 focus-within:opacity-100"
       data-testid="context-button-parent"
     >
       <CircleIconButton
diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte
index ea9e7e3dd1..09ec67e92b 100644
--- a/web/src/lib/components/album-page/album-viewer.svelte
+++ b/web/src/lib/components/album-page/album-viewer.svelte
@@ -117,7 +117,7 @@
       <!-- ALBUM DESCRIPTION -->
       {#if album.description}
         <p
-          class="whitespace-pre-line mb-12 mt-6 w-full pb-2 text-left font-medium text-base text-black dark:text-gray-300"
+          class="whitespace-pre-line mb-12 mt-6 w-full pb-2 text-start font-medium text-base text-black dark:text-gray-300"
         >
           {album.description}
         </p>
diff --git a/web/src/lib/components/album-page/albums-table-row.svelte b/web/src/lib/components/album-page/albums-table-row.svelte
index c900930f8a..034ed62010 100644
--- a/web/src/lib/components/album-page/albums-table-row.svelte
+++ b/web/src/lib/components/album-page/albums-table-row.svelte
@@ -35,13 +35,13 @@
   onclick={() => goto(`${AppRoute.ALBUMS}/${album.id}`)}
   {oncontextmenu}
 >
-  <td class="text-md text-ellipsis text-left w-8/12 sm:w-4/12 md:w-4/12 xl:w-[30%] 2xl:w-[40%] items-center">
+  <td class="text-md text-ellipsis text-start w-8/12 sm:w-4/12 md:w-4/12 xl:w-[30%] 2xl:w-[40%] items-center">
     {album.albumName}
     {#if album.shared}
       <Icon
         path={mdiShareVariantOutline}
         size="16"
-        class="inline ml-1 opacity-70"
+        class="inline ms-1 opacity-70"
         title={album.ownerId === $user.id
           ? $t('shared_by_you')
           : $t('shared_by_user', { values: { user: album.owner.name } })}
diff --git a/web/src/lib/components/album-page/albums-table.svelte b/web/src/lib/components/album-page/albums-table.svelte
index bd7c7fd7f5..9f51f9a19a 100644
--- a/web/src/lib/components/album-page/albums-table.svelte
+++ b/web/src/lib/components/album-page/albums-table.svelte
@@ -24,7 +24,7 @@
   let { groupedAlbums, albumGroupOption = AlbumGroupBy.None, onShowContextMenu }: Props = $props();
 </script>
 
-<table class="mt-2 w-full text-left">
+<table class="mt-2 w-full text-start">
   <thead
     class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
   >
@@ -48,18 +48,18 @@
         class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray dark:text-immich-dark-fg"
       >
         <tr
-          class="flex w-full place-items-center p-2 md:pl-5 md:pr-5 md:pt-3 md:pb-3"
+          class="flex w-full place-items-center p-2 md:ps-5 md:pe-5 md:pt-3 md:pb-3"
           onclick={() => toggleAlbumGroupCollapsing(albumGroup.id)}
           aria-expanded={!isCollapsed}
         >
-          <td class="text-md text-left -mb-1">
+          <td class="text-md text-start -mb-1">
             <Icon
               path={mdiChevronRight}
               size="20"
               class="inline-block -mt-2 transition-all duration-[250ms] {iconRotation}"
             />
             <span class="font-bold text-2xl">{albumGroup.name}</span>
-            <span class="ml-1.5">
+            <span class="ms-1.5">
               ({$t('albums_count', { values: { count: albumGroup.albums.length } })})
             </span>
           </td>
diff --git a/web/src/lib/components/album-page/user-selection-modal.svelte b/web/src/lib/components/album-page/user-selection-modal.svelte
index 1496c1ce66..9ee7cc550d 100644
--- a/web/src/lib/components/album-page/user-selection-modal.svelte
+++ b/web/src/lib/components/album-page/user-selection-modal.svelte
@@ -94,7 +94,7 @@
                 </div>
 
                 <!-- <UserAvatar {user} size="md" /> -->
-                <div class="text-left flex-grow">
+                <div class="text-start flex-grow">
                   <p class="text-immich-fg dark:text-immich-dark-fg">
                     {user.name}
                   </p>
@@ -136,7 +136,7 @@
                   class="flex w-full place-items-center gap-4 p-4"
                 >
                   <UserAvatar {user} size="md" />
-                  <div class="text-left flex-grow">
+                  <div class="text-start flex-grow">
                     <p class="text-immich-fg dark:text-immich-dark-fg">
                       {user.name}
                     </p>
diff --git a/web/src/lib/components/asset-viewer/activity-viewer.svelte b/web/src/lib/components/asset-viewer/activity-viewer.svelte
index caa1ced290..94b66d4c22 100644
--- a/web/src/lib/components/asset-viewer/activity-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/activity-viewer.svelte
@@ -186,7 +186,7 @@
       >
         {#each reactions as reaction, index (reaction.id)}
           {#if reaction.type === ReactionType.Comment}
-            <div class="flex dark:bg-gray-800 bg-gray-200 py-3 pl-3 mt-3 rounded-lg gap-4 justify-start">
+            <div class="flex dark:bg-gray-800 bg-gray-200 py-3 ps-3 mt-3 rounded-lg gap-4 justify-start">
               <div class="flex items-center">
                 <UserAvatar user={reaction.user} size="sm" />
               </div>
@@ -202,7 +202,7 @@
                 </a>
               {/if}
               {#if reaction.user.id === user.id || albumOwnerId === user.id}
-                <div class="mr-4">
+                <div class="me-4">
                   <ButtonContextMenu
                     icon={mdiDotsVertical}
                     title={$t('comment_options')}
@@ -231,7 +231,7 @@
             {/if}
           {:else if reaction.type === ReactionType.Like}
             <div class="relative">
-              <div class="flex py-3 pl-3 mt-3 gap-4 items-center text-sm">
+              <div class="flex py-3 ps-3 mt-3 gap-4 items-center text-sm">
                 <div class="text-red-600"><Icon path={mdiHeart} size={20} /></div>
 
                 <div class="w-full" title={`${reaction.user.name} (${reaction.user.email})`}>
@@ -255,7 +255,7 @@
                   </a>
                 {/if}
                 {#if reaction.user.id === user.id || albumOwnerId === user.id}
-                  <div class="mr-4">
+                  <div class="me-4">
                     <ButtonContextMenu
                       icon={mdiDotsVertical}
                       title={$t('reaction_options')}
@@ -307,17 +307,17 @@
               }}
               class="h-[18px] {disabled
                 ? 'cursor-not-allowed'
-                : ''} w-full max-h-56 pr-2 items-center overflow-y-auto leading-4 outline-none resize-none bg-gray-200"
+                : ''} w-full max-h-56 pe-2 items-center overflow-y-auto leading-4 outline-none resize-none bg-gray-200"
             ></textarea>
           </div>
           {#if isSendingMessage}
-            <div class="flex items-end place-items-center pb-2 ml-0">
+            <div class="flex items-end place-items-center pb-2 ms-0">
               <div class="flex w-full place-items-center">
                 <LoadingSpinner />
               </div>
             </div>
           {:else if message}
-            <div class="flex items-end w-fit ml-0">
+            <div class="flex items-end w-fit ms-0">
               <CircleIconButton
                 title={$t('send_message')}
                 size="15"
diff --git a/web/src/lib/components/asset-viewer/album-list-item.svelte b/web/src/lib/components/asset-viewer/album-list-item.svelte
index 1d7ee2971a..6c50ecbc1d 100644
--- a/web/src/lib/components/asset-viewer/album-list-item.svelte
+++ b/web/src/lib/components/asset-viewer/album-list-item.svelte
@@ -43,7 +43,7 @@
   type="button"
   onclick={onAlbumClick}
   use:scrollIntoViewIfSelected
-  class="flex w-full gap-4 px-6 py-2 text-left transition-colors hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl"
+  class="flex w-full gap-4 px-6 py-2 text-start transition-colors hover:bg-gray-200 dark:hover:bg-gray-700 rounded-xl"
   class:bg-gray-200={selected}
   class:dark:bg-gray-700={selected}
 >
diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte
index 91461d574d..98bc087f71 100644
--- a/web/src/lib/components/asset-viewer/asset-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte
@@ -422,7 +422,7 @@
 
 <section
   id="immich-asset-viewer"
-  class="fixed left-0 top-0 z-[1001] grid size-full grid-cols-4 grid-rows-[64px_1fr] overflow-hidden bg-black"
+  class="fixed start-0 top-0 z-[1001] grid size-full grid-cols-4 grid-rows-[64px_1fr] overflow-hidden bg-black"
   use:focusTrap
 >
   <!-- Top navigation bar -->
@@ -547,7 +547,7 @@
           />
         {/if}
         {#if $slideshowState === SlideshowState.None && isShared && ((album && album.isActivityEnabled) || numberOfComments > 0)}
-          <div class="z-[9999] absolute bottom-0 right-0 mb-20 mr-8">
+          <div class="z-[9999] absolute bottom-0 end-0 mb-20 me-8">
             <ActivityStatus
               disabled={!album?.isActivityEnabled}
               {isLiked}
@@ -571,7 +571,7 @@
     <div
       transition:fly={{ duration: 150 }}
       id="detail-panel"
-      class="z-[1002] row-start-1 row-span-4 w-[360px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg"
+      class="z-[1002] row-start-1 row-span-4 w-[360px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-s-immich-dark-gray dark:bg-immich-dark-bg"
       translate="yes"
     >
       <DetailPanel {asset} currentAlbum={album} albums={appearsInAlbums} onClose={() => ($isShowDetail = false)} />
@@ -582,7 +582,7 @@
     <div
       transition:fly={{ duration: 150 }}
       id="editor-panel"
-      class="z-[1002] row-start-1 row-span-4 w-[400px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg"
+      class="z-[1002] row-start-1 row-span-4 w-[400px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-s-immich-dark-gray dark:bg-immich-dark-bg"
       translate="yes"
     >
       <EditorPanel {asset} onUpdateSelectedType={handleUpdateSelectedEditType} onClose={closeEditor} />
@@ -631,7 +631,7 @@
     <div
       transition:fly={{ duration: 150 }}
       id="activity-panel"
-      class="z-[1002] row-start-1 row-span-5 w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg"
+      class="z-[1002] row-start-1 row-span-5 w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-s-immich-dark-gray dark:bg-immich-dark-bg"
       translate="yes"
     >
       <ActivityViewer
diff --git a/web/src/lib/components/asset-viewer/detail-panel-location.svelte b/web/src/lib/components/asset-viewer/detail-panel-location.svelte
index 9e59243aa1..42cbefadf1 100644
--- a/web/src/lib/components/asset-viewer/detail-panel-location.svelte
+++ b/web/src/lib/components/asset-viewer/detail-panel-location.svelte
@@ -33,7 +33,7 @@
 {#if asset.exifInfo?.country}
   <button
     type="button"
-    class="flex w-full text-left justify-between place-items-start gap-4 py-4"
+    class="flex w-full text-start justify-between place-items-start gap-4 py-4"
     onclick={() => (isOwner ? (isShowChangeLocation = true) : null)}
     title={isOwner ? $t('edit_location') : ''}
     class:hover:dark:text-immich-dark-primary={isOwner}
@@ -68,7 +68,7 @@
 {:else if !asset.exifInfo?.city && isOwner}
   <button
     type="button"
-    class="flex w-full text-left justify-between place-items-start gap-4 py-4 rounded-lg hover:dark:text-immich-dark-primary hover:text-immich-primary"
+    class="flex w-full text-start justify-between place-items-start gap-4 py-4 rounded-lg hover:dark:text-immich-dark-primary hover:text-immich-primary"
     onclick={() => (isShowChangeLocation = true)}
     title={$t('add_location')}
   >
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 39ca096efd..592279e353 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 pl-3 pr-1 group-hover:pl-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-tl-full rounded-bl-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 roudned-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">
@@ -60,7 +60,7 @@
 
           <button
             type="button"
-            class="text-gray-100 dark:text-immich-dark-gray bg-immich-primary/95 dark:bg-immich-dark-primary/95 rounded-tr-full rounded-br-full place-items-center place-content-center pr-2 pl-1 py-1 hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
+            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(tag.id)}
           >
diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte
index b96a4660ee..5ef0ac0d73 100644
--- a/web/src/lib/components/asset-viewer/detail-panel.svelte
+++ b/web/src/lib/components/asset-viewer/detail-panel.svelte
@@ -296,7 +296,7 @@
     {#if dateTime}
       <button
         type="button"
-        class="flex w-full text-left justify-between place-items-start gap-4 py-4"
+        class="flex w-full text-start justify-between place-items-start gap-4 py-4"
         onclick={() => (isOwner ? (isShowChangeDate = true) : null)}
         title={isOwner ? $t('edit_date') : ''}
         class:hover:dark:text-immich-dark-primary={isOwner}
diff --git a/web/src/lib/components/asset-viewer/download-panel.svelte b/web/src/lib/components/asset-viewer/download-panel.svelte
index 80a14a5ac3..27e48b0d28 100644
--- a/web/src/lib/components/asset-viewer/download-panel.svelte
+++ b/web/src/lib/components/asset-viewer/download-panel.svelte
@@ -16,14 +16,14 @@
 {#if downloadStore.isDownloading}
   <div
     transition:fly={{ x: -100, duration: 350 }}
-    class="fixed bottom-10 left-2 z-[10000] max-h-[270px] w-[315px] rounded-2xl border bg-immich-bg p-4 text-sm shadow-sm"
+    class="fixed bottom-10 start-2 z-[10000] max-h-[270px] w-[315px] rounded-2xl border bg-immich-bg p-4 text-sm shadow-sm"
   >
     <p class="mb-2 text-xs text-gray-500">{$t('downloading').toUpperCase()}</p>
     <div class="my-2 mb-2 flex max-h-[200px] flex-col overflow-y-auto text-sm">
       {#each Object.keys(downloadStore.assets) as downloadKey (downloadKey)}
         {@const download = downloadStore.assets[downloadKey]}
         <div class="mb-2 flex place-items-center" transition:slide>
-          <div class="w-full pr-10">
+          <div class="w-full pe-10">
             <div class="flex place-items-center justify-between gap-2 text-xs font-medium">
               <p class="truncate">■ {downloadKey}</p>
               {#if download.total}
@@ -41,7 +41,7 @@
               </p>
             </div>
           </div>
-          <div class="absolute right-2">
+          <div class="absolute end-2">
             <CircleIconButton
               title={$t('close')}
               onclick={() => abort(downloadKey, download)}
diff --git a/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte b/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte
index c50c21bef8..5c50f230cc 100644
--- a/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte
+++ b/web/src/lib/components/asset-viewer/face-editor/face-editor.svelte
@@ -308,13 +308,13 @@
   };
 </script>
 
-<div class="absolute left-0 top-0">
-  <canvas bind:this={canvasEl} id="face-editor" class="absolute top-0 left-0"></canvas>
+<div class="absolute start-0 top-0">
+  <canvas bind:this={canvasEl} id="face-editor" class="absolute top-0 start-0"></canvas>
 
   <div
     id="face-selector"
     bind:this={faceSelectorEl}
-    class="absolute top-[calc(50%-250px)] left-[calc(50%-125px)] max-w-[250px] w-[250px] bg-white dark:bg-immich-dark-gray dark:text-immich-dark-fg backdrop-blur-sm px-2 py-4 rounded-xl border border-gray-200 dark:border-gray-800"
+    class="absolute top-[calc(50%-250px)] start-[calc(50%-125px)] max-w-[250px] w-[250px] bg-white dark:bg-immich-dark-gray dark:text-immich-dark-fg backdrop-blur-sm px-2 py-4 rounded-xl border border-gray-200 dark:border-gray-800"
   >
     <p class="text-center text-sm">Select a person to tag</p>
 
@@ -329,7 +329,7 @@
             <button
               onclick={() => tagFace(person)}
               type="button"
-              class="w-full flex place-items-center gap-2 rounded-lg pl-1 pr-4 py-2 hover:bg-immich-primary/25"
+              class="w-full flex place-items-center gap-2 rounded-lg ps-1 pe-4 py-2 hover:bg-immich-primary/25"
             >
               <ImageThumbnail
                 curve
diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte
index d3a9da3633..fdb986786e 100644
--- a/web/src/lib/components/asset-viewer/photo-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte
@@ -213,7 +213,7 @@
         <img
           src={assetFileUrl}
           alt={$getAltText(asset)}
-          class="absolute top-0 left-0 -z-10 object-cover h-full w-full blur-lg"
+          class="absolute top-0 start-0 -z-10 object-cover h-full w-full blur-lg"
           draggable="false"
         />
       {/if}
diff --git a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte
index 55357abbc0..2e8ad6ca32 100644
--- a/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte
+++ b/web/src/lib/components/assets/thumbnail/image-thumbnail.svelte
@@ -101,7 +101,7 @@
 {/if}
 
 {#if hidden}
-  <div class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform">
+  <div class="absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform">
     <Icon {title} path={mdiEyeOffOutline} size="2em" class={hiddenIconClass} />
   </div>
 {/if}
diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte
index c21acd8f86..eba10317aa 100644
--- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte
+++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte
@@ -332,20 +332,20 @@
 
         <!-- Favorite asset star -->
         {#if !isSharedLink() && asset.isFavorite}
-          <div class="absolute bottom-2 left-2 z-10">
+          <div class="absolute bottom-2 start-2 z-10">
             <Icon path={mdiHeart} size="24" class="text-white" />
           </div>
         {/if}
 
         {#if !isSharedLink() && showArchiveIcon && asset.isArchived}
-          <div class={['absolute left-2 z-10', asset.isFavorite ? 'bottom-10' : 'bottom-2']}>
+          <div class={['absolute start-2 z-10', asset.isFavorite ? 'bottom-10' : 'bottom-2']}>
             <Icon path={mdiArchiveArrowDownOutline} size="24" class="text-white" />
           </div>
         {/if}
 
         {#if asset.type === AssetTypeEnum.Image && asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR}
-          <div class="absolute right-0 top-0 z-10 flex place-items-center gap-1 text-xs font-medium text-white">
-            <span class="pr-2 pt-2">
+          <div class="absolute end-0 top-0 z-10 flex place-items-center gap-1 text-xs font-medium text-white">
+            <span class="pe-2 pt-2">
               <Icon path={mdiRotate360} size="24" />
             </span>
           </div>
@@ -356,10 +356,10 @@
           <div
             class={[
               'absolute z-10 flex place-items-center gap-1 text-xs font-medium text-white',
-              asset.type == AssetTypeEnum.Image && !asset.livePhotoVideoId ? 'top-0 right-0' : 'top-7 right-1',
+              asset.type == AssetTypeEnum.Image && !asset.livePhotoVideoId ? 'top-0 end-0' : 'top-7 end-1',
             ]}
           >
-            <span class="pr-2 pt-2 flex place-items-center gap-1">
+            <span class="pe-2 pt-2 flex place-items-center gap-1">
               <p>{asset.stack.assetCount.toLocaleString($locale)}</p>
               <Icon path={mdiCameraBurst} size="24" />
             </span>
diff --git a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte
index fc3cb2e951..9959c86548 100644
--- a/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte
+++ b/web/src/lib/components/assets/thumbnail/video-thumbnail.svelte
@@ -55,7 +55,7 @@
   };
 </script>
 
-<div class="absolute right-0 top-0 z-20 flex place-items-center gap-1 text-xs font-medium text-white">
+<div class="absolute end-0 top-0 z-20 flex place-items-center gap-1 text-xs font-medium text-white">
   {#if showTime}
     <span class="pt-2">
       {#if remainingSeconds < 60}
@@ -69,7 +69,7 @@
   {/if}
 
   <!-- svelte-ignore a11y_no_static_element_interactions -->
-  <span class="pr-2 pt-2" onmouseenter={onMouseEnter} onmouseleave={onMouseLeave}>
+  <span class="pe-2 pt-2" onmouseenter={onMouseEnter} onmouseleave={onMouseLeave}>
     {#if enablePlayback}
       {#if loading}
         <LoadingSpinner />
diff --git a/web/src/lib/components/elements/buttons/button.svelte b/web/src/lib/components/elements/buttons/button.svelte
index 991bbaecee..ac7d9808f3 100644
--- a/web/src/lib/components/elements/buttons/button.svelte
+++ b/web/src/lib/components/elements/buttons/button.svelte
@@ -79,7 +79,7 @@
   };
 
   const sizeClasses: Record<Size, string> = {
-    tiny: 'p-0 ml-2 mr-0 align-top',
+    tiny: 'p-0 ms-2 me-0 align-top',
     icon: 'p-2.5',
     link: 'p-2 font-medium',
     sm: 'px-4 py-2 text-sm font-medium',
diff --git a/web/src/lib/components/elements/buttons/skip-link.svelte b/web/src/lib/components/elements/buttons/skip-link.svelte
index a1a24634c4..40b1b9c526 100644
--- a/web/src/lib/components/elements/buttons/skip-link.svelte
+++ b/web/src/lib/components/elements/buttons/skip-link.svelte
@@ -50,7 +50,7 @@
   };
 </script>
 
-<div class="absolute z-50 top-2 left-2 transition-transform {isFocused ? 'translate-y-0' : '-translate-y-10 sr-only'}">
+<div class="absolute z-50 top-2 start-2 transition-transform {isFocused ? 'translate-y-0' : '-translate-y-10 sr-only'}">
   <Button
     size="sm"
     rounded="none"
diff --git a/web/src/lib/components/elements/dropdown.svelte b/web/src/lib/components/elements/dropdown.svelte
index 52360f04de..3966ea8310 100644
--- a/web/src/lib/components/elements/dropdown.svelte
+++ b/web/src/lib/components/elements/dropdown.svelte
@@ -82,10 +82,10 @@
   const getAlignClass = (position: 'bottom-left' | 'bottom-right') => {
     switch (position) {
       case 'bottom-left': {
-        return 'left-0';
+        return 'start-0';
       }
       case 'bottom-right': {
-        return 'right-0';
+        return 'end-0';
       }
 
       default: {
diff --git a/web/src/lib/components/faces-page/edit-name-input.svelte b/web/src/lib/components/faces-page/edit-name-input.svelte
index ebb44c4008..a5d0fdd757 100644
--- a/web/src/lib/components/faces-page/edit-name-input.svelte
+++ b/web/src/lib/components/faces-page/edit-name-input.svelte
@@ -35,7 +35,7 @@
     : 'rounded-lg'}  bg-gray-100 p-2 dark:bg-gray-700 border border-gray-200 dark:border-immich-dark-gray"
 >
   <ImageThumbnail circle shadow url={thumbnailData} altText={person.name} widthStyle="2rem" heightStyle="2rem" />
-  <form class="ml-4 flex w-full justify-between gap-16" autocomplete="off" {onsubmit}>
+  <form class="ms-4 flex w-full justify-between gap-16" autocomplete="off" {onsubmit}>
     <SearchPeople
       bind:searchName={name}
       bind:searchedPeopleLocal={suggestedPeople}
diff --git a/web/src/lib/components/faces-page/face-thumbnail.svelte b/web/src/lib/components/faces-page/face-thumbnail.svelte
index cc3fffe5d7..196777f0af 100644
--- a/web/src/lib/components/faces-page/face-thumbnail.svelte
+++ b/web/src/lib/components/faces-page/face-thumbnail.svelte
@@ -44,7 +44,7 @@
   </div>
 
   <div
-    class="absolute left-0 top-0 h-full w-full bg-immich-primary/30 opacity-0"
+    class="absolute start-0 top-0 h-full w-full bg-immich-primary/30 opacity-0"
     class:hover:opacity-100={selectable}
     class:rounded-full={circle}
     class:rounded-lg={!circle}
@@ -52,7 +52,7 @@
 
   {#if selected}
     <div
-      class="absolute left-0 top-0 h-full w-full bg-blue-500/80"
+      class="absolute start-0 top-0 h-full w-full bg-blue-500/80"
       class:rounded-full={circle}
       class:rounded-lg={!circle}
     ></div>
@@ -60,7 +60,7 @@
 
   {#if person.name}
     <span
-      class="w-100 text-white-shadow absolute bottom-2 left-0 w-full text-ellipsis px-1 text-center font-medium text-white hover:cursor-pointer"
+      class="w-100 text-white-shadow absolute bottom-2 start-0 w-full text-ellipsis px-1 text-center font-medium text-white hover:cursor-pointer"
     >
       {person.name}
     </span>
diff --git a/web/src/lib/components/faces-page/manage-people-visibility.svelte b/web/src/lib/components/faces-page/manage-people-visibility.svelte
index 0c2ed7bad0..c3c75d495b 100644
--- a/web/src/lib/components/faces-page/manage-people-visibility.svelte
+++ b/web/src/lib/components/faces-page/manage-people-visibility.svelte
@@ -117,12 +117,12 @@
   <div class="flex items-center">
     <CircleIconButton title={$t('close')} icon={mdiClose} onclick={onClose} />
     <div class="flex gap-2 items-center">
-      <p id={titleId} class="ml-2">{$t('show_and_hide_people')}</p>
+      <p id={titleId} class="ms-2">{$t('show_and_hide_people')}</p>
       <p class="text-sm text-gray-400 dark:text-gray-600">({totalPeopleCount.toLocaleString($locale)})</p>
     </div>
   </div>
   <div class="flex items-center justify-end">
-    <div class="flex items-center md:mr-4">
+    <div class="flex items-center md:me-4">
       <CircleIconButton title={$t('reset_people_visibility')} icon={mdiRestart} onclick={handleResetVisibility} />
       <CircleIconButton title={toggleButton.label} icon={toggleButton.icon} onclick={handleToggleVisibility} />
     </div>
@@ -154,7 +154,7 @@
           hiddenIconClass="text-white group-hover:text-black transition-colors"
         />
         {#if person.name}
-          <span class="absolute bottom-2 left-0 w-full select-text px-1 text-center font-medium text-white">
+          <span class="absolute bottom-2 start-0 w-full select-text px-1 text-center font-medium text-white">
             {person.name}
           </span>
         {/if}
diff --git a/web/src/lib/components/faces-page/merge-face-selector.svelte b/web/src/lib/components/faces-page/merge-face-selector.svelte
index 88576d2845..c2c1c65647 100644
--- a/web/src/lib/components/faces-page/merge-face-selector.svelte
+++ b/web/src/lib/components/faces-page/merge-face-selector.svelte
@@ -99,7 +99,7 @@
 
 <section
   transition:fly={{ y: 500, duration: 100, easing: quintOut }}
-  class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
+  class="absolute start-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
 >
   <ControlAppBar onClose={onBack}>
     {#snippet leading()}
@@ -113,7 +113,7 @@
     {#snippet trailing()}
       <Button size="sm" disabled={!hasSelection} onclick={handleMerge}>
         <Icon path={mdiMerge} size={18} />
-        <span class="ml-2">{$t('merge')}</span></Button
+        <span class="ms-2">{$t('merge')}</span></Button
       >
     {/snippet}
   </ControlAppBar>
diff --git a/web/src/lib/components/faces-page/people-card.svelte b/web/src/lib/components/faces-page/people-card.svelte
index 4aff4e96f8..b740953340 100644
--- a/web/src/lib/components/faces-page/people-card.svelte
+++ b/web/src/lib/components/faces-page/people-card.svelte
@@ -54,7 +54,7 @@
         circle
       />
       {#if person.isFavorite}
-        <div class="absolute top-4 left-4">
+        <div class="absolute top-4 start-4">
           <Icon path={mdiHeart} size="24" class="text-white" />
         </div>
       {/if}
@@ -62,7 +62,7 @@
   </a>
 
   {#if showVerticalDots}
-    <div class="absolute top-2 right-2">
+    <div class="absolute top-2 end-2">
       <ButtonContextMenu
         buttonClass="icon-white-drop-shadow focus:opacity-100 {showVerticalDots ? 'opacity-100' : 'opacity-0'}"
         color="opaque"
diff --git a/web/src/lib/components/faces-page/person-side-panel.svelte b/web/src/lib/components/faces-page/person-side-panel.svelte
index 73c3ea7ae5..5e8b35c74b 100644
--- a/web/src/lib/components/faces-page/person-side-panel.svelte
+++ b/web/src/lib/components/faces-page/person-side-panel.svelte
@@ -227,7 +227,7 @@
             <div
               role="button"
               tabindex={index}
-              class="absolute left-0 top-0 h-[90px] w-[90px] cursor-default"
+              class="absolute start-0 top-0 h-[90px] w-[90px] cursor-default"
               onfocus={() => ($boundingBoxesArray = [peopleWithFaces[index]])}
               onmouseover={() => ($boundingBoxesArray = [peopleWithFaces[index]])}
               onmouseleave={() => ($boundingBoxesArray = [])}
@@ -303,7 +303,7 @@
                 </p>
               {/if}
 
-              <div class="absolute -right-[5px] -top-[5px] h-[20px] w-[20px] rounded-full">
+              <div class="absolute -end-[5px] -top-[5px] h-[20px] w-[20px] rounded-full">
                 {#if selectedPersonToCreate[face.id] || selectedPersonToReassign[face.id]}
                   <CircleIconButton
                     color="primary"
@@ -311,7 +311,7 @@
                     title={$t('reset')}
                     size="18"
                     padding="1"
-                    class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
+                    class="absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
                     onclick={() => handleReset(face.id)}
                   />
                 {:else}
@@ -321,29 +321,29 @@
                     title={$t('select_new_face')}
                     size="18"
                     padding="1"
-                    class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
+                    class="absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
                     onclick={() => handleFacePicker(face)}
                   />
                 {/if}
               </div>
-              <div class="absolute right-[25px] -top-[5px] h-[20px] w-[20px] rounded-full">
+              <div class="absolute end-[25px] -top-[5px] h-[20px] w-[20px] rounded-full">
                 {#if !selectedPersonToCreate[face.id] && !selectedPersonToReassign[face.id] && !face.person}
                   <div
-                    class="flex place-content-center place-items-center rounded-full bg-[#d3d3d3] p-1 transition-all absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
+                    class="flex place-content-center place-items-center rounded-full bg-[#d3d3d3] p-1 transition-all absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
                   >
                     <Icon color="primary" path={mdiAccountOff} ariaHidden size="18" />
                   </div>
                 {/if}
               </div>
               {#if face.person != null}
-                <div class="absolute -right-[5px] top-[25px] h-[20px] w-[20px] rounded-full">
+                <div class="absolute -end-[5px] top-[25px] h-[20px] w-[20px] rounded-full">
                   <CircleIconButton
                     color="red"
                     icon={mdiTrashCan}
                     title={$t('delete_face')}
                     size="18"
                     padding="1"
-                    class="absolute left-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
+                    class="absolute start-1/2 top-1/2 translate-x-[-50%] translate-y-[-50%] transform"
                     onclick={() => deleteAssetFace(face)}
                   />
                 </div>
diff --git a/web/src/lib/components/faces-page/unmerge-face-selector.svelte b/web/src/lib/components/faces-page/unmerge-face-selector.svelte
index e808c98748..41c584d602 100644
--- a/web/src/lib/components/faces-page/unmerge-face-selector.svelte
+++ b/web/src/lib/components/faces-page/unmerge-face-selector.svelte
@@ -120,7 +120,7 @@
 
 <section
   transition:fly={{ y: 500, duration: 100, easing: quintOut }}
-  class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
+  class="absolute start-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
 >
   <ControlAppBar {onClose}>
     {#snippet leading()}
@@ -140,7 +140,7 @@
           {:else}
             <LoadingSpinner />
           {/if}
-          <span class="ml-2"> {$t('create_new_person')}</span></Button
+          <span class="ms-2"> {$t('create_new_person')}</span></Button
         >
         <Button
           size="sm"
@@ -155,7 +155,7 @@
           {:else}
             <LoadingSpinner />
           {/if}
-          <span class="ml-2"> {$t('reassign')}</span></Button
+          <span class="ms-2"> {$t('reassign')}</span></Button
         >
       </div>
     {/snippet}
diff --git a/web/src/lib/components/forms/library-import-paths-form.svelte b/web/src/lib/components/forms/library-import-paths-form.svelte
index 639b81071f..64c32532ef 100644
--- a/web/src/lib/components/forms/library-import-paths-form.svelte
+++ b/web/src/lib/components/forms/library-import-paths-form.svelte
@@ -173,7 +173,7 @@
 {/if}
 
 <form {onsubmit} autocomplete="off" class="m-4 flex flex-col gap-4">
-  <table class="text-left">
+  <table class="text-start">
     <tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
       {#each validatedPaths as validatedPath, listIndex (validatedPath.importPath)}
         <tr
@@ -183,7 +183,7 @@
               : 'bg-immich-bg dark:bg-immich-dark-gray/50'
           }`}
         >
-          <td class="w-1/8 text-ellipsis pl-8 text-sm">
+          <td class="w-1/8 text-ellipsis ps-8 text-sm">
             {#if validatedPath.isValid}
               <Icon
                 path={mdiCheckCircleOutline}
diff --git a/web/src/lib/components/forms/library-scan-settings-form.svelte b/web/src/lib/components/forms/library-scan-settings-form.svelte
index 0ce414c10d..7a021a1c0c 100644
--- a/web/src/lib/components/forms/library-scan-settings-form.svelte
+++ b/web/src/lib/components/forms/library-scan-settings-form.svelte
@@ -123,7 +123,7 @@
 {/if}
 
 <form {onsubmit} autocomplete="off" class="m-4 flex flex-col gap-4">
-  <table class="w-full text-left">
+  <table class="w-full text-start">
     <tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
       {#each exclusionPatterns as exclusionPattern, listIndex (exclusionPattern)}
         <tr
diff --git a/web/src/lib/components/forms/tag-asset-form.svelte b/web/src/lib/components/forms/tag-asset-form.svelte
index 9392b1538d..a7f95c03c0 100644
--- a/web/src/lib/components/forms/tag-asset-form.svelte
+++ b/web/src/lib/components/forms/tag-asset-form.svelte
@@ -72,7 +72,7 @@
       {#if tag}
         <div class="flex group transition-all">
           <span
-            class="inline-block h-min whitespace-nowrap pl-3 pr-1 group-hover:pl-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-tl-full rounded-bl-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 roudned-s-full hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
           >
             <p class="text-sm">
               {tag.value}
@@ -81,7 +81,7 @@
 
           <button
             type="button"
-            class="text-gray-100 dark:text-immich-dark-gray bg-immich-primary/95 dark:bg-immich-dark-primary/95 rounded-tr-full rounded-br-full place-items-center place-content-center pr-2 pl-1 py-1 hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
+            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)}
           >
diff --git a/web/src/lib/components/layouts/AuthPageLayout.svelte b/web/src/lib/components/layouts/AuthPageLayout.svelte
index a30202e468..870a741bd1 100644
--- a/web/src/lib/components/layouts/AuthPageLayout.svelte
+++ b/web/src/lib/components/layouts/AuthPageLayout.svelte
@@ -17,7 +17,7 @@
       alt="Immich logo"
     />
     <div
-      class="w-full h-[99%] absolute left-0 top-0 backdrop-blur-[200px] bg-transparent dark:bg-immich-dark-bg/20"
+      class="w-full h-[99%] absolute start-0 top-0 backdrop-blur-[200px] bg-transparent dark:bg-immich-dark-bg/20"
     ></div>
   </div>
 
diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte
index 45aaf85b67..8d33859fef 100644
--- a/web/src/lib/components/memory-page/memory-viewer.svelte
+++ b/web/src/lib/components/memory-page/memory-viewer.svelte
@@ -360,8 +360,8 @@
 
         {#each current.memory.assets as asset, index (asset.id)}
           <a class="relative w-full py-2" href={asHref(asset)} aria-label={$t('view')}>
-            <span class="absolute left-0 h-[2px] w-full bg-gray-500"></span>
-            <span class="absolute left-0 h-[2px] bg-white" style:width={`${toProgressPercentage(index)}%`}></span>
+            <span class="absolute start-0 h-[2px] w-full bg-gray-500"></span>
+            <span class="absolute start-0 h-[2px] bg-white" style:width={`${toProgressPercentage(index)}%`}></span>
           </a>
         {/each}
 
@@ -380,7 +380,7 @@
 
     {#if galleryInView}
       <div
-        class="fixed top-20 z-30 left-1/2 -translate-x-1/2 transition-opacity"
+        class="fixed top-20 z-30 start-1/2 -translate-x-1/2 transition-opacity"
         class:opacity-0={!galleryInView}
         class:opacity-100={galleryInView}
       >
@@ -396,7 +396,7 @@
     <!-- Viewer -->
     <section class="overflow-hidden pt-32 md:pt-20" bind:clientHeight={viewerHeight}>
       <div
-        class="ml-[-100%] box-border flex h-[calc(100vh_-_224px)] md:h-[calc(100vh_-_180px)] w-[300%] items-center justify-center gap-10 overflow-hidden"
+        class="ms-[-100%] box-border flex h-[calc(100vh_-_224px)] md:h-[calc(100vh_-_180px)] w-[300%] items-center justify-center gap-10 overflow-hidden"
       >
         <!-- PREVIOUS MEMORY -->
         <div class="h-1/2 w-[20vw] rounded-2xl {current.previousMemory ? 'opacity-25 hover:opacity-70' : 'opacity-0'}">
@@ -424,7 +424,7 @@
             {/if}
 
             {#if current.previousMemory}
-              <div class="absolute bottom-4 right-4 text-left text-white">
+              <div class="absolute bottom-4 end-4 text-start text-white">
                 <p class="text-xs font-semibold text-gray-200">{$t('previous').toUpperCase()}</p>
                 <p class="text-xl">{$memoryLaneTitle(current.previousMemory)}</p>
               </div>
@@ -465,7 +465,7 @@
             {/key}
 
             <div
-              class="absolute bottom-0 right-0 p-2 transition-all flex h-full justify-between flex-col items-end gap-2"
+              class="absolute bottom-0 end-0 p-2 transition-all flex h-full justify-between flex-col items-end gap-2"
               class:opacity-0={galleryInView}
               class:opacity-100={!galleryInView}
             >
@@ -521,7 +521,7 @@
             </div>
             <!-- CONTROL BUTTONS -->
             {#if current.previous}
-              <div class="absolute top-1/2 left-0 ml-4">
+              <div class="absolute top-1/2 start-0 ms-4">
                 <CircleIconButton
                   title={$t('previous_memory')}
                   icon={mdiChevronLeft}
@@ -532,7 +532,7 @@
             {/if}
 
             {#if current.next}
-              <div class="absolute top-1/2 right-0 mr-4">
+              <div class="absolute top-1/2 end-0 me-4">
                 <CircleIconButton
                   title={$t('next_memory')}
                   icon={mdiChevronRight}
@@ -542,7 +542,7 @@
               </div>
             {/if}
 
-            <div class="absolute left-8 top-4 text-sm font-medium text-white">
+            <div class="absolute start-8 top-4 text-sm font-medium text-white">
               <p>
                 {fromLocalDateTime(current.memory.assets[0].localDateTime).toLocaleString(DateTime.DATE_FULL, {
                   locale: $locale,
@@ -582,7 +582,7 @@
             {/if}
 
             {#if current.nextMemory}
-              <div class="absolute bottom-4 left-4 text-left text-white">
+              <div class="absolute bottom-4 start-4 text-start text-white">
                 <p class="text-xs font-semibold text-gray-200">{$t('up_next').toUpperCase()}</p>
                 <p class="text-xl">{$memoryLaneTitle(current.nextMemory)}</p>
               </div>
diff --git a/web/src/lib/components/photos-page/asset-date-group.svelte b/web/src/lib/components/photos-page/asset-date-group.svelte
index e45ebea1e7..ecb25b0697 100644
--- a/web/src/lib/components/photos-page/asset-date-group.svelte
+++ b/web/src/lib/components/photos-page/asset-date-group.svelte
@@ -149,7 +149,7 @@
         </div>
       {/if}
 
-      <span class="w-full truncate first-letter:capitalize ml-2.5" title={getDateLocaleString(dateGroup.date)}>
+      <span class="w-full truncate first-letter:capitalize ms-2.5" title={getDateLocaleString(dateGroup.date)}>
         {dateGroup.groupTitle}
       </span>
     </div>
diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte
index ccba4c88a8..da4387a490 100644
--- a/web/src/lib/components/photos-page/asset-grid.svelte
+++ b/web/src/lib/components/photos-page/asset-grid.svelte
@@ -743,7 +743,7 @@
 <!-- Right margin MUST be equal to the width of immich-scrubbable-scrollbar -->
 <section
   id="asset-grid"
-  class={['scrollbar-hidden h-full overflow-y-auto outline-none', { 'm-0': isEmpty }, { 'ml-0': !isEmpty }]}
+  class={['scrollbar-hidden h-full overflow-y-auto outline-none', { 'm-0': isEmpty }, { 'ms-0': !isEmpty }]}
   style:margin-right={(usingMobileDevice ? 0 : scrubberWidth) + 'px'}
   tabindex="-1"
   bind:clientHeight={assetStore.viewportHeight}
diff --git a/web/src/lib/components/photos-page/memory-lane.svelte b/web/src/lib/components/photos-page/memory-lane.svelte
index 280273d0d1..e94f0af2c4 100644
--- a/web/src/lib/components/photos-page/memory-lane.svelte
+++ b/web/src/lib/components/photos-page/memory-lane.svelte
@@ -44,9 +44,9 @@
     onscroll={onScroll}
   >
     {#if canScrollLeft || canScrollRight}
-      <div class="sticky left-0 z-20">
+      <div class="sticky start-0 z-20">
         {#if canScrollLeft}
-          <div class="absolute left-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}>
+          <div class="absolute start-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}>
             <button
               type="button"
               class="rounded-full border border-gray-500 bg-gray-100 p-2 text-gray-500 opacity-50 hover:opacity-100"
@@ -59,7 +59,7 @@
           </div>
         {/if}
         {#if canScrollRight}
-          <div class="absolute right-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}>
+          <div class="absolute end-4 top-[6rem] z-20" transition:fade={{ duration: 200 }}>
             <button
               type="button"
               class="rounded-full border border-gray-500 bg-gray-100 p-2 text-gray-500 opacity-50 hover:opacity-100"
@@ -76,7 +76,7 @@
     <div class="inline-block" use:resizeObserver={({ width }) => (innerWidth = width)}>
       {#each memoryStore.memories as memory (memory.id)}
         <a
-          class="memory-card relative mr-2 md:mr-4 last:mr-0 inline-block aspect-[3/4] md:aspect-[4/3] max-md:h-[150px] xl:aspect-video h-[215px] rounded-xl"
+          class="memory-card relative me-2 md:me-4 last:me-0 inline-block aspect-[3/4] md:aspect-[4/3] max-md:h-[150px] xl:aspect-video h-[215px] rounded-xl"
           href="{AppRoute.MEMORY}?{QueryParameter.ID}={memory.assets[0].id}"
         >
           <img
@@ -85,11 +85,11 @@
             alt={$t('memory_lane_title', { values: { title: $getAltText(memory.assets[0]) } })}
             draggable="false"
           />
-          <p class="absolute bottom-2 left-4 z-10 text-lg text-white max-md:text-sm">
+          <p class="absolute bottom-2 start-4 z-10 text-lg text-white max-md:text-sm">
             {$memoryLaneTitle(memory)}
           </p>
           <div
-            class="absolute left-0 top-0 z-0 h-full w-full rounded-xl bg-gradient-to-t from-black/40 via-transparent to-transparent transition-all hover:bg-black/20"
+            class="absolute start-0 top-0 z-0 h-full w-full rounded-xl bg-gradient-to-t from-black/40 via-transparent to-transparent transition-all hover:bg-black/20"
           ></div>
         </a>
       {/each}
diff --git a/web/src/lib/components/photos-page/skeleton.svelte b/web/src/lib/components/photos-page/skeleton.svelte
index dd32d68842..5c43887450 100644
--- a/web/src/lib/components/photos-page/skeleton.svelte
+++ b/web/src/lib/components/photos-page/skeleton.svelte
@@ -14,7 +14,7 @@
     {title}
   </div>
   <div
-    class="animate-pulse absolute h-full ml-[10px] mr-[10px]"
+    class="animate-pulse absolute h-full ms-[10px] me-[10px]"
     style:width="calc(100% - 20px)"
     data-skeleton="true"
   ></div>
diff --git a/web/src/lib/components/places-page/places-card-group.svelte b/web/src/lib/components/places-page/places-card-group.svelte
index 9bd863a95d..78e54e9bb4 100644
--- a/web/src/lib/components/places-page/places-card-group.svelte
+++ b/web/src/lib/components/places-page/places-card-group.svelte
@@ -25,7 +25,7 @@
     <button
       type="button"
       onclick={() => togglePlacesGroupCollapsing(group.id)}
-      class="w-fit mt-2 pt-2 pr-2 mb-2 dark:text-immich-dark-fg"
+      class="w-fit mt-2 pt-2 pe-2 mb-2 dark:text-immich-dark-fg"
       aria-expanded={!isCollapsed}
     >
       <Icon
@@ -34,7 +34,7 @@
         class="inline-block -mt-2.5 transition-all duration-[250ms] {iconRotation}"
       />
       <span class="font-bold text-3xl text-black dark:text-white">{group.name}</span>
-      <span class="ml-1.5">({$t('places_count', { values: { count: places.length } })})</span>
+      <span class="ms-1.5">({$t('places_count', { values: { count: places.length } })})</span>
     </button>
     <hr class="dark:border-immich-dark-gray" />
   </div>
diff --git a/web/src/lib/components/shared-components/change-date.svelte b/web/src/lib/components/shared-components/change-date.svelte
index 13b2752f0c..ef682d9048 100644
--- a/web/src/lib/components/shared-components/change-date.svelte
+++ b/web/src/lib/components/shared-components/change-date.svelte
@@ -144,7 +144,7 @@
   <!-- @migration-task: migrate this slot by hand, `prompt` would shadow a prop on the parent component -->
   <!-- @migration-task: migrate this slot by hand, `prompt` would shadow a prop on the parent component -->
   {#snippet promptSnippet()}
-    <div class="flex flex-col text-left gap-2">
+    <div class="flex flex-col text-start gap-2">
       <div class="flex flex-col">
         <label for="datetime">{$t('date_and_time')}</label>
         <DateInput class="immich-form-input" id="datetime" type="datetime-local" bind:value={selectedDate} />
diff --git a/web/src/lib/components/shared-components/change-location.svelte b/web/src/lib/components/shared-components/change-location.svelte
index a84838f1db..f981e85029 100644
--- a/web/src/lib/components/shared-components/change-location.svelte
+++ b/web/src/lib/components/shared-components/change-location.svelte
@@ -147,7 +147,7 @@
                   : ''}"
                 onclick={() => handleUseSuggested(place.latitude, place.longitude)}
               >
-                <p class="ml-4 text-sm text-gray-700 dark:text-gray-100 truncate">
+                <p class="ms-4 text-sm text-gray-700 dark:text-gray-100 truncate">
                   {getLocation(place.name, place.admin1name, place.admin2name)}
                 </p>
               </button>
@@ -189,7 +189,7 @@
         {/await}
       </div>
 
-      <div class="grid sm:grid-cols-2 gap-4 text-sm text-left mt-4">
+      <div class="grid sm:grid-cols-2 gap-4 text-sm text-start mt-4">
         <CoordinatesInput
           lat={point ? point.lat : assetLat}
           lng={point ? point.lng : assetLng}
diff --git a/web/src/lib/components/shared-components/combobox.svelte b/web/src/lib/components/shared-components/combobox.svelte
index 3b70b0e859..8be5aabc6c 100644
--- a/web/src/lib/components/shared-components/combobox.svelte
+++ b/web/src/lib/components/shared-components/combobox.svelte
@@ -258,7 +258,7 @@
 >
   <div>
     {#if isActive}
-      <div class="absolute inset-y-0 left-0 flex items-center pl-3">
+      <div class="absolute inset-y-0 start-0 flex items-center ps-3">
         <div class="dark:text-immich-dark-fg/75">
           <Icon path={mdiMagnify} ariaHidden={true} />
         </div>
@@ -273,11 +273,11 @@
       aria-expanded={isOpen}
       autocomplete="off"
       bind:this={input}
-      class:!pl-8={isActive}
+      class:!ps-8={isActive}
       class:!rounded-b-none={isOpen && dropdownDirection === 'bottom'}
       class:!rounded-t-none={isOpen && dropdownDirection === 'top'}
       class:cursor-pointer={!isActive}
-      class="immich-form-input text-sm text-left w-full !pr-12 transition-all"
+      class="immich-form-input text-sm w-full !pe-12 transition-all"
       id={inputId}
       onfocus={activate}
       oninput={onInput}
@@ -325,8 +325,8 @@
     />
 
     <div
-      class="absolute right-0 top-0 h-full flex px-4 justify-center items-center content-between"
-      class:pr-2={selectedOption}
+      class="absolute end-0 top-0 h-full flex px-4 justify-center items-center content-between"
+      class:pe-2={selectedOption}
       class:pointer-events-none={!selectedOption}
     >
       {#if selectedOption}
@@ -341,7 +341,7 @@
     role="listbox"
     id={listboxId}
     transition:fly={{ duration: 250 }}
-    class="fixed text-left text-sm w-full overflow-y-auto bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-900 z-[10000]"
+    class="fixed text-start text-sm w-full overflow-y-auto bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-900 z-[10000]"
     class:rounded-b-xl={dropdownDirection === 'bottom'}
     class:rounded-t-xl={dropdownDirection === 'top'}
     class:shadow={dropdownDirection === 'bottom'}
@@ -360,7 +360,7 @@
           role="option"
           aria-selected={selectedIndex === 0}
           aria-disabled={true}
-          class="text-left w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 cursor-default aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700"
+          class="text-start w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 cursor-default aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700"
           id={`${listboxId}-${0}`}
           onclick={closeDropdown}
         >
@@ -372,7 +372,7 @@
         <li
           aria-selected={index === selectedIndex}
           bind:this={optionRefs[index]}
-          class="text-left w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all cursor-pointer aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700 break-words"
+          class="text-start w-full px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all cursor-pointer aria-selected:bg-gray-200 aria-selected:dark:bg-gray-700 break-words"
           id={`${listboxId}-${index}`}
           onclick={() => handleSelect(option)}
           role="option"
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 a3e12e4f12..67a17db950 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
@@ -7,6 +7,7 @@
   } from '$lib/components/elements/buttons/circle-icon-button.svelte';
   import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
   import { optionClickCallbackStore, selectedIdStore } from '$lib/stores/context-menu.store';
+  import { languageManager } from '$lib/stores/language-manager.svelte';
   import {
     getContextMenuPositionFromBoundingRect,
     getContextMenuPositionFromEvent,
@@ -26,6 +27,7 @@
     /**
      * The direction in which the context menu should open.
      */
+    // TODO change to start vs end
     direction?: 'left' | 'right';
     color?: Color;
     size?: string | undefined;
@@ -62,7 +64,15 @@
   const menuId = `context-menu-${id}`;
 
   const openDropdown = (event: KeyboardEvent | MouseEvent) => {
-    contextMenuPosition = getContextMenuPositionFromEvent(event, align);
+    let layoutAlign = align;
+    if (languageManager.rtl) {
+      if (align.includes('left')) {
+        layoutAlign = align.replace('left', 'right') as Align;
+      } else if (align.includes('right')) {
+        layoutAlign = align.replace('right', 'left') as Align;
+      }
+    }
+    contextMenuPosition = getContextMenuPositionFromEvent(event, layoutAlign);
     isOpen = true;
     menuContainer?.focus();
   };
diff --git a/web/src/lib/components/shared-components/context-menu/context-menu.svelte b/web/src/lib/components/shared-components/context-menu/context-menu.svelte
index 976f86d3e5..a79a3bd385 100644
--- a/web/src/lib/components/shared-components/context-menu/context-menu.svelte
+++ b/web/src/lib/components/shared-components/context-menu/context-menu.svelte
@@ -3,6 +3,7 @@
   import { slide } from 'svelte/transition';
   import { clickOutside } from '$lib/actions/click-outside';
   import type { Snippet } from 'svelte';
+  import { languageManager } from '$lib/stores/language-manager.svelte';
 
   interface Props {
     isVisible?: boolean;
@@ -41,12 +42,17 @@
 
   $effect(() => {
     if (menuElement) {
+      let layoutDirection = direction;
+      if (languageManager.rtl) {
+        layoutDirection = direction === 'left' ? 'right' : 'left';
+      }
+
       const rect = menuElement.getBoundingClientRect();
-      const directionWidth = direction === 'left' ? rect.width : 0;
+      const directionWidth = layoutDirection === 'left' ? rect.width : 0;
       const menuHeight = Math.min(menuElement.clientHeight, height) || 0;
 
-      left = Math.min(window.innerWidth - rect.width, x - directionWidth);
-      top = Math.min(window.innerHeight - menuHeight, y);
+      left = Math.max(8, Math.min(window.innerWidth - rect.width, x - directionWidth));
+      top = Math.max(8, Math.min(window.innerHeight - menuHeight, y));
     }
   });
 </script>
@@ -66,7 +72,7 @@
     aria-labelledby={ariaLabelledBy}
     bind:this={menuElement}
     class="{isVisible
-      ? 'max-h-dvh max-h-svh'
+      ? 'max-h-dvh'
       : 'max-h-0'} flex flex-col transition-all duration-[250ms] ease-in-out outline-none overflow-auto"
     role="menu"
     tabindex="-1"
diff --git a/web/src/lib/components/shared-components/context-menu/menu-option.svelte b/web/src/lib/components/shared-components/context-menu/menu-option.svelte
index b3a6d41018..b331804958 100644
--- a/web/src/lib/components/shared-components/context-menu/menu-option.svelte
+++ b/web/src/lib/components/shared-components/context-menu/menu-option.svelte
@@ -53,7 +53,7 @@
   onclick={handleClick}
   onmouseover={() => ($selectedIdStore = id)}
   onmouseleave={() => ($selectedIdStore = undefined)}
-  class="w-full p-4 text-left text-sm font-medium {textColor} focus:outline-none focus:ring-2 focus:ring-inset cursor-pointer border-gray-200 flex gap-2 items-center {isActive
+  class="w-full p-4 text-start text-sm font-medium {textColor} focus:outline-none focus:ring-2 focus:ring-inset cursor-pointer border-gray-200 flex gap-2 items-center {isActive
     ? activeColor
     : 'bg-slate-100'}"
   role="menuitem"
@@ -65,7 +65,7 @@
     <div class="flex justify-between">
       {text}
       {#if shortcutLabel}
-        <span class="text-gray-500 pl-4">
+        <span class="text-gray-500 ps-4">
           {shortcutLabel}
         </span>
       {/if}
diff --git a/web/src/lib/components/shared-components/context-menu/right-click-context-menu.svelte b/web/src/lib/components/shared-components/context-menu/right-click-context-menu.svelte
index 9b9e68b6c5..27d50f4fe5 100644
--- a/web/src/lib/components/shared-components/context-menu/right-click-context-menu.svelte
+++ b/web/src/lib/components/shared-components/context-menu/right-click-context-menu.svelte
@@ -38,7 +38,7 @@
     const elements = document.elementsFromPoint(event.x, event.y);
 
     if (menuContainer && elements.includes(menuContainer)) {
-      // User right-clicked on the context menu itself, we keep the context
+      // User end-clicked on the context menu itself, we keep the context
       // menu as is
       return;
     }
@@ -91,7 +91,7 @@
         },
       ]}
     >
-      <section class="fixed left-0 top-0 z-10 flex h-dvh w-dvw" {oncontextmenu} role="presentation">
+      <section class="fixed start-0 top-0 z-10 flex h-dvh w-dvw" {oncontextmenu} role="presentation">
         <ContextMenu
           {direction}
           {x}
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 7c630875c0..a26da45465 100644
--- a/web/src/lib/components/shared-components/control-app-bar.svelte
+++ b/web/src/lib/components/shared-components/control-app-bar.svelte
@@ -91,7 +91,7 @@
       {@render children?.()}
     </div>
 
-    <div class="mr-4 flex place-items-center gap-1 justify-self-end">
+    <div class="me-4 flex place-items-center gap-1 justify-self-end">
       {@render trailing?.()}
     </div>
   </div>
diff --git a/web/src/lib/components/shared-components/duplicates-modal.svelte b/web/src/lib/components/shared-components/duplicates-modal.svelte
index 96f563989b..c9abea1377 100644
--- a/web/src/lib/components/shared-components/duplicates-modal.svelte
+++ b/web/src/lib/components/shared-components/duplicates-modal.svelte
@@ -12,7 +12,7 @@
 <FullScreenModal title={$t('deduplication_info')} width="auto" {onClose}>
   <div class="text-sm dark:text-white">
     <p>{$t('deduplication_info_description')}</p>
-    <ol class="ml-8 mt-2" style="list-style: decimal">
+    <ol class="ms-8 mt-2" style="list-style: decimal">
       <li>{$t('deduplication_criteria_1')}</li>
       <li>{$t('deduplication_criteria_2')}</li>
     </ol>
diff --git a/web/src/lib/components/shared-components/full-screen-modal.svelte b/web/src/lib/components/shared-components/full-screen-modal.svelte
index 9284283f54..1640dad692 100644
--- a/web/src/lib/components/shared-components/full-screen-modal.svelte
+++ b/web/src/lib/components/shared-components/full-screen-modal.svelte
@@ -77,7 +77,7 @@
   role="presentation"
   in:fade={{ duration: 100 }}
   out:fade={{ duration: 100 }}
-  class="fixed left-0 top-0 z-[9999] flex h-dvh w-dvw place-content-center place-items-center bg-black/40"
+  class="fixed start-0 top-0 z-[9999] flex h-dvh w-dvw place-content-center place-items-center bg-black/40"
   onkeydown={(event) => {
     event.stopPropagation();
   }}
diff --git a/web/src/lib/components/shared-components/immich-logo-small-link.svelte b/web/src/lib/components/shared-components/immich-logo-small-link.svelte
index 7f96d88df8..5a940fa278 100644
--- a/web/src/lib/components/shared-components/immich-logo-small-link.svelte
+++ b/web/src/lib/components/shared-components/immich-logo-small-link.svelte
@@ -3,6 +3,6 @@
   import { mobileDevice } from '$lib/stores/mobile-device.svelte';
 </script>
 
-<a data-sveltekit-preload-data="hover" class="ml-4" href="/">
+<a data-sveltekit-preload-data="hover" class="ms-4" href="/">
   <ImmichLogo class="h-[50px] max-w-none md:w-auto md:max-w-full" noText={mobileDevice.maxMd} />
 </a>
diff --git a/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte b/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte
index 5bf3f0a621..92db67eba0 100644
--- a/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte
+++ b/web/src/lib/components/shared-components/navigation-bar/account-info-panel.svelte
@@ -48,7 +48,7 @@
   in:fade={{ duration: 100 }}
   out:fade={{ duration: 100 }}
   id="account-info-panel"
-  class="absolute right-[25px] top-[75px] z-[100] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
+  class="absolute end-[25px] top-[75px] z-[100] w-[min(360px,100vw-50px)] rounded-3xl bg-gray-200 shadow-lg dark:border dark:border-immich-dark-gray dark:bg-immich-dark-gray"
   use:focusTrap
 >
   <div
@@ -56,7 +56,7 @@
   >
     <div class="relative">
       <UserAvatar user={$user} size="xl" />
-      <div class="absolute z-10 bottom-0 right-0 rounded-full w-6 h-6">
+      <div class="absolute z-10 bottom-0 end-0 rounded-full w-6 h-6">
         <CircleIconButton
           color="primary"
           icon={mdiPencil}
diff --git a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
index 90f6b3c55b..e91db5cc3a 100644
--- a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
+++ b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
@@ -83,8 +83,8 @@
         <ImmichLogo class="max-md:h-[48px] h-[50px]" noText={!mobileDevice.isFullSidebar} />
       </a>
     </div>
-    <div class="flex justify-between gap-4 lg:gap-8 pr-6">
-      <div class="hidden w-full max-w-5xl flex-1 tall:pl-0 sm:block">
+    <div class="flex justify-between gap-4 lg:gap-8 pe-6">
+      <div class="hidden w-full max-w-5xl flex-1 tall:ps-0 sm:block">
         {#if $featureFlags.search}
           <SearchBar grayTheme={true} />
         {/if}
@@ -154,7 +154,7 @@
         >
           <button
             type="button"
-            class="flex pl-2"
+            class="flex ps-2"
             onmouseover={() => (shouldShowAccountInfo = true)}
             onfocus={() => (shouldShowAccountInfo = true)}
             onblur={() => (shouldShowAccountInfo = false)}
@@ -170,7 +170,7 @@
             <div
               in:fade={{ delay: 500, duration: 150 }}
               out:fade={{ delay: 200, duration: 150 }}
-              class="absolute -bottom-12 right-5 rounded-md border bg-gray-500 p-2 text-[12px] text-gray-100 shadow-md dark:border-immich-dark-gray dark:bg-immich-dark-gray"
+              class="absolute -bottom-12 end-5 rounded-md border bg-gray-500 p-2 text-[12px] text-gray-100 shadow-md dark:border-immich-dark-gray dark:bg-immich-dark-gray"
             >
               <p>{$user.name}</p>
               <p>{$user.email}</p>
diff --git a/web/src/lib/components/shared-components/navigation-loading-bar.svelte b/web/src/lib/components/shared-components/navigation-loading-bar.svelte
index bd5f15ae4e..afdb8ee967 100644
--- a/web/src/lib/components/shared-components/navigation-loading-bar.svelte
+++ b/web/src/lib/components/shared-components/navigation-loading-bar.svelte
@@ -26,7 +26,7 @@
 </script>
 
 {#if showing}
-  <div class="absolute left-0 top-0 z-[999999999] h-[3px] w-dvw bg-white">
+  <div class="absolute start-0 top-0 z-[999999999] h-[3px] w-dvw bg-white">
     <span class="absolute h-[3px] bg-immich-primary" style:width={`${$progress}%`}></span>
   </div>
 {/if}
diff --git a/web/src/lib/components/shared-components/notification/notification-card.svelte b/web/src/lib/components/shared-components/notification/notification-card.svelte
index 5054c18695..30c8622bba 100644
--- a/web/src/lib/components/shared-components/notification/notification-card.svelte
+++ b/web/src/lib/components/shared-components/notification/notification-card.svelte
@@ -100,7 +100,7 @@
     />
   </div>
 
-  <p class="whitespace-pre-wrap pl-[28px] pr-[16px] text-sm" data-testid="message">
+  <p class="whitespace-pre-wrap ps-[28px] pe-[16px] text-sm" data-testid="message">
     {#if isComponentNotification(notification)}
       <notification.component.type {...notification.component.props} />
     {:else}
@@ -109,7 +109,7 @@
   </p>
 
   {#if notification.button}
-    <p class="pl-[28px] mt-2.5 text-sm">
+    <p class="ps-[28px] mt-2.5 text-sm">
       <button
         type="button"
         class="{buttonStyle[notification.type]} rounded px-3 pt-1.5 pb-1 transition-all duration-200"
diff --git a/web/src/lib/components/shared-components/notification/notification-list.svelte b/web/src/lib/components/shared-components/notification/notification-list.svelte
index c7c54be267..6e067e839a 100644
--- a/web/src/lib/components/shared-components/notification/notification-list.svelte
+++ b/web/src/lib/components/shared-components/notification/notification-list.svelte
@@ -11,7 +11,7 @@
 
 <div role="status" aria-relevant="additions" aria-label={$t('notifications')}>
   {#if $notificationList.length > 0}
-    <section transition:fade={{ duration: 250 }} id="notification-list" class="fixed right-5 top-[80px] z-[99999999]">
+    <section transition:fade={{ duration: 250 }} id="notification-list" class="fixed end-5 top-[80px] z-[99999999]">
       {#each $notificationList as notification (notification.id)}
         <div animate:flip={{ duration: 250, easing: quintOut }}>
           <NotificationCard {notification} />
diff --git a/web/src/lib/components/shared-components/password-field.svelte b/web/src/lib/components/shared-components/password-field.svelte
index 8519f84134..32879a1755 100644
--- a/web/src/lib/components/shared-components/password-field.svelte
+++ b/web/src/lib/components/shared-components/password-field.svelte
@@ -19,7 +19,7 @@
 <div class="relative w-full">
   <input
     {...rest}
-    class="immich-form-input w-full !pr-12"
+    class="immich-form-input w-full !pe-12"
     type={showPassword ? 'text' : 'password'}
     {required}
     value={password}
diff --git a/web/src/lib/components/shared-components/progress-bar/progress-bar.svelte b/web/src/lib/components/shared-components/progress-bar/progress-bar.svelte
index 5b663bd1a9..2b3abb9708 100644
--- a/web/src/lib/components/shared-components/progress-bar/progress-bar.svelte
+++ b/web/src/lib/components/shared-components/progress-bar/progress-bar.svelte
@@ -88,5 +88,5 @@
 </script>
 
 {#if !hidden}
-  <span class="absolute left-0 h-[3px] bg-immich-primary shadow-2xl" style:width={`${$progress * 100}%`}></span>
+  <span class="absolute start-0 h-[3px] bg-immich-primary shadow-2xl" style:width={`${$progress * 100}%`}></span>
 {/if}
diff --git a/web/src/lib/components/shared-components/scrubber/scrubber.svelte b/web/src/lib/components/shared-components/scrubber/scrubber.svelte
index f5d9c00768..d7b0dfd28d 100644
--- a/web/src/lib/components/shared-components/scrubber/scrubber.svelte
+++ b/web/src/lib/components/shared-components/scrubber/scrubber.svelte
@@ -446,7 +446,7 @@
   aria-valuemax={toScrollY(1)}
   aria-valuemin={toScrollY(0)}
   data-id="immich-scrubbable-scrollbar"
-  class="absolute right-0 z-[1] select-none bg-immich-bg hover:cursor-row-resize"
+  class="absolute end-0 z-[1] select-none bg-immich-bg hover:cursor-row-resize"
   style:padding-top={PADDING_TOP + 'px'}
   style:padding-bottom={PADDING_BOTTOM + 'px'}
   style:width
@@ -464,7 +464,7 @@
       class={[
         { 'border-b-2': isDragging },
         { 'rounded-bl-md': !isDragging },
-        'truncate opacity-85 pointer-events-none absolute right-0 z-[100] min-w-20 max-w-64 w-fit rounded-tl-md  border-immich-primary bg-immich-bg py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:bg-immich-dark-gray dark:text-immich-dark-fg',
+        'truncate opacity-85 pointer-events-none absolute end-0 z-[100] min-w-20 max-w-64 w-fit rounded-ss-md  border-immich-primary bg-immich-bg py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:bg-immich-dark-gray dark:text-immich-dark-fg',
       ]}
       style:top="{hoverY + 2}px"
     >
@@ -474,7 +474,7 @@
   {#if usingMobileDevice && ((assetStore.scrolling && scrollHoverLabel) || isHover || isDragging)}
     <div
       id="time-label"
-      class="rounded-l-full w-[32px] pl-2 text-white bg-immich-primary dark:bg-gray-600 hover:cursor-pointer select-none"
+      class="rounded-s-full w-[32px] ps-2 text-white bg-immich-primary dark:bg-gray-600 hover:cursor-pointer select-none"
       style:top="{PADDING_TOP + (scrollY - 50 / 2)}px"
       style:height="50px"
       style:right="0"
@@ -482,8 +482,8 @@
       in:fade={{ duration: 200 }}
       out:fade={{ duration: 200 }}
     >
-      <Icon path={mdiPlay} size="20" class="-rotate-90 relative top-[9px] -right-[2px]" />
-      <Icon path={mdiPlay} size="20" class="rotate-90 relative top-[1px] -right-[2px]" />
+      <Icon path={mdiPlay} size="20" class="-rotate-90 relative top-[9px] -end-[2px]" />
+      <Icon path={mdiPlay} size="20" class="rotate-90 relative top-[1px] -end-[2px]" />
       {#if (assetStore.scrolling && scrollHoverLabel) || isHover || isDragging}
         <p
           transition:fade={{ duration: 200 }}
@@ -500,13 +500,13 @@
   <!-- Scroll Position Indicator Line -->
   {#if !usingMobileDevice && !isDragging}
     <div
-      class="absolute right-0 h-[2px] w-10 bg-immich-primary dark:bg-immich-dark-primary"
+      class="absolute end-0 h-[2px] w-10 bg-immich-primary dark:bg-immich-dark-primary"
       style:top="{scrollY + PADDING_TOP - 2}px"
     >
       {#if assetStore.scrolling && scrollHoverLabel && !isHover}
         <p
           transition:fade={{ duration: 200 }}
-          class="truncate pointer-events-none absolute right-0 bottom-0 z-[100] min-w-20 max-w-64 w-fit rounded-tl-md border-b-2 border-immich-primary bg-immich-bg/80 py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:bg-immich-dark-gray/80 dark:text-immich-dark-fg"
+          class="truncate pointer-events-none absolute end-0 bottom-0 z-[100] min-w-20 max-w-64 w-fit rounded-tl-md border-b-2 border-immich-primary bg-immich-bg/80 py-1 px-1 text-sm font-medium shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:border-immich-dark-primary dark:bg-immich-dark-gray/80 dark:text-immich-dark-fg"
         >
           {scrollHoverLabel}
         </p>
@@ -521,7 +521,7 @@
     data-label={segments.at(0)?.dateFormatted}
   >
     {#if relativeTopOffset > 6}
-      <div class="absolute right-[0.75rem] h-[4px] w-[4px] rounded-full bg-gray-300"></div>
+      <div class="absolute end-[0.75rem] h-[4px] w-[4px] rounded-full bg-gray-300"></div>
     {/if}
   </div>
   <!-- Time Segment -->
@@ -535,12 +535,12 @@
     >
       {#if !usingMobileDevice}
         {#if segment.hasLabel}
-          <div class="absolute right-[1.25rem] top-[-16px] z-10 text-[12px] dark:text-immich-dark-fg font-immich-mono">
+          <div class="absolute end-[1.25rem] top-[-16px] z-10 text-[12px] dark:text-immich-dark-fg font-immich-mono">
             {segment.date.year}
           </div>
         {/if}
         {#if segment.hasDot}
-          <div class="absolute right-[0.75rem] bottom-0 h-[4px] w-[4px] rounded-full bg-gray-300"></div>
+          <div class="absolute end-[0.75rem] bottom-0 h-[4px] w-[4px] rounded-full bg-gray-300"></div>
         {/if}
       {/if}
     </div>
diff --git a/web/src/lib/components/shared-components/search-bar/search-bar.svelte b/web/src/lib/components/shared-components/search-bar/search-bar.svelte
index a98e2f7882..e01b0edf1b 100644
--- a/web/src/lib/components/shared-components/search-bar/search-bar.svelte
+++ b/web/src/lib/components/shared-components/search-bar/search-bar.svelte
@@ -261,15 +261,15 @@
       />
     </div>
 
-    <div class="absolute inset-y-0 {showClearIcon ? 'right-14' : 'right-2'} flex items-center pl-6 transition-all">
+    <div class="absolute inset-y-0 {showClearIcon ? 'end-14' : 'end-2'} flex items-center ps-6 transition-all">
       <CircleIconButton title={$t('show_search_options')} icon={mdiTune} onclick={onFilterClick} size="20" />
     </div>
 
     {#if isFocus}
       <div
         class="absolute inset-y-0 flex items-center"
-        class:right-16={isFocus}
-        class:right-28={isFocus && value.length > 0}
+        class:end-16={isFocus}
+        class:end-28={isFocus && value.length > 0}
       >
         <p
           class="bg-immich-primary text-white dark:bg-immich-dark-primary/90 dark:text-black/75 rounded-full px-3 py-1 text-xs z-10"
@@ -280,11 +280,11 @@
     {/if}
 
     {#if showClearIcon}
-      <div class="absolute inset-y-0 right-0 flex items-center pr-2">
+      <div class="absolute inset-y-0 end-0 flex items-center pe-2">
         <CircleIconButton onclick={onClear} icon={mdiClose} title={$t('clear')} size="20" />
       </div>
     {/if}
-    <div class="absolute inset-y-0 left-0 flex items-center pl-2">
+    <div class="absolute inset-y-0 start-0 flex items-center ps-2">
       <CircleIconButton
         type="submit"
         disabled={showFilter}
diff --git a/web/src/lib/components/shared-components/search-bar/search-history-box.svelte b/web/src/lib/components/shared-components/search-bar/search-history-box.svelte
index a6188e46a9..2eb0b06d4b 100644
--- a/web/src/lib/components/shared-components/search-bar/search-history-box.svelte
+++ b/web/src/lib/components/shared-components/search-bar/search-history-box.svelte
@@ -122,7 +122,7 @@
             <!-- svelte-ignore a11y_click_events_have_key_events -->
             <div
               id={getId(index)}
-              class="relative flex w-full cursor-pointer gap-3 py-3 pl-5 hover:bg-gray-100 aria-selected:bg-gray-100 dark:aria-selected:bg-gray-500/30 dark:hover:bg-gray-500/30"
+              class="relative flex w-full cursor-pointer gap-3 py-3 ps-5 hover:bg-gray-100 aria-selected:bg-gray-100 dark:aria-selected:bg-gray-500/30 dark:hover:bg-gray-500/30"
               onclick={() => handleSelect(savedSearchTerm)}
               role="option"
               tabindex="-1"
@@ -132,7 +132,7 @@
               <Icon path={mdiMagnify} size="1.5em" ariaHidden={true} />
               {savedSearchTerm}
             </div>
-            <div aria-hidden={true} class="absolute right-5 top-0 items-center justify-center py-3">
+            <div aria-hidden={true} class="absolute end-5 top-0 items-center justify-center py-3">
               <CircleIconButton
                 icon={mdiClose}
                 title={$t('remove')}
diff --git a/web/src/lib/components/shared-components/search-bar/search-tags-section.svelte b/web/src/lib/components/shared-components/search-bar/search-tags-section.svelte
index 6071da1460..0819022ae2 100644
--- a/web/src/lib/components/shared-components/search-bar/search-tags-section.svelte
+++ b/web/src/lib/components/shared-components/search-bar/search-tags-section.svelte
@@ -57,7 +57,7 @@
         {#if tag}
           <div class="flex group transition-all">
             <span
-              class="inline-block h-min whitespace-nowrap pl-3 pr-1 group-hover:pl-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-tl-full rounded-bl-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 roudned-s-full hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
             >
               <p class="text-sm">
                 {tag.value}
@@ -66,7 +66,7 @@
 
             <button
               type="button"
-              class="text-gray-100 dark:text-immich-dark-gray bg-immich-primary/95 dark:bg-immich-dark-primary/95 rounded-tr-full rounded-br-full place-items-center place-content-center pr-2 pl-1 py-1 hover:bg-immich-primary/80 dark:hover:bg-immich-dark-primary/80 transition-all"
+              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)}
             >
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 bfa379edcd..5ae41c0551 100755
--- a/web/src/lib/components/shared-components/settings/setting-accordion.svelte
+++ b/web/src/lib/components/shared-components/settings/setting-accordion.svelte
@@ -73,7 +73,7 @@
     type="button"
     aria-expanded={isOpen}
     {onclick}
-    class="flex w-full place-items-center justify-between text-left"
+    class="flex w-full place-items-center justify-between text-start"
   >
     <div>
       <div class="flex gap-2 place-items-center">
@@ -110,7 +110,7 @@
   </button>
 
   {#if isOpen}
-    <ul transition:slide={{ duration: 150 }} class="mb-2 ml-4">
+    <ul transition:slide={{ duration: 150 }} class="mb-2 ms-4">
       {@render children?.()}
     </ul>
   {/if}
diff --git a/web/src/lib/components/shared-components/settings/setting-input-field.svelte b/web/src/lib/components/shared-components/settings/setting-input-field.svelte
index 9259f59cfb..946f40c08d 100644
--- a/web/src/lib/components/shared-components/settings/setting-input-field.svelte
+++ b/web/src/lib/components/shared-components/settings/setting-input-field.svelte
@@ -101,7 +101,7 @@
       {#if inputType === SettingInputFieldType.COLOR}
         <input
           bind:this={input}
-          class="immich-form-input w-full pb-2 rounded-none mr-1"
+          class="immich-form-input w-full pb-2 rounded-none me-1"
           aria-describedby={description ? `${label}-desc` : undefined}
           aria-labelledby="{label}-label"
           id={label}
diff --git a/web/src/lib/components/shared-components/settings/setting-select.svelte b/web/src/lib/components/shared-components/settings/setting-select.svelte
index 1cfd7123ac..87260e6d6a 100644
--- a/web/src/lib/components/shared-components/settings/setting-select.svelte
+++ b/web/src/lib/components/shared-components/settings/setting-select.svelte
@@ -65,12 +65,12 @@
       path={mdiChevronDown}
       size="1.2em"
       ariaHidden={true}
-      class="pointer-events-none right-1 relative col-start-1 row-start-1 self-center justify-self-end {disabled
+      class="pointer-events-none end-1 relative col-start-1 row-start-1 self-center justify-self-end {disabled
         ? 'text-immich-bg'
         : 'text-immich-fg dark:text-immich-bg'}"
     />
     <select
-      class="immich-form-input w-full appearance-none row-start-1 col-start-1 !pr-6"
+      class="immich-form-input w-full appearance-none row-start-1 col-start-1 !pe-6"
       {disabled}
       aria-describedby={desc ? `${name}-desc` : undefined}
       {name}
diff --git a/web/src/lib/components/shared-components/settings/setting-switch.svelte b/web/src/lib/components/shared-components/settings/setting-switch.svelte
index 29c1f213d3..aa165cfaaa 100644
--- a/web/src/lib/components/shared-components/settings/setting-switch.svelte
+++ b/web/src/lib/components/shared-components/settings/setting-switch.svelte
@@ -33,7 +33,7 @@
 </script>
 
 <div class="flex place-items-center justify-between">
-  <div class="mr-2">
+  <div class="me-2">
     <div class="flex h-[26px] place-items-center gap-1">
       <label class="font-medium text-immich-primary dark:text-immich-dark-primary text-sm" for={sliderId}>
         {title}
diff --git a/web/src/lib/components/shared-components/show-shortcuts.svelte b/web/src/lib/components/shared-components/show-shortcuts.svelte
index cee7545f48..e1454b49df 100644
--- a/web/src/lib/components/shared-components/show-shortcuts.svelte
+++ b/web/src/lib/components/shared-components/show-shortcuts.svelte
@@ -55,7 +55,7 @@
             <div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
               <div class="flex justify-self-end">
                 {#each shortcut.key as key (key)}
-                  <p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
+                  <p class="me-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
                     {key}
                   </p>
                 {/each}
@@ -74,7 +74,7 @@
             <div class="grid grid-cols-[30%_70%] items-center gap-4 pt-4 text-sm">
               <div class="flex justify-self-end">
                 {#each shortcut.key as key (key)}
-                  <p class="mr-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
+                  <p class="me-1 flex items-center justify-center justify-self-end rounded-lg bg-immich-primary/25 p-2">
                     {key}
                   </p>
                 {/each}
diff --git a/web/src/lib/components/shared-components/side-bar/purchase-info.svelte b/web/src/lib/components/shared-components/side-bar/purchase-info.svelte
index 67d3eaf523..d94a0c169e 100644
--- a/web/src/lib/components/shared-components/side-bar/purchase-info.svelte
+++ b/web/src/lib/components/shared-components/side-bar/purchase-info.svelte
@@ -78,7 +78,7 @@
   <LicenseModal onClose={() => (isOpen = false)} />
 {/if}
 
-<div class="license-status pl-4 text-sm">
+<div class="license-status ps-4 text-sm">
   {#if $isPurchased && $preferences.purchase.showSupportBadge}
     <button
       onclick={() => goto(`${AppRoute.USER_SETTINGS}?isOpen=user-purchase-settings`)}
@@ -123,7 +123,7 @@
   {#if showMessage}
     <dialog
       open
-      class="hidden sidebar:block w-[500px] absolute bottom-[75px] left-[255px] bg-gray-50 dark:border-gray-800 border border-gray-200 dark:bg-immich-dark-gray dark:text-white text-black rounded-3xl z-10 shadow-2xl px-8 py-6"
+      class="hidden sidebar:block w-[500px] absolute bottom-[75px] start-[255px] bg-gray-50 dark:border-gray-800 border border-gray-200 dark:bg-immich-dark-gray dark:text-white text-black rounded-3xl z-10 shadow-2xl px-8 py-6"
       transition:fade={{ duration: 150 }}
       onmouseover={() => (hoverMessage = true)}
       onmouseleave={() => (hoverMessage = false)}
diff --git a/web/src/lib/components/shared-components/side-bar/recent-albums.svelte b/web/src/lib/components/shared-components/side-bar/recent-albums.svelte
index 9674ed9366..d121f74fdf 100644
--- a/web/src/lib/components/shared-components/side-bar/recent-albums.svelte
+++ b/web/src/lib/components/shared-components/side-bar/recent-albums.svelte
@@ -27,7 +27,7 @@
   <a
     href={'/albums/' + album.id}
     title={album.albumName}
-    class="flex w-full place-items-center justify-between gap-4 rounded-r-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-immich-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary pl-10 group-hover:sm:px-10 md:px-10"
+    class="flex w-full place-items-center justify-between gap-4 rounded-e-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-immich-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary ps-10 group-hover:sm:px-10 md:px-10"
   >
     <div>
       <div
diff --git a/web/src/lib/components/shared-components/side-bar/server-status.svelte b/web/src/lib/components/shared-components/side-bar/server-status.svelte
index 8ca552a1f4..500bce524e 100644
--- a/web/src/lib/components/shared-components/side-bar/server-status.svelte
+++ b/web/src/lib/components/shared-components/side-bar/server-status.svelte
@@ -42,7 +42,7 @@
 {/if}
 
 <div
-  class="text-sm flex md:flex pl-5 pr-1 place-items-center place-content-center justify-between min-w-52 overflow-hidden"
+  class="text-sm flex md:flex ps-5 pe-1 place-items-center place-content-center justify-between min-w-52 overflow-hidden"
 >
   {#if $connected}
     <div class="flex gap-2 place-items-center place-content-center">
diff --git a/web/src/lib/components/shared-components/side-bar/side-bar-link.svelte b/web/src/lib/components/shared-components/side-bar/side-bar-link.svelte
index f8bf89cd29..1be7356e94 100644
--- a/web/src/lib/components/shared-components/side-bar/side-bar-link.svelte
+++ b/web/src/lib/components/shared-components/side-bar/side-bar-link.svelte
@@ -37,7 +37,7 @@
 
 <div class="relative">
   {#if hasDropdown}
-    <span class="hidden md:block absolute left-1 z-50 h-full">
+    <span class="hidden md:block absolute start-1 z-50 h-full">
       <button
         type="button"
         aria-label={$t('recent-albums')}
@@ -59,12 +59,12 @@
     data-sveltekit-preload-data={preloadData ? 'hover' : 'off'}
     draggable="false"
     aria-current={isSelected ? 'page' : undefined}
-    class="flex w-full place-items-center gap-4 rounded-r-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-immich-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary
+    class="flex w-full place-items-center gap-4 rounded-e-full py-3 transition-[padding] delay-100 duration-100 hover:cursor-pointer hover:bg-immich-gray hover:text-immich-primary dark:text-immich-dark-fg dark:hover:bg-immich-dark-gray dark:hover:text-immich-dark-primary
     {isSelected
       ? 'bg-immich-primary/10 text-immich-primary hover:bg-immich-primary/10 dark:bg-immich-dark-primary/10 dark:text-immich-dark-primary'
       : ''}"
   >
-    <div class="flex w-full place-items-center gap-4 pl-5 overflow-hidden truncate">
+    <div class="flex w-full place-items-center gap-4 ps-5 overflow-hidden truncate">
       <Icon path={icon} size="1.5em" class="shrink-0" flipped={flippedLogo} ariaHidden />
       <span class="text-sm font-medium">{title}</span>
     </div>
diff --git a/web/src/lib/components/shared-components/side-bar/side-bar-section.svelte b/web/src/lib/components/shared-components/side-bar/side-bar-section.svelte
index 74eb7d266b..9ef0bd1b9f 100644
--- a/web/src/lib/components/shared-components/side-bar/side-bar-section.svelte
+++ b/web/src/lib/components/shared-components/side-bar/side-bar-section.svelte
@@ -35,7 +35,7 @@
   tabindex="-1"
   class="immich-scrollbar relative z-10 w-0 sidebar:w-[16rem] overflow-y-auto overflow-x-hidden bg-immich-bg pt-8 transition-all duration-200 dark:bg-immich-dark-bg"
   class:shadow-2xl={isExpanded}
-  class:dark:border-r-immich-dark-gray={isExpanded}
+  class:dark:border-e-immich-dark-gray={isExpanded}
   class:border-r={isExpanded}
   class:w-[min(100vw,16rem)]={sidebarStore.isOpen}
   data-testid="sidebar-parent"
@@ -43,7 +43,7 @@
   use:clickOutside={{ onOutclick: closeSidebar, onEscape: closeSidebar }}
   use:focusTrap={{ active: isExpanded }}
 >
-  <div class="pr-6 flex flex-col gap-1 h-max min-h-full">
+  <div class="pe-6 flex flex-col gap-1 h-max min-h-full">
     {@render children?.()}
   </div>
 </section>
diff --git a/web/src/lib/components/shared-components/side-bar/storage-space.svelte b/web/src/lib/components/shared-components/side-bar/storage-space.svelte
index 604522c4f0..4a40cbc1e7 100644
--- a/web/src/lib/components/shared-components/side-bar/storage-space.svelte
+++ b/web/src/lib/components/shared-components/side-bar/storage-space.svelte
@@ -46,7 +46,7 @@
 </script>
 
 <div
-  class="storage-status p-4 bg-gray-100 dark:bg-immich-dark-primary/10 ml-4 rounded-lg text-sm min-w-52"
+  class="storage-status p-4 bg-gray-100 dark:bg-immich-dark-primary/10 ms-4 rounded-lg text-sm min-w-52"
   title={$t('storage_usage', {
     values: {
       used: getByteUnitString(usedBytes, $locale, 3),
diff --git a/web/src/lib/components/shared-components/tree/breadcrumbs.svelte b/web/src/lib/components/shared-components/tree/breadcrumbs.svelte
index 7da2215a77..a0d4d250f7 100644
--- a/web/src/lib/components/shared-components/tree/breadcrumbs.svelte
+++ b/web/src/lib/components/shared-components/tree/breadcrumbs.svelte
@@ -23,7 +23,7 @@
         icon={mdiArrowUpLeft}
         title={$t('to_parent')}
         href={getLink(pathSegments.slice(0, -1).join('/'))}
-        class="mr-2"
+        class="me-2"
         padding="2"
         onclick={() => {}}
       />
diff --git a/web/src/lib/components/shared-components/tree/tree-items.svelte b/web/src/lib/components/shared-components/tree/tree-items.svelte
index 3b770e47ef..0df71ca605 100644
--- a/web/src/lib/components/shared-components/tree/tree-items.svelte
+++ b/web/src/lib/components/shared-components/tree/tree-items.svelte
@@ -14,7 +14,7 @@
   let { items, parent = '', active = '', icons, getLink, getColor = () => undefined }: Props = $props();
 </script>
 
-<ul class="list-none ml-2">
+<ul class="list-none ms-2">
   <!-- eslint-disable-next-line svelte/require-each-key -->
   {#each Object.entries(items).sort() as [path, tree]}
     {@const value = normalizeTreePath(`${parent}/${path}`)}
diff --git a/web/src/lib/components/shared-components/tree/tree.svelte b/web/src/lib/components/shared-components/tree/tree.svelte
index ded6f70690..463871154d 100644
--- a/web/src/lib/components/shared-components/tree/tree.svelte
+++ b/web/src/lib/components/shared-components/tree/tree.svelte
@@ -31,7 +31,7 @@
 <a
   href={getLink(path)}
   title={value}
-  class={`flex flex-grow place-items-center pl-2 py-1 text-sm rounded-lg hover:bg-slate-200 dark:hover:bg-slate-800 hover:font-semibold ${isTarget ? 'bg-slate-100 dark:bg-slate-700 font-semibold text-immich-primary dark:text-immich-dark-primary' : 'dark:text-gray-200'}`}
+  class={`flex flex-grow place-items-center ps-2 py-1 text-sm rounded-lg hover:bg-slate-200 dark:hover:bg-slate-800 hover:font-semibold ${isTarget ? 'bg-slate-100 dark:bg-slate-700 font-semibold text-immich-primary dark:text-immich-dark-primary' : 'dark:text-gray-200'}`}
   data-sveltekit-keepfocus
 >
   <button type="button" {onclick} class={Object.values(tree).length === 0 ? 'invisible' : ''}>
@@ -45,7 +45,7 @@
       size={20}
     />
   </div>
-  <span class="text-nowrap overflow-hidden text-ellipsis font-mono pl-1 pt-1 whitespace-pre-wrap">{value}</span>
+  <span class="text-nowrap overflow-hidden text-ellipsis font-mono ps-1 pt-1 whitespace-pre-wrap">{value}</span>
 </a>
 
 {#if isOpen}
diff --git a/web/src/lib/components/shared-components/upload-panel.svelte b/web/src/lib/components/shared-components/upload-panel.svelte
index 0eb7d1655c..e7708e5c30 100644
--- a/web/src/lib/components/shared-components/upload-panel.svelte
+++ b/web/src/lib/components/shared-components/upload-panel.svelte
@@ -48,7 +48,7 @@
       }
       uploadAssetsStore.reset();
     }}
-    class="fixed bottom-6 right-16 z-[10000]"
+    class="fixed bottom-6 end-16 z-[10000]"
   >
     {#if showDetail}
       <div
@@ -136,7 +136,7 @@
           type="button"
           in:scale={{ duration: 250, easing: quartInOut }}
           onclick={() => (showDetail = true)}
-          class="absolute -left-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-primary p-5 text-xs text-gray-200"
+          class="absolute -start-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-primary p-5 text-xs text-gray-200"
         >
           {$remainingUploads.toLocaleString($locale)}
         </button>
@@ -145,7 +145,7 @@
             type="button"
             in:scale={{ duration: 250, easing: quartInOut }}
             onclick={() => (showDetail = true)}
-            class="absolute -right-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-error p-5 text-xs text-gray-200"
+            class="absolute -end-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-error p-5 text-xs text-gray-200"
           >
             {$stats.errors.toLocaleString($locale)}
           </button>
diff --git a/web/src/lib/components/user-settings-page/app-settings.svelte b/web/src/lib/components/user-settings-page/app-settings.svelte
index 63209ca289..5b4a19c34f 100644
--- a/web/src/lib/components/user-settings-page/app-settings.svelte
+++ b/web/src/lib/components/user-settings-page/app-settings.svelte
@@ -96,8 +96,8 @@
 
 <section class="my-4">
   <div in:fade={{ duration: 500 }}>
-    <div class="ml-4 mt-4 flex flex-col gap-4">
-      <div class="ml-4">
+    <div class="ms-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4">
         <SettingSwitch
           title={$t('theme_selection')}
           subtitle={$t('theme_selection_description')}
@@ -106,7 +106,7 @@
         />
       </div>
 
-      <div class="ml-4">
+      <div class="ms-4">
         <SettingCombobox
           comboboxPlaceholder={$t('language')}
           selectedOption={langOptions.find(({ value }) => value === closestLanguage) || defaultLangOption}
@@ -117,7 +117,7 @@
         />
       </div>
 
-      <div class="ml-4">
+      <div class="ms-4">
         <SettingSwitch
           title={$t('default_locale')}
           subtitle={$t('default_locale_description')}
@@ -128,7 +128,7 @@
         </SettingSwitch>
       </div>
       {#if $locale !== undefined}
-        <div class="ml-4">
+        <div class="ms-4">
           <SettingCombobox
             comboboxPlaceholder={$t('searching_locales')}
             {selectedOption}
@@ -140,7 +140,7 @@
         </div>
       {/if}
 
-      <div class="ml-4">
+      <div class="ms-4">
         <SettingSwitch
           title={$t('display_original_photos')}
           subtitle={$t('display_original_photos_setting_description')}
@@ -148,7 +148,7 @@
           onToggle={() => ($alwaysLoadOriginalFile = !$alwaysLoadOriginalFile)}
         />
       </div>
-      <div class="ml-4">
+      <div class="ms-4">
         <SettingSwitch
           title={$t('video_hover_setting')}
           subtitle={$t('video_hover_setting_description')}
@@ -156,7 +156,7 @@
           onToggle={() => ($playVideoThumbnailOnHover = !$playVideoThumbnailOnHover)}
         />
       </div>
-      <div class="ml-4">
+      <div class="ms-4">
         <SettingSwitch
           title={$t('loop_videos')}
           subtitle={$t('loop_videos_description')}
@@ -165,7 +165,7 @@
         />
       </div>
 
-      <div class="ml-4">
+      <div class="ms-4">
         <SettingSwitch
           title={$t('permanent_deletion_warning')}
           subtitle={$t('permanent_deletion_warning_setting_description')}
diff --git a/web/src/lib/components/user-settings-page/change-password-settings.svelte b/web/src/lib/components/user-settings-page/change-password-settings.svelte
index dc8cfac42e..2735c4f13e 100644
--- a/web/src/lib/components/user-settings-page/change-password-settings.svelte
+++ b/web/src/lib/components/user-settings-page/change-password-settings.svelte
@@ -44,7 +44,7 @@
 <section class="my-4">
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
         <SettingInputField
           inputType={SettingInputFieldType.PASSWORD}
           label={$t('password')}
diff --git a/web/src/lib/components/user-settings-page/device-card.svelte b/web/src/lib/components/user-settings-page/device-card.svelte
index 74e6579dd0..ad0b621921 100644
--- a/web/src/lib/components/user-settings-page/device-card.svelte
+++ b/web/src/lib/components/user-settings-page/device-card.svelte
@@ -31,7 +31,7 @@
 </script>
 
 <div class="flex w-full flex-row">
-  <div class="hidden items-center justify-center pr-2 text-immich-primary dark:text-immich-dark-primary sm:flex">
+  <div class="hidden items-center justify-center pe-2 text-immich-primary dark:text-immich-dark-primary sm:flex">
     {#if device.deviceOS === 'Android'}
       <Icon path={mdiAndroid} size="40" />
     {:else if device.deviceOS === 'iOS' || device.deviceOS === 'macOS'}
@@ -50,7 +50,7 @@
       <Icon path={mdiHelp} size="40" />
     {/if}
   </div>
-  <div class="flex grow flex-row justify-between gap-1 pl-4 sm:pl-0">
+  <div class="flex grow flex-row justify-between gap-1 ps-4 sm:ps-0">
     <div class="flex flex-col justify-center gap-1 dark:text-white">
       <span class="text-sm">
         {#if device.deviceType || device.deviceOS}
diff --git a/web/src/lib/components/user-settings-page/download-settings.svelte b/web/src/lib/components/user-settings-page/download-settings.svelte
index e2401a8a76..9ed7285aa0 100644
--- a/web/src/lib/components/user-settings-page/download-settings.svelte
+++ b/web/src/lib/components/user-settings-page/download-settings.svelte
@@ -43,7 +43,7 @@
 <section class="my-4">
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
         <SettingInputField
           inputType={SettingInputFieldType.NUMBER}
           label={$t('archive_size')}
diff --git a/web/src/lib/components/user-settings-page/feature-settings.svelte b/web/src/lib/components/user-settings-page/feature-settings.svelte
index 4fdd8f21d7..d331e40432 100644
--- a/web/src/lib/components/user-settings-page/feature-settings.svelte
+++ b/web/src/lib/components/user-settings-page/feature-settings.svelte
@@ -63,14 +63,14 @@
 <section class="my-4">
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4 flex flex-col">
+      <div class="ms-4 mt-4 flex flex-col">
         <SettingAccordion key="folders" title={$t('folders')} subtitle={$t('folders_feature_description')}>
-          <div class="ml-4 mt-6">
+          <div class="ms-4 mt-6">
             <SettingSwitch title={$t('enable')} bind:checked={foldersEnabled} />
           </div>
 
           {#if foldersEnabled}
-            <div class="ml-4 mt-6">
+            <div class="ms-4 mt-6">
               <SettingSwitch
                 title={$t('sidebar')}
                 subtitle={$t('sidebar_display_description')}
@@ -81,18 +81,18 @@
         </SettingAccordion>
 
         <SettingAccordion key="memories" title={$t('time_based_memories')} subtitle={$t('photos_from_previous_years')}>
-          <div class="ml-4 mt-6">
+          <div class="ms-4 mt-6">
             <SettingSwitch title={$t('enable')} bind:checked={memoriesEnabled} />
           </div>
         </SettingAccordion>
 
         <SettingAccordion key="people" title={$t('people')} subtitle={$t('people_feature_description')}>
-          <div class="ml-4 mt-6">
+          <div class="ms-4 mt-6">
             <SettingSwitch title={$t('enable')} bind:checked={peopleEnabled} />
           </div>
 
           {#if peopleEnabled}
-            <div class="ml-4 mt-6">
+            <div class="ms-4 mt-6">
               <SettingSwitch
                 title={$t('sidebar')}
                 subtitle={$t('sidebar_display_description')}
@@ -103,17 +103,17 @@
         </SettingAccordion>
 
         <SettingAccordion key="rating" title={$t('rating')} subtitle={$t('rating_description')}>
-          <div class="ml-4 mt-6">
+          <div class="ms-4 mt-6">
             <SettingSwitch title={$t('enable')} bind:checked={ratingsEnabled} />
           </div>
         </SettingAccordion>
 
         <SettingAccordion key="shared-links" title={$t('shared_links')} subtitle={$t('shared_links_description')}>
-          <div class="ml-4 mt-6">
+          <div class="ms-4 mt-6">
             <SettingSwitch title={$t('enable')} bind:checked={sharedLinksEnabled} />
           </div>
           {#if sharedLinksEnabled}
-            <div class="ml-4 mt-6">
+            <div class="ms-4 mt-6">
               <SettingSwitch
                 title={$t('sidebar')}
                 subtitle={$t('sidebar_display_description')}
@@ -124,11 +124,11 @@
         </SettingAccordion>
 
         <SettingAccordion key="tags" title={$t('tags')} subtitle={$t('tag_feature_description')}>
-          <div class="ml-4 mt-6">
+          <div class="ms-4 mt-6">
             <SettingSwitch title={$t('enable')} bind:checked={tagsEnabled} />
           </div>
           {#if tagsEnabled}
-            <div class="ml-4 mt-6">
+            <div class="ms-4 mt-6">
               <SettingSwitch
                 title={$t('sidebar')}
                 subtitle={$t('sidebar_display_description')}
diff --git a/web/src/lib/components/user-settings-page/notifications-settings.svelte b/web/src/lib/components/user-settings-page/notifications-settings.svelte
index 51a6558f43..fe4b51e20a 100644
--- a/web/src/lib/components/user-settings-page/notifications-settings.svelte
+++ b/web/src/lib/components/user-settings-page/notifications-settings.svelte
@@ -46,14 +46,14 @@
 <section class="my-4">
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" {onsubmit}>
-      <div class="ml-4 mt-4 flex flex-col gap-4">
-        <div class="ml-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
+        <div class="ms-4">
           <SettingSwitch
             title={$t('notification_toggle_setting_description')}
             bind:checked={emailNotificationsEnabled}
           />
         </div>
-        <div class="ml-4">
+        <div class="ms-4">
           <SettingSwitch
             title={$t('album_added')}
             subtitle={$t('album_added_notification_setting_description')}
@@ -61,7 +61,7 @@
             disabled={!emailNotificationsEnabled}
           />
         </div>
-        <div class="ml-4">
+        <div class="ms-4">
           <SettingSwitch
             title={$t('album_updated')}
             subtitle={$t('album_updated_setting_description')}
diff --git a/web/src/lib/components/user-settings-page/partner-selection-modal.svelte b/web/src/lib/components/user-settings-page/partner-selection-modal.svelte
index f5f76e099f..37c6580429 100644
--- a/web/src/lib/components/user-settings-page/partner-selection-modal.svelte
+++ b/web/src/lib/components/user-settings-page/partner-selection-modal.svelte
@@ -54,7 +54,7 @@
             <UserAvatar {user} size="lg" />
           {/if}
 
-          <div class="text-left">
+          <div class="text-start">
             <p class="text-immich-fg dark:text-immich-dark-fg">
               {user.name}
             </p>
diff --git a/web/src/lib/components/user-settings-page/partner-settings.svelte b/web/src/lib/components/user-settings-page/partner-settings.svelte
index 909b9ec517..b2238b84e2 100644
--- a/web/src/lib/components/user-settings-page/partner-settings.svelte
+++ b/web/src/lib/components/user-settings-page/partner-settings.svelte
@@ -130,7 +130,7 @@
         <div class="flex gap-4 rounded-lg pb-4 transition-all justify-between">
           <div class="flex gap-4">
             <UserAvatar user={partner.user} size="md" />
-            <div class="text-left">
+            <div class="text-start">
               <p class="text-immich-fg dark:text-immich-dark-fg">
                 {partner.user.name}
               </p>
diff --git a/web/src/lib/components/user-settings-page/user-api-key-list.svelte b/web/src/lib/components/user-settings-page/user-api-key-list.svelte
index 53e9079bff..6dae502aca 100644
--- a/web/src/lib/components/user-settings-page/user-api-key-list.svelte
+++ b/web/src/lib/components/user-settings-page/user-api-key-list.svelte
@@ -126,7 +126,7 @@
     </div>
 
     {#if keys.length > 0}
-      <table class="w-full text-left">
+      <table class="w-full text-start">
         <thead
           class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
         >
diff --git a/web/src/lib/components/user-settings-page/user-profile-settings.svelte b/web/src/lib/components/user-settings-page/user-profile-settings.svelte
index c36c36d7cc..90487f532f 100644
--- a/web/src/lib/components/user-settings-page/user-profile-settings.svelte
+++ b/web/src/lib/components/user-settings-page/user-profile-settings.svelte
@@ -42,7 +42,7 @@
 <section class="my-4">
   <div in:fade={{ duration: 500 }}>
     <form autocomplete="off" onsubmit={preventDefault(bubble('submit'))}>
-      <div class="ml-4 mt-4 flex flex-col gap-4">
+      <div class="ms-4 mt-4 flex flex-col gap-4">
         <SettingInputField
           inputType={SettingInputFieldType.TEXT}
           label={$t('user_id')}
diff --git a/web/src/lib/components/user-settings-page/user-purchase-settings.svelte b/web/src/lib/components/user-settings-page/user-purchase-settings.svelte
index 64dac133f2..ba8aadd73f 100644
--- a/web/src/lib/components/user-settings-page/user-purchase-settings.svelte
+++ b/web/src/lib/components/user-settings-page/user-purchase-settings.svelte
@@ -122,7 +122,7 @@
       <!-- PRODUCT KEY INFO CARD -->
       {#if isServerProduct}
         <div
-          class="bg-gray-50 border border-immich-dark-primary/20 dark:bg-immich-dark-primary/15 p-6 pr-12 rounded-xl flex place-content-center gap-4"
+          class="bg-gray-50 border border-immich-dark-primary/20 dark:bg-immich-dark-primary/15 p-6 pe-12 rounded-xl flex place-content-center gap-4"
         >
           <Icon path={mdiKey} size="56" class="text-immich-primary dark:text-immich-dark-primary" />
 
@@ -152,7 +152,7 @@
         {/if}
       {:else}
         <div
-          class="bg-gray-50 border border-immich-dark-primary/20 dark:bg-immich-dark-primary/15 p-6 pr-12 rounded-xl flex place-content-center gap-4"
+          class="bg-gray-50 border border-immich-dark-primary/20 dark:bg-immich-dark-primary/15 p-6 pe-12 rounded-xl flex place-content-center gap-4"
         >
           <Icon path={mdiKey} size="56" class="text-immich-primary dark:text-immich-dark-primary" />
 
diff --git a/web/src/lib/components/user-settings-page/user-usage-statistic.svelte b/web/src/lib/components/user-settings-page/user-usage-statistic.svelte
index f7de1d8f64..ad77516d55 100644
--- a/web/src/lib/components/user-settings-page/user-usage-statistic.svelte
+++ b/web/src/lib/components/user-settings-page/user-usage-statistic.svelte
@@ -68,7 +68,7 @@
 <section class="my-6">
   <p class="text-xs dark:text-white uppercase">{$t('photos_and_videos')}</p>
   <div class="overflow-x-auto">
-    <table class="w-full text-left mt-4">
+    <table class="w-full text-start mt-4">
       <thead
         class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
       >
@@ -92,7 +92,7 @@
     <p class="text-xs dark:text-white uppercase">{$t('albums')}</p>
   </div>
   <div class="overflow-x-auto">
-    <table class="w-full text-left mt-4">
+    <table class="w-full text-start mt-4">
       <thead
         class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
       >
diff --git a/web/src/lib/components/utilities-page/duplicates/duplicate-asset.svelte b/web/src/lib/components/utilities-page/duplicates/duplicate-asset.svelte
index 97f44e3ec4..b8409cb0ef 100644
--- a/web/src/lib/components/utilities-page/duplicates/duplicate-asset.svelte
+++ b/web/src/lib/components/utilities-page/duplicates/duplicate-asset.svelte
@@ -44,14 +44,14 @@
 
       <!-- FAVORITE ICON -->
       {#if asset.isFavorite}
-        <div class="absolute bottom-2 left-2">
+        <div class="absolute bottom-2 start-2">
           <Icon path={mdiHeart} size="24" class="text-white" />
         </div>
       {/if}
 
       <!-- OVERLAY CHIP -->
       <div
-        class="absolute bottom-1 right-3 px-4 py-1 rounded-xl text-xs transition-colors {isSelected
+        class="absolute bottom-1 end-3 px-4 py-1 rounded-xl text-xs transition-colors {isSelected
           ? 'bg-green-400/90'
           : 'bg-red-300/90'}"
       >
@@ -59,7 +59,7 @@
       </div>
 
       <!-- EXTERNAL LIBRARY / STACK COUNT CHIP -->
-      <div class="absolute top-2 right-3">
+      <div class="absolute top-2 end-3">
         {#if isFromExternalLibrary}
           <div class="bg-immich-primary/90 px-2 py-1 rounded-xl text-xs text-white">
             {$t('external')}
@@ -68,7 +68,7 @@
         {#if asset.stack?.assetCount}
           <div class="bg-immich-primary/90 px-2 py-1 my-0.5 rounded-xl text-xs text-white">
             <div class="flex items-center justify-center">
-              <div class="mr-1">{asset.stack.assetCount}</div>
+              <div class="me-1">{asset.stack.assetCount}</div>
               <Icon path={mdiImageMultipleOutline} size="18" />
             </div>
           </div>
@@ -79,7 +79,7 @@
     <button
       type="button"
       onclick={() => onViewAsset(asset)}
-      class="absolute rounded-full top-1 left-1 text-gray-200 p-2 hover:text-white bg-black/35 hover:bg-black/50"
+      class="absolute rounded-full top-1 start-1 text-gray-200 p-2 hover:text-white bg-black/35 hover:bg-black/50"
       title={$t('view')}
     >
       <Icon ariaLabel={$t('view')} path={mdiMagnifyPlus} flipped size="18" />
diff --git a/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte b/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte
index b7cd9aa240..d383ed1b10 100644
--- a/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte
+++ b/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte
@@ -130,12 +130,12 @@
     <div class="flex text-xs text-black">
       <button
         type="button"
-        class="px-4 py-3 flex place-items-center gap-2 rounded-tl-full rounded-bl-full dark:bg-immich-dark-primary hover:dark:bg-immich-dark-primary/90 bg-immich-primary/25 hover:bg-immich-primary/50"
+        class="px-4 py-3 flex place-items-center gap-2 rounded-s-full dark:bg-immich-dark-primary hover:dark:bg-immich-dark-primary/90 bg-immich-primary/25 hover:bg-immich-primary/50"
         onclick={onSelectAll}><Icon path={mdiCheck} size="20" />{$t('select_keep_all')}</button
       >
       <button
         type="button"
-        class="px-4 py-3 flex place-items-center gap-2 rounded-tr-full rounded-br-full dark:bg-immich-dark-primary/50 hover:dark:bg-immich-dark-primary/70 bg-immich-primary hover:bg-immich-primary/80 text-white"
+        class="px-4 py-3 flex place-items-center gap-2 rounded-e-full dark:bg-immich-dark-primary/50 hover:dark:bg-immich-dark-primary/70 bg-immich-primary hover:bg-immich-primary/80 text-white"
         onclick={onSelectNone}><Icon path={mdiTrashCanOutline} size="20" />{$t('select_trash_all')}</button
       >
     </div>
@@ -143,21 +143,11 @@
     <!-- CONFIRM BUTTONS -->
     <div class="flex text-xs text-black">
       {#if trashCount === 0}
-        <Button
-          size="sm"
-          color="primary"
-          class="flex place-items-center rounded-tl-full rounded-bl-full gap-2"
-          onclick={handleResolve}
-        >
+        <Button size="sm" color="primary" class="flex place-items-center rounded-s-full gap-2" onclick={handleResolve}>
           <Icon path={mdiCheck} size="20" />{$t('keep_all')}
         </Button>
       {:else}
-        <Button
-          size="sm"
-          color="red"
-          class="flex place-items-center rounded-tl-full rounded-bl-full gap-2 py-3"
-          onclick={handleResolve}
-        >
+        <Button size="sm" color="red" class="flex place-items-center rounded-s-full gap-2 py-3" onclick={handleResolve}>
           <Icon path={mdiTrashCanOutline} size="20" />{trashCount === assets.length
             ? $t('trash_all')
             : $t('trash_count', { values: { count: trashCount } })}
@@ -166,7 +156,7 @@
       <Button
         size="sm"
         color="primary"
-        class="flex place-items-center rounded-tr-full rounded-br-full  gap-2"
+        class="flex place-items-center rounded-e-full  gap-2"
         onclick={handleStack}
         disabled={selectedAssetIds.size !== 1}
       >
diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts
index 2c21d04865..f5b044cd41 100644
--- a/web/src/lib/constants.ts
+++ b/web/src/lib/constants.ts
@@ -268,8 +268,8 @@ export const defaultLang = { name: 'English', code: 'en', loader: () => import('
 
 export const langs = [
   { name: 'Afrikaans', code: 'af', loader: () => import('$i18n/af.json') },
-  { name: 'Arabic', code: 'ar', loader: () => import('$i18n/ar.json') },
-  { name: 'Azerbaijani', code: 'az', loader: () => import('$i18n/az.json') },
+  { name: 'Arabic', code: 'ar', loader: () => import('$i18n/ar.json'), rtl: true },
+  { name: 'Azerbaijani', code: 'az', loader: () => import('$i18n/az.json'), rtl: true },
   { name: 'Belarusian', code: 'be', loader: () => import('$i18n/be.json') },
   { name: 'Bulgarian', code: 'bg', loader: () => import('$i18n/bg.json') },
   { name: 'Bislama', code: 'bi', loader: () => import('$i18n/bi.json') },
@@ -284,12 +284,12 @@ export const langs = [
   { name: 'Spanish', code: 'es', loader: () => import('$i18n/es.json') },
   { name: 'Estonian', code: 'et', loader: () => import('$i18n/et.json') },
   { name: 'Basque', code: 'eu', loader: () => import('$i18n/eu.json') },
-  { name: 'Persian', code: 'fa', loader: () => import('$i18n/fa.json') },
+  { name: 'Persian', code: 'fa', loader: () => import('$i18n/fa.json'), rtl: true },
   { name: 'Finnish', code: 'fi', loader: () => import('$i18n/fi.json') },
   { name: 'Filipino', code: 'fil', loader: () => import('$i18n/fil.json') },
   { name: 'French', code: 'fr', loader: () => import('$i18n/fr.json') },
   { name: 'Galician', code: 'gl', loader: () => import('$i18n/gl.json') },
-  { name: 'Hebrew', code: 'he', loader: () => import('$i18n/he.json') },
+  { name: 'Hebrew', code: 'he', loader: () => import('$i18n/he.json'), rtl: true },
   { name: 'Hindi', code: 'hi', loader: () => import('$i18n/hi.json') },
   { name: 'Croatian', code: 'hr', loader: () => import('$i18n/hr.json') },
   { name: 'Hungarian', code: 'hu', loader: () => import('$i18n/hu.json') },
@@ -299,7 +299,7 @@ export const langs = [
   { name: 'Japanese', code: 'ja', loader: () => import('$i18n/ja.json') },
   { name: 'Georgian', code: 'ka', loader: () => import('$i18n/ka.json') },
   { name: 'Kazakh', code: 'kk', loader: () => import('$i18n/kk.json') },
-  { name: 'Kurdish (Northern)', code: 'kmr', loader: () => import('$i18n/kmr.json') },
+  { name: 'Kurdish (Northern)', code: 'kmr', loader: () => import('$i18n/kmr.json'), rtl: true },
   { name: 'Kannada', code: 'kn', loader: () => import('$i18n/kn.json') },
   { name: 'Korean', code: 'ko', loader: () => import('$i18n/ko.json') },
   { name: 'Luxembourgish', code: 'lb', loader: () => import('$i18n/lb.json') },
@@ -335,7 +335,7 @@ export const langs = [
   { name: 'Thai', code: 'th', loader: () => import('$i18n/th.json') },
   { name: 'Turkish', code: 'tr', loader: () => import('$i18n/tr.json') },
   { name: 'Ukrainian', code: 'uk', loader: () => import('$i18n/uk.json') },
-  { name: 'Urdu', code: 'ur', loader: () => import('$i18n/ur.json') },
+  { name: 'Urdu', code: 'ur', loader: () => import('$i18n/ur.json'), rtl: true },
   { name: 'Vietnamese', code: 'vi', loader: () => import('$i18n/vi.json') },
   {
     name: 'Chinese (Traditional)',
diff --git a/web/src/lib/stores/event-manager.svelte.ts b/web/src/lib/stores/event-manager.svelte.ts
index 09e9b45c3c..335c5cbefb 100644
--- a/web/src/lib/stores/event-manager.svelte.ts
+++ b/web/src/lib/stores/event-manager.svelte.ts
@@ -51,4 +51,5 @@ class EventManager<EventMap extends Record<string, unknown[]>> {
 export const eventManager = new EventManager<{
   'user.login': [];
   'auth.logout': [];
+  'language.change': [{ name: string; code: string; rtl?: boolean }];
 }>();
diff --git a/web/src/lib/stores/language-manager.svelte.ts b/web/src/lib/stores/language-manager.svelte.ts
new file mode 100644
index 0000000000..70b3245e0f
--- /dev/null
+++ b/web/src/lib/stores/language-manager.svelte.ts
@@ -0,0 +1,21 @@
+import { langs } from '$lib/constants';
+import { eventManager } from '$lib/stores/event-manager.svelte';
+
+class LanguageManager {
+  rtl = $state(false);
+
+  setLanguage(code: string) {
+    const item = langs.find((item) => item.code === code);
+    if (!item) {
+      return;
+    }
+
+    this.rtl = item.rtl ?? false;
+
+    document.body.setAttribute('dir', item.rtl ? 'rtl' : 'ltr');
+
+    eventManager.emit('language.change', item);
+  }
+}
+
+export const languageManager = new LanguageManager();
diff --git a/web/src/lib/utils/album-utils.ts b/web/src/lib/utils/album-utils.ts
index 74a2e3c5e9..a5b830774c 100644
--- a/web/src/lib/utils/album-utils.ts
+++ b/web/src/lib/utils/album-utils.ts
@@ -59,7 +59,7 @@ export const sortOptionsMetadata: AlbumSortOptionMetadata[] = [
   {
     id: AlbumSortBy.Title,
     defaultOrder: SortOrder.Asc,
-    columnStyle: 'text-left w-8/12 sm:w-4/12 md:w-4/12 md:w-4/12 xl:w-[30%] 2xl:w-[40%]',
+    columnStyle: 'text-start w-8/12 sm:w-4/12 md:w-4/12 xl:w-[30%] 2xl:w-[40%]',
   },
   {
     id: AlbumSortBy.ItemCount,
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 818f48fe4a..767902bd94 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
@@ -708,7 +708,7 @@
       </AssetGrid>
 
       {#if showActivityStatus}
-        <div class="absolute z-[2] bottom-0 right-0 mb-6 mr-6 justify-self-end">
+        <div class="absolute z-[2] bottom-0 end-0 mb-6 me-6 justify-self-end">
           <ActivityStatus
             disabled={!album.isActivityEnabled}
             {isLiked}
@@ -725,7 +725,7 @@
       <div
         transition:fly={{ duration: 150 }}
         id="activity-panel"
-        class="z-[2] w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg"
+        class="z-[2] w-[360px] md:w-[460px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-s-immich-dark-gray dark:bg-immich-dark-bg"
         translate="yes"
       >
         <ActivityViewer
diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte
index ec62d5e869..49d38d0815 100644
--- a/web/src/routes/(user)/explore/+page.svelte
+++ b/web/src/routes/(user)/explore/+page.svelte
@@ -48,7 +48,7 @@
         <p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('people')}</p>
         <a
           href={AppRoute.PEOPLE}
-          class="pr-4 text-sm font-medium hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
+          class="pe-4 text-sm font-medium hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
           draggable="false">{$t('view_all')}</a
         >
       </div>
@@ -64,7 +64,7 @@
                 widthStyle="100%"
               />
               {#if person.isFavorite}
-                <div class="absolute top-2 left-2">
+                <div class="absolute top-2 start-2">
                   <Icon path={mdiHeart} size="24" class="text-white" />
                 </div>
               {/if}
@@ -82,7 +82,7 @@
         <p class="mb-4 font-medium dark:text-immich-dark-fg">{$t('places')}</p>
         <a
           href={AppRoute.PLACES}
-          class="pr-4 text-sm font-medium hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
+          class="pe-4 text-sm font-medium hover:text-immich-primary dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
           draggable="false">{$t('view_all')}</a
         >
       </div>
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 f5a4f6cd09..d1fc0bcd90 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
@@ -87,7 +87,7 @@
 </script>
 
 {#if assetInteraction.selectionActive}
-  <div class="fixed z-[910] top-0 left-0 w-full">
+  <div class="fixed z-[910] top-0 start-0 w-full">
     <AssetSelectControlBar
       assets={assetInteraction.selectedAssets}
       clearSelect={() => cancelMultiselect(assetInteraction)}
@@ -133,7 +133,7 @@
     <SideBarSection>
       <SkipLink target={`#${headerId}`} text={$t('skip_to_folders')} breakpoint="md" />
       <section>
-        <div class="text-xs pl-4 mb-2 dark:text-white">{$t('explorer').toUpperCase()}</div>
+        <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 }}
diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte
index 3e1b5a774c..fd632fccce 100644
--- a/web/src/routes/(user)/people/+page.svelte
+++ b/web/src/routes/(user)/people/+page.svelte
@@ -458,7 +458,7 @@
   <dialog
     open
     transition:fly={{ y: innerHeight, duration: 150, easing: quintOut, opacity: 0 }}
-    class="absolute left-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
+    class="absolute start-0 top-0 z-[9999] h-full w-full bg-immich-bg dark:bg-immich-dark-bg"
     aria-modal="true"
     aria-labelledby="manage-visibility-title"
     use:focusTrap
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 36b59e3ace..4c58f52265 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
@@ -486,7 +486,7 @@
 </header>
 
 <main
-  class="relative h-dvh overflow-hidden bg-immich-bg tall:ml-4 md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg"
+  class="relative h-dvh overflow-hidden bg-immich-bg tall:ms-4 md:pt-[var(--navbar-height-md)] pt-[var(--navbar-height)] dark:bg-immich-dark-bg"
   use:scrollMemoryClearer={{
     routeStartsWith: AppRoute.PEOPLE,
     beforeClear: () => {
@@ -542,7 +542,7 @@
                     heightStyle="3.375rem"
                   />
                   <div
-                    class="flex flex-col justify-center text-left px-4 text-immich-primary dark:text-immich-dark-primary"
+                    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">
@@ -598,7 +598,7 @@
                         widthStyle="2rem"
                         heightStyle="2rem"
                       />
-                      <p class="ml-4 text-gray-700 dark:text-gray-100">{person.name}</p>
+                      <p class="ms-4 text-gray-700 dark:text-gray-100">{person.name}</p>
                     </button>
                   {/each}
                 </div>
diff --git a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte
index c985104e3c..9d427e1ea7 100644
--- a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -251,7 +251,7 @@
 
 <section>
   {#if assetInteraction.selectionActive}
-    <div class="fixed z-[100] top-0 left-0 w-full">
+    <div class="fixed z-[100] top-0 start-0 w-full">
       <AssetSelectControlBar
         assets={assetInteraction.selectedAssets}
         clearSelect={() => cancelMultiselect(assetInteraction)}
@@ -289,13 +289,13 @@
       </AssetSelectControlBar>
     </div>
   {:else}
-    <div class="fixed z-[100] top-0 left-0 w-full">
+    <div class="fixed z-[100] top-0 start-0 w-full">
       <ControlAppBar onClose={() => goto(previousRoute)} backIcon={mdiArrowLeft}>
         <div
           class="-z-[1] bg-immich-bg dark:bg-immich-dark-bg"
           style="position:absolute;top:0;left:0;right:0;bottom:0;"
         ></div>
-        <div class="w-full flex-1 pl-4">
+        <div class="w-full flex-1 ps-4">
           <SearchBar grayTheme={false} value={terms?.query ?? ''} searchQuery={terms} />
         </div>
       </ControlAppBar>
@@ -313,13 +313,13 @@
       <div class="flex place-content-center place-items-center text-xs">
         <div
           class="bg-immich-primary py-2 px-4 text-white dark:text-black dark:bg-immich-dark-primary
-          {value === true ? 'rounded-full' : 'rounded-tl-full rounded-bl-full'}"
+          {value === true ? 'rounded-full' : 'roudned-s-full'}"
         >
           {getHumanReadableSearchKey(key as keyof SearchTerms)}
         </div>
 
         {#if value !== true}
-          <div class="bg-gray-300 py-2 px-4 dark:bg-gray-800 dark:text-white rounded-tr-full rounded-br-full">
+          <div class="bg-gray-300 py-2 px-4 dark:bg-gray-800 dark:text-white rounded-e-full">
             {#if (key === 'takenAfter' || key === 'takenBefore') && typeof value === 'string'}
               {getHumanReadableDate(value)}
             {:else if key === 'personIds' && Array.isArray(value)}
@@ -349,7 +349,7 @@
 >
   {#if searchResultAlbums.length > 0}
     <section>
-      <div class="ml-6 text-4xl font-medium text-black/70 dark:text-white/80">{$t('albums').toUpperCase()}</div>
+      <div class="ms-6 text-4xl font-medium text-black/70 dark:text-white/80">{$t('albums').toUpperCase()}</div>
       <AlbumCardGroup albums={searchResultAlbums} showDateRange showItemCount />
 
       <div class="m-6 text-4xl font-medium text-black/70 dark:text-white/80">
diff --git a/web/src/routes/(user)/sharing/+page.svelte b/web/src/routes/(user)/sharing/+page.svelte
index e3d6ac1ced..a55452b5d1 100644
--- a/web/src/routes/(user)/sharing/+page.svelte
+++ b/web/src/routes/(user)/sharing/+page.svelte
@@ -68,7 +68,7 @@
               class="flex gap-4 rounded-lg px-5 py-4 transition-all hover:bg-gray-200 dark:hover:bg-gray-700"
             >
               <UserAvatar user={partner} size="lg" />
-              <div class="text-left">
+              <div class="text-start">
                 <p class="text-immich-fg dark:text-immich-dark-fg">
                   {partner.name}
                 </p>
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 8bb43676e8..8d33a2eb6e 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
@@ -146,7 +146,7 @@
     <SideBarSection>
       <SkipLink target={`#${headerId}`} text={$t('skip_to_tags')} breakpoint="md" />
       <section>
-        <div class="text-xs pl-4 mb-2 dark:text-white">{$t('explorer').toUpperCase()}</div>
+        <div class="text-xs ps-4 mb-2 dark:text-white">{$t('explorer').toUpperCase()}</div>
         <div class="h-full">
           <TreeItems
             icons={{ default: mdiTag, active: mdiTag }}
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte
index f3b3930e12..6df1010760 100644
--- a/web/src/routes/+layout.svelte
+++ b/web/src/routes/+layout.svelte
@@ -11,7 +11,8 @@
   import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
   import VersionAnnouncementBox from '$lib/components/shared-components/version-announcement-box.svelte';
   import { Theme } from '$lib/constants';
-  import { colorTheme, handleToggleTheme, type ThemeSetting } from '$lib/stores/preferences.store';
+  import { languageManager } from '$lib/stores/language-manager.svelte';
+  import { colorTheme, handleToggleTheme, lang, type ThemeSetting } from '$lib/stores/preferences.store';
   import { serverConfig } from '$lib/stores/server-config.store';
   import { user } from '$lib/stores/user.store';
   import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket';
@@ -66,6 +67,8 @@
     element?.remove();
     // if the browser theme changes, changes the Immich theme too
     globalThis.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', handleChangeTheme);
+
+    return lang.subscribe((lang) => languageManager.setLanguage(lang));
   });
 
   onDestroy(() => {
diff --git a/web/src/routes/admin/jobs-status/+page.svelte b/web/src/routes/admin/jobs-status/+page.svelte
index 21381081e0..07757614e5 100644
--- a/web/src/routes/admin/jobs-status/+page.svelte
+++ b/web/src/routes/admin/jobs-status/+page.svelte
@@ -107,7 +107,7 @@
   >
     {#snippet promptSnippet()}
       <form {onsubmit} autocomplete="off" id="create-tag-form" class="w-full">
-        <div class="flex flex-col gap-1 text-left">
+        <div class="flex flex-col gap-1 text-start">
           <Combobox
             bind:selectedOption={selectedJob}
             label={$t('jobs')}
diff --git a/web/src/routes/admin/library-management/+page.svelte b/web/src/routes/admin/library-management/+page.svelte
index 9a598101e1..23ebcfbf0b 100644
--- a/web/src/routes/admin/library-management/+page.svelte
+++ b/web/src/routes/admin/library-management/+page.svelte
@@ -282,7 +282,7 @@
   <section class="my-4">
     <div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
       {#if libraries.length > 0}
-        <table class="w-full text-left">
+        <table class="w-full text-start">
           <thead
             class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
           >
@@ -369,7 +369,7 @@
               {/if}
               {#if editScanSettings === index}
                 <!-- svelte-ignore node_invalid_placement_ssr -->
-                <div transition:slide={{ duration: 250 }} class="mb-4 ml-4 mr-4">
+                <div transition:slide={{ duration: 250 }} class="mb-4 ms-4 me-4">
                   <LibraryScanSettingsForm
                     {library}
                     onSubmit={handleUpdate}
diff --git a/web/src/routes/admin/repair/+page.svelte b/web/src/routes/admin/repair/+page.svelte
index 635a140452..b04d8f1944 100644
--- a/web/src/routes/admin/repair/+page.svelte
+++ b/web/src/routes/admin/repair/+page.svelte
@@ -228,7 +228,7 @@
         </div>
       {:else}
         <div class="gap-2">
-          <table class="table-fixed mt-5 w-full text-left">
+          <table class="table-fixed mt-5 w-full text-start">
             <thead
               class="mb-4 flex w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
             >
@@ -265,7 +265,7 @@
             </tbody>
           </table>
 
-          <table class="table-fixed mt-5 w-full text-left">
+          <table class="table-fixed mt-5 w-full text-start">
             <thead
               class="mb-4 flex w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
             >
@@ -295,7 +295,7 @@
                   <td onclick={() => copyToClipboard(orphan.pathValue)}>
                     <CircleIconButton title={$t('copy_file_path')} icon={mdiContentCopy} size="18" onclick={() => {}} />
                   </td>
-                  <td class="truncate text-sm font-mono text-left" title={orphan.pathValue}>
+                  <td class="truncate text-sm font-mono text-start" title={orphan.pathValue}>
                     {orphan.pathValue}
                   </td>
                   <td class="text-sm font-mono">
@@ -306,7 +306,7 @@
             </tbody>
           </table>
 
-          <table class="table-fixed mt-5 w-full text-left max-h-[300px]">
+          <table class="table-fixed mt-5 w-full text-start max-h-[300px]">
             <thead
               class="mb-4 flex w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
             >
@@ -337,11 +337,11 @@
                   <td onclick={() => copyToClipboard(extra.filename)}>
                     <CircleIconButton title={$t('copy_file_path')} icon={mdiContentCopy} size="18" onclick={() => {}} />
                   </td>
-                  <td class="w-full text-md text-ellipsis flex justify-between pr-5">
-                    <span class="text-ellipsis grow truncate font-mono text-sm pr-5" title={extra.filename}
+                  <td class="w-full text-md text-ellipsis flex justify-between pe-5">
+                    <span class="text-ellipsis grow truncate font-mono text-sm pe-5" title={extra.filename}
                       >{extra.filename}</span
                     >
-                    <span class="text-sm font-mono dark:text-immich-dark-primary text-immich-primary pr-5">
+                    <span class="text-sm font-mono dark:text-immich-dark-primary text-immich-primary pes-5">
                       {#if extra.checksum}
                         [sha1:{extra.checksum}]
                       {/if}
diff --git a/web/src/routes/admin/user-management/+page.svelte b/web/src/routes/admin/user-management/+page.svelte
index 0ca17c4ed8..a25799588a 100644
--- a/web/src/routes/admin/user-management/+page.svelte
+++ b/web/src/routes/admin/user-management/+page.svelte
@@ -180,7 +180,7 @@
         </ConfirmDialog>
       {/if}
 
-      <table class="my-5 w-full text-left">
+      <table class="my-5 w-full text-start">
         <thead
           class="mb-4 flex h-12 w-full rounded-md border bg-gray-50 text-immich-primary dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-primary"
         >
diff --git a/web/src/routes/auth/login/+page.svelte b/web/src/routes/auth/login/+page.svelte
index c3d01b3c56..aa756ac2e8 100644
--- a/web/src/routes/auth/login/+page.svelte
+++ b/web/src/routes/auth/login/+page.svelte
@@ -132,7 +132,7 @@
           <div class="inline-flex w-full items-center justify-center my-4">
             <hr class="my-4 h-px w-3/4 border-0 bg-gray-200 dark:bg-gray-600" />
             <span
-              class="absolute left-1/2 -translate-x-1/2 bg-gray-50 px-3 font-medium text-gray-900 dark:bg-neutral-900 dark:text-white"
+              class="absolute start-1/2 -translate-x-1/2 bg-gray-50 px-3 font-medium text-gray-900 dark:bg-neutral-900 dark:text-white"
             >
               {$t('or').toUpperCase()}
             </span>