aimake.log
인프라

내가 쓰지도 않은 CSS가 컴포넌트를 망가뜨릴 때 — 전역 선택자 함정 2개

홈 화면 히어로를 캐러셀로 바꿨습니다. 로컬에서 확인하고, 모바일에서 확인하고, 배포했습니다. 그리고 사흘 뒤에 데스크톱에서 열어본 뒤에야 알았습니다 — 히어로 오른쪽 절반이 텅 빈 검정이었습니다. 원인은 제가 그날 작성한 코드 어디에도 없었습니다. 3주…

2026.08.18 9 MIN
세 줄 요약

새로 만든 컴포넌트가 깨지는 원인은 지금 보고 있는 파일 밖에 있을 때가 많습니다. 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 에서 멈췄습니다.

홈 히어로 수정 전후 비교. 위쪽 BEFORE 는 뷰포트 1440px 인데 슬라이드가 720px 에서 끊겨 오른쪽 절반이 빗금 친 빈 검정으로 남아 있고 720px 위치에 붉은 점선이 그어져 있다. 아래쪽 AFTER 는 같은 화면이 1440px 을 전부 채워 커버 이미지와 제목이 정상으로 보인다.
같은 뷰포트 1440px. 위는 720px 에서 잘렸고 아래는 다 채운다

범인은 글 상세 페이지를 만들 때 쓴 이 한 줄이었습니다.

article{max-width:720px;min-width:0}   /* 본문 읽기 좋은 폭 */

본문 칼럼 폭을 잡으려고 쓴 규칙입니다. 그때는 페이지에 <article> 이 본문 하나뿐이었으니 맞는 코드였습니다. 문제는 3주 뒤에 제가 히어로 슬라이드를 <article> 로 만들었다는 것입니다.

전역 선택자가 새 컴포넌트로 새는 길전역 요소 선택자는 나중에 만든 것에도 걸린다이름을 안 붙인 규칙은 대상을 고르지 않는다 — 그 태그를 쓰면 전부 걸린다article {max-width:720px }글 본문 폭 잡으려고 쓴 것의도한 대상<article class=”prose”>720px — 맞다3주 뒤에 만든 것<article class=”slide”>position:absolute; inset:0← 폭을 꽉 채우라고 줬지만720px — 갇힘inset:0 은 max-width 를이기지 못한다
이름을 안 붙인 규칙은 대상을 고르지 않는다 — 그 태그를 쓰면 전부 걸린다

inset:0 이 지는가

position:absoluteleft: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}

다음부터 어떻게 할까

규칙 세 개로 정리했습니다.

  1. 전역 요소 선택자에 폭·여백을 걸지 않는다. article{max-width:720px} 이 아니라 .prose{max-width:720px} 로 쓴다. 태그는 의미를 고르는 것이지 대상을 고르는 게 아니다
  2. 남이 만드는 마크업은 출력부터 본다. the_posts_pagination() 처럼 함수가 HTML 을 찍어주면, 클래스가 어디 붙는지 실제 출력으로 확인하고 CSS 를 건다
  3. 확인은 넓은 쪽과 좁은 쪽 양쪽에서. 좁은 화면에서만 보면 상한에 걸리는 버그가 통째로 숨는다

지난번 CSS 함정 3개는 전부 내가 쓴 규칙이 원인이었습니다. 이번 둘은 반대입니다 — 내가 지금 보고 있는 파일 밖에서 왔습니다. 새 컴포넌트가 이유 없이 이상하면, 그 컴포넌트를 더 들여다보는 대신 부모와 자식의 숫자부터 찍어보는 편이 빠릅니다.

검증 기록: 2026-08-18 실측. 히어로 폭은 Playwright 1.62.0(Chromium) 으로 1440·1200·900·390px 뷰포트에서 getBoundingClientRect() 를 찍어 대조 — 수정 전 부모 1440·자식 720, 수정 후 양쪽 1440. 가로 넘침은 document.documentElement.scrollWidth 가 뷰포트 폭과 같은 것으로 확인. 페이지네이션은 같은 방식으로 수정 전후 문서 높이 2622px → 2553px 확인. 두 수정 모두 aimakelog.app 테마(1.2.1 / 1.3.1)에 반영해 배포 후 라이브에서 재확인.

Author

재박

AI로 뭔가를 만들다 막힌 지점과, 그걸 어떻게 뚫었는지 기록합니다.

RSS
구독
이어서 읽기
aimake.log AI로 실제 결과물 만드는 실전 기록 소개 개인정보처리방침 RSS