대표 이미지가 있는 카드의 본문 여백을 줄이려 쓴 CSS 한 줄이 댓글까지 바꿀 수 있다. 카드 안에 이미지가 있으면 모든 문단을 선택하는 규칙에서는 댓글의 작은 아바타도 이미지 조건을 만족한다. 대표 이미지를 추가한 적이 없어도 댓글이 붙는 순간 카드의 모양이 달라지는 이유다.
가브리엘 쇼욤보가 11월 3일 스매싱 매거진에 공개한 글은 이런 사례를 통해 짧아진 코드와 단순해진 시스템이 항상 같지는 않다고 설명한다. CSS가 더 많은 동작을 맡을 수 있게 됐을 때 중요한 질문은 어느 언어가 짧은가보다, 새 규칙이 기존 화면의 어디까지 영향을 주는가에 있다.
조건의 범위와 바뀌는 대상은 다른 두 자리다
문제의 모양은 .story:has(img) p처럼 쓸 수 있다. 괄호 안의 img는 카드 밑의 어느 깊이에 있는 이미지라도 조건으로 삼고, 뒤의 p 역시 카드 밑의 문단을 모두 선택한다. 댓글 안의 아바타가 조건을 참으로 만들고 댓글 문단 자체도 변경 대상으로 들어간다. 한 선택자 안에서 조건을 찾는 범위와 스타일을 적용하는 범위가 동시에 넓어진 것이다.
선택자 표준에서 :has()는 기준 요소에 대한 상대 선택자가 일치하는지 판단한다. 직접 자식을 뜻하는 기호를 조건 안에 넣으면 그 탐색 관계를 좁힐 수 있다. 뒤에 붙는 본문 선택에도 같은 제한을 주면 카드 자신의 문단만 대상으로 삼는다. 중요한 것은 클래스 이름을 길게 쓰는 일이 아니라 대표 이미지와 본문이 실제로 어느 부모의 직접 자식인지 일치시키는 일이다.
세 카드에서 계산된 여백을 확인했다
ARCH.B는 실제 서비스와 분리된 로컬 HTML 예제를 만들고, 같은 브라우저에서 세 조건을 실행했다. 기본 본문 여백은 16픽셀, 댓글 여백은 12픽셀로 정했다. 자손 전체를 찾는 규칙을 쓴 첫 카드에서는 아바타만 있어도 본문과 댓글의 위 여백이 모두 0픽셀이 됐다.
두 번째 카드에는 같은 아바타를 놓되 조건을 대표 이미지의 직접 자식 관계로 제한했다. 대표 이미지가 없으므로 본문 16픽셀과 댓글 12픽셀이 유지됐다. 세 번째 카드에 대표 이미지를 직접 자식으로 추가하자 본문만 0픽셀로 바뀌고 댓글은 12픽셀을 유지했다. 계산된 스타일과 조건 일치 여부를 각각 읽어 세 경우를 검증했다.
아래 코드는 이 검사에 사용한 핵심 규칙을 정리한 독립 설명 예제다. 원문의 시연 화면이나 실제 프로젝트 코드를 복제한 것이 아니다. 코드의 대표 이미지·본문 클래스는 카드 바로 아래에 있다는 전제를 갖는다. 감싸는 요소를 추가한다면 DOM의 관계와 선택 조건을 함께 바꿔야 한다.

ARCH.B 독립 예제: 대표 이미지와 자체 본문으로 범위 한정CSS
/* ARCH.B 독립 설명 예제: 원문이나 실제 서비스의 소스가 아닙니다. */
/* story 바로 아래의 대표 이미지를 조건으로, 자체 본문만 선택합니다. */
.story:has(> .story-hero) > .story-body {
margin-block: 0;
}
/* 중첩된 댓글은 자신의 기본 규칙을 유지합니다. */
.comment p {
margin-block: 12px;
}폭을 판단하는 주체가 둘로 남는 경우
원문은 선택자 외에도 CSS로 바꾸는 중간 단계의 충돌을 보여준다. ResizeObserver가 특정 폭에서 클래스를 붙이던 카드에 컨테이너 쿼리를 추가하면서 스크립트와 예전 제목 규칙을 남겨 놓으면, 같은 카드의 배치와 제목 크기가 서로 다른 기준을 따를 수 있다. 처음에는 경계값이 같아 정상처럼 보이지만 한쪽 값만 바꾸는 순간 엇갈린 상태가 드러난다.
여기서 CSS와 자바스크립트가 한 컴포넌트에 함께 있다는 사실 자체가 문제는 아니다. 서버 요청의 상태를 관리하는 코드와 그 상태를 표현하는 CSS는 역할을 나눌 수 있다. 문제가 되는 것은 같은 폭이나 같은 상태를 서로 다른 곳에서 따로 계산해 결과를 정하는 경우다. 동작을 옮길 때에는 새 규칙만 추가하는 작업과 기존 계산을 끝내는 작업을 함께 봐야 한다.
컨테이너를 만든 자리도 배치의 일부다
container-type을 inline-size로 지정하면 자식이 그 요소의 폭을 기준으로 조건을 판단할 수 있다. 동시에 요소의 안쪽 크기를 내용물에서 독립적으로 계산하는 격리가 적용된다. MDN은 부모 등 주변 맥락이나 명시적인 값에서 크기를 얻지 못하면 크기 격리가 적용된 요소가 축소될 수 있다고 설명한다. 내용만큼 폭을 얻던 카드에 속성 하나를 붙이는 일이 주변 배치를 바꾸는 이유다.
따라서 컨테이너 쿼리로 바꾼 카드를 넓은 빈 화면에서만 보는 것으로는 충분하지 않다. 폭이 부모에서 주어지는 자리와 내용 크기에 기대는 자리에서는 전제가 다르다. 원문은 부모로부터 폭을 받는 바깥 요소를 질의 컨테이너로 두는 수정안을 보여준다. 쿼리가 검사할 기준과 실제로 스타일을 바꿀 내용의 자리를 분리한 선택이다.
카드의 아바타 사례에서는 조건과 대상의 DOM 범위를 좁히는 것이 해결의 핵심이었다. 폭에 따른 배치를 옮기는 경우에는 남아 있는 계산과 컨테이너의 크기 결정 방식이 중요했다. CSS 문법의 짧음만으로는 이 서로 다른 경계를 알 수 없다.
ARCH.B 의견
컴포넌트의 규칙은 의도한 조건이 참인 경우뿐 아니라, 비슷하게 보이지만 그 조건에 속하지 않는 경우까지 함께 보면 선명해진다. 카드에 이미지가 있는지를 시험할 때 댓글 아바타만 있는 카드를 곁에 둔 이유다. 대표 이미지를 넣은 성공 예만으로는 선택자가 댓글까지 닿는 문제를 드러내기 어렵다.
이번 독립 예제는 세 카드의 선택과 계산된 여백을 확인한 범위에 한정된다. 다른 브라우저의 성능이나 실제 사용자의 이해도를 측정한 검사는 아니다. 그래도 컴포넌트의 경계를 코드에서 읽고 검토할 수 있는 작은 기준은 남긴다. 무엇이 조건이고 무엇이 바뀌는 대상인지 명확해지면, 코드를 줄이는 작업도 다음 사람이 안전하게 고칠 수 있는 작업으로 이어진다.
