새로 만든 컴포넌트가 깨지는 원인은 지금 보고 있는 파일 밖에 있을 때가 많습니다. 3주 전에 쓴 전역 규칙, 그리고 워드프레스가 정한 마크업이 그렇습니다.
article{max-width:720px} 한 줄이 새로 만든 히어로 슬라이드를 720px 에 가뒀습니다. position:absolute; inset:0 을 줬는데도 max-width 가 이깁니다.
둘 다 모바일에서만 확인해서 놓쳤습니다. 폭이 720px 보다 좁으면 증상이 안 나옵니다. 데스크톱 폭에서 부모와 자식 폭을 숫자로 찍어보면 3분이면 드러납니다.
홈 화면 히어로를 캐러셀로 바꿨습니다. 로컬에서 확인하고, 모바일에서 확인하고, 배포했습니다. 그리고 사흘 뒤에 데스크톱에서 열어본 뒤에야 알았습니다 — 히어로 오른쪽 절반이 텅 빈 검정이었습니다.
원인은 제가 그날 작성한 코드 어디에도 없었습니다. 3주 전에 쓴 CSS 한 줄에 있었습니다. 같은 종류의 함정을 이번 주에 두 번 밟았으니 정리해 둡니다.
함정 ① 전역 요소 선택자는 나중에 만든 것에도 걸린다
히어로 슬라이드를 이렇게 만들었습니다. 슬라이드를 겹쳐 쌓고 크로스페이드로 넘기는 흔한 구조입니다.
<section class="hero">
<article class="slide is-active"> … </article>
<article class="slide"> … </article>
</section>
.hero{position:relative}
.hero .slide{position:absolute;inset:0} /* 히어로를 꽉 채워라 */
inset:0 은 “위아래좌우를 부모에 딱 붙여라”입니다. 부모가 1440px 이면 슬라이드도 1440px 이어야 합니다. 그런데 실제로는 720px 에서 멈췄습니다.

