diff --git a/frontend/src/components/ContextMenu.vue b/frontend/src/components/ContextMenu.vue
new file mode 100644
index 0000000000..14663fd9b5
--- /dev/null
+++ b/frontend/src/components/ContextMenu.vue
@@ -0,0 +1,47 @@
+<template>
+  <div
+    class="context-menu"
+    ref="contextMenu"
+    v-show="show"
+    :style="{
+      top: `${props.pos.y}px`,
+      left: `${left}px`,
+    }"
+  >
+    <slot />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, watch, computed, onUnmounted } from "vue";
+
+const emit = defineEmits(["hide"]);
+const props = defineProps<{ show: boolean; pos: { x: number; y: number } }>();
+const contextMenu = ref<HTMLElement | null>(null);
+
+const left = computed(() => {
+  return Math.min(
+    props.pos.x,
+    window.innerWidth - (contextMenu.value?.clientWidth ?? 0)
+  );
+});
+
+const hideContextMenu = () => {
+  emit("hide");
+};
+
+watch(
+  () => props.show,
+  (val) => {
+    if (val) {
+      document.addEventListener("click", hideContextMenu);
+    } else {
+      document.removeEventListener("click", hideContextMenu);
+    }
+  }
+);
+
+onUnmounted(() => {
+  document.removeEventListener("click", hideContextMenu);
+});
+</script>
diff --git a/frontend/src/components/files/ListingItem.vue b/frontend/src/components/files/ListingItem.vue
index 75326f4cfc..932bc4b1dd 100644
--- a/frontend/src/components/files/ListingItem.vue
+++ b/frontend/src/components/files/ListingItem.vue
@@ -8,6 +8,7 @@
     @dragover="dragOver"
     @drop="drop"
     @click="itemClick"
+    @contextmenu="contextMenu"
     @mousedown="handleMouseDown"
     @mouseup="handleMouseUp"
     @mouseleave="handleMouseLeave"
@@ -220,6 +221,17 @@ const itemClick = (event: Event | KeyboardEvent) => {
   else click(event);
 };
 
