diff --git a/frontend/src/components/StatusSelect.vue b/frontend/src/components/StatusSelect.vue
index 633ac1f..37a2d87 100644
--- a/frontend/src/components/StatusSelect.vue
+++ b/frontend/src/components/StatusSelect.vue
@@ -1,7 +1,7 @@
diff --git a/frontend/src/composables/useDropdownMenu.ts b/frontend/src/composables/useDropdownMenu.ts
new file mode 100644
index 0000000..58eb258
--- /dev/null
+++ b/frontend/src/composables/useDropdownMenu.ts
@@ -0,0 +1,58 @@
+import { nextTick, ref } from 'vue'
+import { useClickOutside } from './useClickOutside'
+
+// Teleport(body) + fixed 위치로 띄우는 드롭다운 메뉴 공용 훅.
+// 트리거 기준 좌표 계산 · open 토글 · 바깥클릭/ESC/스크롤 닫기를 한 곳으로 모은다.
+// (DropdownSelect, StatusSelect 등 스크롤/overflow 컨테이너 안에서도 안 잘리는 메뉴에 사용)
+//
+// - align: 트리거 좌/우 끝 기준 정렬 (기본 'left')
+// - matchWidth: 메뉴 최소 너비를 트리거 너비에 맞출지 (기본 false)
+// - gap: 트리거 하단과 메뉴 사이 간격(px) (기본 6)
+// - esc: ESC 로 닫을지 (기본 true)
+export function useDropdownMenu(
+ opts: {
+ align?: 'left' | 'right'
+ matchWidth?: boolean
+ gap?: number
+ esc?: boolean
+ } = {},
+) {
+ const { align = 'left', matchWidth = false, gap = 6, esc = true } = opts
+
+ const open = ref(false)
+ const triggerRef = ref(null)
+ const menuRef = ref(null)
+ // 메뉴 fixed 위치(트리거 기준 계산)
+ const menuStyle = ref>({})
+
+ function position(): void {
+ const el = triggerRef.value
+ if (!el) return
+ const r = el.getBoundingClientRect()
+ const style: Record = { top: `${r.bottom + gap}px` }
+ if (matchWidth) style.minWidth = `${r.width}px`
+ if (align === 'right') {
+ style.right = `${Math.max(0, window.innerWidth - r.right)}px`
+ } else {
+ style.left = `${r.left}px`
+ }
+ menuStyle.value = style
+ }
+
+ function toggle(): void {
+ open.value = !open.value
+ if (open.value) void nextTick(position)
+ }
+ function close(): void {
+ open.value = false
+ }
+
+ // 바깥클릭(트리거/메뉴 외부)·ESC·스크롤(위치 어긋남) 닫기
+ useClickOutside(open, close, [triggerRef, menuRef], {
+ event: 'click',
+ esc,
+ closeOnScroll: true,
+ })
+
+ return { open, triggerRef, menuRef, menuStyle, toggle, close, position }
+}