2 분 소요

🧩 발단

MUI Dialog 안에서 react-colorSketchPicker를 사용하고 있었습니다.
색상 피커는 정상적으로 열렸습니다.

그런데 문제가 하나 있었습니다.

  • hex / RGB input을 클릭해도 커서가 생기지 않음
  • 타이핑이 되지 않음
  • JavaScript로 focus()를 강제로 줘도 반응 없음

“클릭은 되는데 입력이 안 된다?”
이상했습니다. 단순한 이벤트 문제 같지는 않았습니다.


🔎 디버깅 과정

1️⃣ “설마 input이 없는 건 아니겠지?”

document.querySelectorAll('input')

rc-editable-input 관련 input들이 정상적으로 조회되었습니다. DOM 상에 input은 분명 존재했습니다.

일단 input 실종 사건은 아니었습니다.


2️⃣ “혹시 disabled? pointer-events?”

console.log('disabled:', el.disabled);          
console.log('pointerEvents:', ...pointerEvents)
console.log('visibility:', ...visibility);
  • disabled: false
  • pointer-events: auto
  • visibility: visible

전부 정상. input 자체에는 문제가 없어 보였습니다.


3️⃣ “그럼… 위에 뭔가 덮고 있나?”

const rect = input.getBoundingClientRect();
document.elementFromPoint(
  rect.left + rect.width / 2,
  rect.top + rect.height / 2
);
// → sketch-picker div 반환

input의 정확한 좌표를 찍어 확인했는데, 돌아온 것은 input이 아니라 상위 div였습니다.

👉 무언가 input 위를 덮고 있었습니다.


4️⃣ “sketch-picker를 무력화해보면?”

document
  .querySelector('.sketch-picker')
  .style.pointerEvents = 'none';

그러자 picker가 닫혀버렸습니다.

backdrop 클릭이 그대로 통과된 것입니다.

즉,

backdrop이 SketchPicker 위에 올라와 있었습니다.


5️⃣ “그런데 focus()는 왜 안 됐지?”

클릭 차단과는 별개로, focus()조차 동작하지 않는 점이 이상했습니다.

Portal의 렌더링 위치를 확인해보니, Portal 대상이 choice-value-dialog — 즉 MUI Dialog의 루트 div였습니다.

여기서 두 번째 문제가 드러났습니다.


🎯 원인: 두 가지가 동시에 문제였다


1️⃣ Portal이 MUI focus trap 밖에 렌더링되고 있었다

MUI Dialog는 MuiDialog-scrollPaper 내부만 focus trap으로 관리합니다.

그런데 Portal을 루트 div(choice-value-dialog)에 렌더링하면 focus trap 으로 인식됩니다.

MUI Dialog 루트
├── MuiBackdrop-root
├── MuiDialog-scrollPaper  ← focus trap 범위
│   └── dialog 컨텐츠
└── SketchPicker Portal    ← ❌ focus trap 밖 → 포커스 차단

따라서 MUI가 외부 요소로 판단하고 포커스를 강제로 막고 있었습니다.

focus()가 먹히지 않았던 이유였습니다.


2️⃣ backdrop이 SketchPicker 위를 덮고 있었다

position: fixed + top/right/bottom/left: 0 형태의 backdrop을 SketchPicker와 같은 부모 안에 두면, stacking context 충돌이 발생합니다.

그 결과 backdrop이 picker 위에 올라가 모든 클릭을 가로채고 있었습니다.


🤔 왜 하나만 고쳐서는 안 됐을까

수정한 부분 결과
Portal 위치만 수정 backdrop이 여전히 input을 덮음 → 클릭 불가
backdrop 구조만 수정 focus trap이 막음 → 포커스 불가
둘 다 수정 ✅ 정상 동작

문제는 하나가 아니라, 렌더링 위치 + 포커스 관리 + stacking context가 동시에 얽힌 복합 이슈였습니다.


🛠 해결 방법

  1. Portal을 MuiDialog-scrollPaper 안에 렌더링 → focus trap 범위 안으로 이동
  2. backdrop과 picker를 형제 관계로 분리
  3. z-index로 레이어 명확히 구분
  4. picker에 stopPropagation() 적용
<Portal> {/* MuiDialog-scrollPaper 대상 */}
  {/* 전체 화면 backdrop */}
  <div
    style=
    onClick={props.onClose}
  />

  {/* picker */}
  <div
    style=
    onClick={(e) => e.stopPropagation()}
  >
    <SketchPicker ... />
  </div>
</Portal>

이후 input 클릭, 포커스, 타이핑 모두 정상 동작했습니다.


📚 배운 것

  • MUI Dialog 안에서 Portal을 사용할 때는 반드시 MuiDialog-scrollPaper를 대상으로 해야 합니다. 루트 div에 붙이면 focus trap 밖이 됩니다.
  • backdrop과 picker는 같은 부모 안에 두지 않는 것이 안전합니다. stacking context 충돌이 발생할 수 있습니다.
  • focus()도 안 되고, pointer-events도 정상인데 클릭이 안 된다면 → focus trap을 의심해야 합니다.

🌿 회고

처음에는 “왜 클릭이 안 되지?”라는 단순한 의문이었습니다. 하지만 파고들수록 문제는 생각보다 깊었습니다.

렌더링 위치, 포커스 관리, 레이어 구조.

프론트엔드는 결국 눈에 보이는 UI 뒤에서 얼마나 많은 제약이 작동하고 있는지를 이해하는 일이라는 것을 다시 느꼈습니다.

이번 디버깅을 통해 얻은 교훈은 하나입니다.

클릭이 안 되면 이벤트를 의심하기 전에, 누가 위에 서 있는지부터 확인하자. 🔍

카테고리:

업데이트:

댓글남기기