+const contextMenu = (event: MouseEvent) => {
+  event.preventDefault();
+  if (
+    fileStore.selected.length === 0 ||
+    event.ctrlKey ||
+    fileStore.selected.indexOf(props.index) === -1
+  ) {
+    click(event);
+  }
+};
+
 const click = (event: Event | KeyboardEvent) => {
   if (!singleClick.value && fileStore.selectedCount !== 0)
     event.preventDefault();
diff --git a/frontend/src/components/prompts/Move.vue b/frontend/src/components/prompts/Move.vue
index 6591d09d38..0cb0d59946 100644
--- a/frontend/src/components/prompts/Move.vue
+++ b/frontend/src/components/prompts/Move.vue
@@ -5,6 +5,7 @@
     </div>
 
     <div class="card-content">
+      <p>{{ $t("prompts.moveMessage") }}</p>
       <file-list
         ref="fileList"
         @update:selected="(val) => (dest = val)"
diff --git a/frontend/src/css/context-menu.css b/frontend/src/css/context-menu.css
new file mode 100644
index 0000000000..cd7913641c
--- /dev/null
+++ b/frontend/src/css/context-menu.css
@@ -0,0 +1,17 @@
+.context-menu {
+  position: absolute;
+  background: var(--surfacePrimary);
+  min-width: 180px;
+  max-width: 220px;
+  border: 1px solid var(--borderSecondary);
+  box-shadow: 0 2px 4px var(--borderPrimary);
+  z-index: 999;
+}
+
+.context-menu .action {
+  display: block;
+  width: 100%;
+  border-radius: 0;
+  display: flex;
+  align-items: center;
+}
\ No newline at end of file
diff --git a/frontend/src/css/styles.css b/frontend/src/css/styles.css
index 19b94b955b..024b48866c 100644
--- a/frontend/src/css/styles.css
+++ b/frontend/src/css/styles.css
@@ -17,6 +17,7 @@
 @import "./mobile.css";
 @import "./epubReader.css";
 @import "./mdPreview.css";
+@import "./context-menu.css";
 
 /* For testing only
  :focus {
@@ -456,4 +457,4 @@ html[dir="rtl"] .card-content .small + input {
 html[dir="rtl"] .card.floating .card-content .file-list {
   direction: ltr;
   text-align: left;
-}
+}
\ No newline at end of file
diff --git a/frontend/src/views/files/FileListing.vue b/frontend/src/views/files/FileListing.vue
index 8fa48f72e0..e5e929e37a 100644
--- a/frontend/src/views/files/FileListing.vue
+++ b/frontend/src/views/files/FileListing.vue
@@ -208,7 +208,10 @@
         <h2 v-if="fileStore.req?.numDirs ?? false">
           {{ t("files.folders") }}
         </h2>
-        <div v-if="fileStore.req?.numDirs ?? false">
+        <div
+          v-if="fileStore.req?.numDirs ?? false"
+          @contextmenu="showContextMenu"
+        >
           <item
             v-for="item in dirs"
             :key="base64(item.name)"
@@ -224,8 +227,13 @@
           </item>
         </div>
 
-        <h2 v-if="fileStore.req?.numFiles ?? false">{{ t("files.files") }}</h2>
-        <div v-if="fileStore.req?.numFiles ?? false">
+        <h2 v-if="fileStore.req?.numFiles ?? false">
+          {{ t("files.files") }}
+        </h2>
+        <div
+          v-if="fileStore.req?.numFiles ?? false"
+          @contextmenu="showContextMenu"
+        >
           <item
             v-for="item in files"
             :key="base64(item.name)"
@@ -240,6 +248,53 @@
           >
           </item>
         </div>
+        <context-menu
+          :show="isContextMenuVisible"
+          :pos="contextMenuPos"
+          @hide="hideContextMenu"
+        >
+          <action
+            v-if="headerButtons.share"
+            icon="share"
+            :label="t('buttons.share')"
+            show="share"
+          />
+          <action
+            v-if="headerButtons.rename"
+            icon="mode_edit"
+            :label="t('buttons.rename')"
+            show="rename"
+          />
+          <action
+            v-if="headerButtons.copy"
+            id="copy-button"
+            icon="content_copy"
+            :label="t('buttons.copyFile')"
+            show="copy"
+          />
+          <action
+            v-if="headerButtons.move"
+            id="move-button"
+            icon="forward"
+            :label="t('buttons.moveFile')"
+            show="move"
+          />
+          <action
+            v-if="headerButtons.delete"
+            id="delete-button"
+            icon="delete"
+            :label="t('buttons.delete')"
+            show="delete"
+          />
+          <action
+            v-if="headerButtons.download"
+            icon="file_download"
+            :label="t('buttons.download')"
+            @action="download"
+            :counter="fileStore.selectedCount"
+          />
+          <action icon="info" :label="t('buttons.info')" show="info" />
+        </context-menu>
 
         <input
           style="display: none"
@@ -292,6 +347,7 @@ import HeaderBar from "@/components/header/HeaderBar.vue";
 import Action from "@/components/header/Action.vue";
 import Search from "@/components/Search.vue";
 import Item from "@/components/files/ListingItem.vue";
+import ContextMenu from "@/components/ContextMenu.vue";
 import {
   computed,
   inject,
@@ -310,6 +366,8 @@ const columnWidth = ref<number>(280);
 const dragCounter = ref<number>(0);
 const width = ref<number>(window.innerWidth);
 const itemWeight = ref<number>(0);
+const isContextMenuVisible = ref<boolean>(false);
+const contextMenuPos = ref<{ x: number; y: number }>({ x: 0, y: 0 });
 
 const $showError = inject<IToastError>("$showError")!;
 
@@ -444,7 +502,7 @@ watch(req, () => {
 
 onMounted(() => {
   // Check the columns size for the first time.
-  colunmsResize();
+  columnsResize();
 
   // How much every listing item affects the window height
   setItemWeight();
@@ -638,7 +696,7 @@ const paste = (event: Event) => {
   action(overwrite, rename);
 };
 
-const colunmsResize = () => {
+const columnsResize = () => {
   // Update the columns size based on the window width.
   const items_ = css(["#listing.mosaic .item", ".mosaic#listing .item"]);
   if (items_ === null) return;
@@ -841,7 +899,7 @@ const toggleMultipleSelection = () => {
 };
 
 const windowsResize = throttle(() => {
-  colunmsResize();
+  columnsResize();
   width.value = window.innerWidth;
 
   // Listing element is not displayed
@@ -951,4 +1009,17 @@ const fillWindow = (fit = false) => {
   // Set the number of displayed items
   showLimit.value = showQuantity > totalItems ? totalItems : showQuantity;
 };
+
+const showContextMenu = (event: MouseEvent) => {
+  event.preventDefault();
+  isContextMenuVisible.value = true;
+  contextMenuPos.value = {
+    x: event.clientX + 8,
+    y: event.clientY + Math.floor(window.scrollY),
+  };
+};
+
+const hideContextMenu = () => {
+  isContextMenuVisible.value = false;
+};
 </script>
