
오늘의 이슈
메인 페이지를 QA 하면서 z-index가 이상하게 적용되는 문제를 겪었다. 헤더는 z-index 50, 네임 바와 도구 버튼은 20이었고, 모달과 모달 배경은 999였는데 다음과 같이 인덱스 값이 더 낮은 헤더, 도구 버튼, 네임 바가 모달 배경 위로 렌더링되는 문제를 겪었다.

스택킹 컨텍스트란?
스택킹 컨텍스트(Stacking Context)는 HTML 요소들의 3차원 공간에서의 렌더링 순서를 결정하는 개념이다. 쉽게 말해, 어떤 요소가 다른 요소 위에 나타날지를 결정하는 규칙의 집합이다.
새롭게 스택킹 컨텍스트가 생성되는 조건은 다음과 같다.
스택킹 컨텍스트가 생성되는 조건
HTML 문서의 루트 요소 (
<html>
)position: relative
또는position: absolute
와 함께z-index
가auto
가 아닌 요소position: fixed
또는position: sticky
인 요소opacity
가 1보다 작은 요소transform
,filter
,perspective
등의 CSS 속성을 가진 요소isolation: isolate
를 사용한 요소
스택킹 컨텍스트의 쌓임 순서
같은 스택킹 컨텍스트 내에서 요소들은 다음 순서로 쌓인다. (아래에서 위 방향으로)
배경과 테두리(background and borders)
음수 z-index를 가진 요소들
블록 레벨의 일반 흐름에 속한 요소들
플로팅(floating) 요소들
인라인 요소들
z-index: 0 또는 auto인 위치가 지정된 요소들
양수 z-index를 가진 요소들
중요한 점은 각 스택킹 컨텍스트 내에서만 z-index 값이 의미를 갖는다는 것이다. 즉, 서로 다른 스택킹 컨텍스트에 속한 요소들은 단순히 z-index 값의 크기만으로 겹침 순서를 비교할 수 없다.
과정
처음에는 단순히 z-index 값을 높여보는 시도를 했다. 하지만 이것만으로는 해결되지 않았다. 문제의 원인을 찾기 위해 앱 구조를 자세히 분석했더니 두 가지 핵심 문제점을 발견했다:
BaseLayout
컴포넌트에서 헤더가 모달보다 나중에 렌더링되고 있었다.모달과 헤더 모두
fixed
포지션을 사용하고 있어서 DOM의 렌더링 순서가 중요했다.
이 문제를 해결하기 위해 먼저 렌더링 순서를 바꾸고 스택킹 컨텍스트를 수정하는 접근법을 시도했다. BaseLayout
의 구조를 변경하고, 적절한 포지셔닝과 z-index 값을 설정했다. 하지만 이 방법도 완벽히 해결되지 않았다.
여러 시도 끝에 결국 React Portal이라는 해결책을 찾았다. Portal을 사용하면 컴포넌트를 DOM 트리의 다른 부분에 렌더링할 수 있어, 모달을 최상위 레벨로 끌어올릴 수 있다는 것을 알게 되었다.
해결방안
최종적으로 React의 createPortal
을 사용하여 문제를 해결했다. 모달 컴포넌트를 수정하여 모달 내용을 document.body에 직접 렌더링함으로써 DOM 계층 구조와 상관없이 항상 최상위에 표시되도록 했다.
import React from 'react';
import { createPortal } from 'react-dom';
const ModalBackground = React.memo(
({
children,
onClose,
}: {
children?: React.ReactNode;
onClose?: () => void;
}) => {
// 모달 닫기 핸들러
const handlecloseModal = (
event: React.MouseEvent<HTMLButtonElement | HTMLDivElement, MouseEvent>,
) => {
event.stopPropagation();
onClose();
};
// 모달 콘텐츠 정의
const modalContent = (
<div
className='fixed inset-0 z-[99] w-full h-full flex justify-center items-center bg-[#1E3675CC] backdrop-blur-xs'
onClick={handlecloseModal}>
<div onClick={(e) => e.stopPropagation()}>
{children}
</div>
</div>
);
// Portal을 사용하여 body에 직접 렌더링
return createPortal(
modalContent,
document.body,
);
},
);
export default ModalBackground;
이렇게 수정하니 모달이 헤더를 포함한 모든 요소 위에 완벽하게 표시되었다. z-index 값도 더 이상 엄청 높게 설정할 필요가 없어졌다.
배운 점
CSS의 스택킹 컨텍스트는 생각보다 복잡하다. position, z-index, 렌더링 순서 등 여러 요소가 영향을 미친다.
같은 z-index 값을 가진 요소들은 DOM에서의 순서(나중에 나오는 요소가 위에 렌더링됨)에 따라 쌓이게 된다. 이것이 헤더가 모달보다 나중에 렌더링되어 문제가 발생했던 이유이다.
서로 다른 스택킹 컨텍스트에 속한 요소들 간에는 단순히 z-index 값의 크기 비교만으로 겹침 순서를 결정할 수 없다. 이는 부모 요소의 스택킹 컨텍스트가 자식 요소들의 z-index 범위를 제한하기 때문이다.
React에서 컴포넌트의 렌더링 위치와 실제 DOM에서의 위치는 다를 수 있으며, Portal을 사용하면 이를 효과적으로 제어할 수 있다.
문제 해결을 위해서는 여러 접근 방식을 시도해보는 것이 중요하다. 처음에는 단순히 CSS 속성만 변경하려 했지만, 결국 React의 기능을 활용해 더 깔끔한 해결책을 찾았다.
모달 같은 UI 요소는 앱의 기본 레이아웃과 독립적으로 존재해야 할 때가 많으며, Portal은 이런 상황에 최적의 솔루션이다.
이번 경험을 통해 React와 CSS의 상호작용에 대해 더 깊이 이해하게 되었고, 앞으로 비슷한 문제가 발생하면 더 효율적으로 해결할 수 있을 것 같다.