범인은 글 상세 페이지를 만들 때 쓴 이 한 줄이었습니다.
article{max-width:720px;min-width:0} /* 본문 읽기 좋은 폭 */
본문 칼럼 폭을 잡으려고 쓴 규칙입니다. 그때는 페이지에 <article> 이 본문 하나뿐이었으니 맞는 코드였습니다. 문제는 3주 뒤에 제가 히어로 슬라이드를 <article> 로 만들었다는 것입니다.
왜 inset:0 이 지는가
position:absolute 에 left:0;right:0 을 주면 브라우저는 먼저 “이 요소의 폭은 부모 폭”이라고 계산합니다. 그런데 그 값은 확정이 아니라 후보입니다. 계산이 끝난 뒤 max-width 가 다시 한 번 잘라냅니다.
CSS 명세가 정한 순서라 우회할 수 없습니다. width·inset·flex-basis 로 아무리 크게 잡아도 max-width 가 마지막에 이깁니다. 그래서 처방도 하나뿐입니다.
.hero .slide{max-width:none} /* 상속받은 상한을 명시적으로 푼다 */
함정 ② 남이 정한 마크업에 flex 를 잘못 건다
같은 주에 페이지네이션도 깨져 있었습니다. 1 / 2 / 다음 알약이 가로로 나란히 서지 않고 세로로 겹쳐 있었습니다.
CSS 는 멀쩡해 보였습니다.
.pagination{display:flex;gap:.75rem;justify-content:center}
문제는 워드프레스가 만들어주는 마크업이었습니다. the_posts_pagination() 에 클래스를 넘기면 그 클래스는 <nav> 에 붙고, 정작 버튼은 그 안의 <div class="nav-links"> 에 들어갑니다.
<nav class="pagination"> <!-- flex 를 여기 걸었다 -->
<h2 class="screen-reader-text">…</h2>
<div class="nav-links"> <!-- flex 아이템은 이것 하나뿐 -->
<span class="current">1</span> <!-- 버튼은 여전히 inline -->
<a>2</a> <a>다음</a>
</div>
</nav>
flex 아이템이 .nav-links 하나라서 버튼들은 inline 인 채로 남습니다. 그리고 inline 요소는 세로 패딩이 줄 높이를 늘리지 않습니다. 알약 모양을 만들려고 준 위아래 패딩이 이웃 줄을 침범하면서 서로 겹친 겁니다.
.pagination .nav-links{display:flex;gap:.75rem;justify-content:center}
flex 를 실제 아이템의 부모로 옮기니 끝났습니다. 페이지 전체 높이가 2622px 에서 2553px 로 줄었습니다 — 겹쳐 있던 두 줄이 한 줄이 된 만큼입니다.
왜 모바일에서는 멀쩡해 보였을까
둘 다 모바일에서 먼저 확인해서 놓쳤습니다. 이유가 서로 다릅니다.
| 함정 | 모바일에서 안 보인 이유 |
|---|---|
max-width:720px |
390px 뷰포트는 애초에 720px 보다 좁다 — 상한에 안 걸린다 |
| 페이지네이션 flex | 테마가 좁은 화면에서 페이저를 display:none 으로 숨긴다 |
브라우저를 붙이고 나서 390px 로 한 장 찍어보는 습관이 생겼는데, 그 습관이 이번엔 반대로 작용했습니다. 좁은 화면에서 멀쩡하다는 걸 확인하고 넘어간 겁니다. 폭에 상한이 걸리는 버그는 넓은 화면에서만 나옵니다.
어떻게 3분 만에 잡나
눈으로 보면 “뭔가 이상한데”에서 멈춥니다. 부모와 자식의 폭을 나란히 찍으면 바로 나옵니다.
const {chromium} = require('playwright');
const b = await chromium.launch();
for (const w of [1440, 1200, 900, 390]) {
const p = await b.newPage({ viewport: { width: w, height: 900 } });
await p.goto('https://example.com/');
console.log(w, await p.evaluate(() => ({
부모: document.querySelector('.hero').getBoundingClientRect().width,
자식: document.querySelector('.hero .slide').getBoundingClientRect().width,
})));
}
실제로 찍은 값입니다.
| 뷰포트 | 부모 .hero |
자식 .slide |
수정 후 |
|---|---|---|---|
| 1440px | 1440 | 720 | 1440 |
| 1200px | 1200 | 720 | 1200 |
| 900px | 900 | 720 | 900 |
| 390px | 390 | 390 | 390 |
자식이 뷰포트가 바뀌어도 720 으로 고정이라는 게 결정적입니다. 비율이 아니라 고정값이면 범인은 어딘가에 박힌 px 입니다. 그 숫자로 CSS 를 검색하면 한 줄로 좁혀집니다.
$ grep -n "720" style.css
317:article{max-width:720px;min-width:0}
다음부터 어떻게 할까
규칙 세 개로 정리했습니다.
- 전역 요소 선택자에 폭·여백을 걸지 않는다.
article{max-width:720px}이 아니라.prose{max-width:720px}로 쓴다. 태그는 의미를 고르는 것이지 대상을 고르는 게 아니다 - 남이 만드는 마크업은 출력부터 본다.
the_posts_pagination()처럼 함수가 HTML 을 찍어주면, 클래스가 어디 붙는지 실제 출력으로 확인하고 CSS 를 건다 - 확인은 넓은 쪽과 좁은 쪽 양쪽에서. 좁은 화면에서만 보면 상한에 걸리는 버그가 통째로 숨는다
지난번 CSS 함정 3개는 전부 내가 쓴 규칙이 원인이었습니다. 이번 둘은 반대입니다 — 내가 지금 보고 있는 파일 밖에서 왔습니다. 새 컴포넌트가 이유 없이 이상하면, 그 컴포넌트를 더 들여다보는 대신 부모와 자식의 숫자부터 찍어보는 편이 빠릅니다.