내비게이션 바와 같은 요소들은 특별히 화면상에서 다른 요소보다 앞쪽에 표시되어야 합니다.
일반적으로 나중에 선언된 엘리먼트가 앞쪽에 표시되지만,
CSS의 z-index 속성값을 활용하면 선언 순서에서 벗어나 시각적 계층 구조를 정리할 수 있습니다.
다만, z-index를 ’땜질용 속성’처럼 사용해 ‘Magic Number’를 남발하다 보면
코드베이스가 복잡해질수록 “어떤 수치를 적용해야 할지”에 대해 혼돈이 일어나기 쉽습니다.
- 페이지 콘텐츠 앞쪽에 모달 컴포넌트를 표시해야 할 때,
z-index: 100이 적절할까요? 1000이나 2000은요?
- 내비게이션이 화면상의 어떤 요소보다도 위에 표시되어야만 한다면,
이를 보장하기 위해
z-index: 9999가 적절할까요? 충분하지 않다면, 99999는요?
1import styled from 'styled-components';
2
3const ModalOverlay = styled.div`
4 position: fixed;
6 background: rgba(0, 0, 0, 0.5);
7`;
8
9const ModalContent = styled.div`
10 position: absolute;
12`;
13
14const HeaderNav = styled.nav`
15 position: fixed;
17`;
18
19// ...
20
다시 생각해 보면, 우리는 이미 모서리의 곡률이나 폰트 크기와 같이
재사용하는 임의의 시각적 수치를 디자인 시스템으로 견고하게 관리하곤 합니다.
z-index를 통한 시각적 계층 구조 또한 디자인 시스템의 일부로 포함할 수 있습니다.
특히 Tailwind CSS v4 이후 버전을 사용한다면 아래와 같이
CSS Variable 기반 규칙을 정의하는 것만으로 유틸리티 클래스가 자동으로 생성됩니다.
실제 적용하기까지 특별히 신경 쓸 부분이 거의 없을 뿐만 아니라,
이후 새로운 z-index 규칙이 추가되어야 하는 경우에 더욱 유용합니다.
1/* theme/depth.css */
2
3@theme {
4 --z-index-below: -1;
5 --z-index-base: 0;
6 --z-index-header: 500;
7 --z-index-dropdown: 600;
8 --z-index-popover: 700;
9 --z-index-dimmed: 999;
10 --z-index-modal: 1000;
11 --z-index-drawer: 1100; /* 나중에 새로운 규칙이 추가될 경우를 대비해, 100단위 간격을 지정합니다. */
12 --z-index-toast: 2000;
13}
14
1// components/Modal.tsx
2
3export const Modal = () => {
4 // ...
5
6 return createPortal(
7 <div
8 className="fixed inset-0 z-dimmed flex h-full ..."
9 onClick={handleClickModalBackground}
10 >
11 <div className="relative z-modal flex h-full w-full ...">
12 {/* ... */}
13 </div>
14 </div>,
15 document.body
16 );
17};
18
(조금 더 자세하게는) 저는 globals.css에서 직접 theme/*.css를 모두 import하는 대신
JavaScript의 Barrel File과 비슷한 역할을 수행하는 theme.css를 만들어 사용합니다.
globals.css가 너무 자주 커밋되지 않도록 하기 위함입니다.
1/* theme.css */
2
3@import './base.css';
4@import './animation.css';
5@import './breakpoint.css';
6@import './font.css';
7@import './palette.css';
8@import './spacing.css';
9@import './shadow.css';
10@import './depth.css';
11
다만, z-index 속성이 같은 Stacking Context 내부에서만 작동한다는 점을 간과하지 않도록 주의해야 합니다.
이 때문에 전역에서 표시되는 모달이나 토스트와 같은 컴포넌트는 z-index보다도
React의 createPortal()과 같은 기능을 활용해 body 바로 아래에 렌더링하는 패턴을 권장합니다.
1<header class="sticky top-0 z-header bg-white">
2 <h1>헤더 내비게이션</h1>
3</header>
4
5<main>
6 <section style="opacity: 0.9;">
7 <div class="fixed z-modal bg-white">
8 이 모달은 z-index가 1000이지만,
9 부모의 Stacking Context에 갇혀 'z-index: 500'인 헤더보다 뒤에 표시됩니다.
10 </div>
11 </section>
12</main>
13