한 개발자가 Claude Code를 밤새 혼자 돌렸다. 태스크 큐에 쌓아둔 자잘한 작업들—헬스체크 엔드포인트, 플레이키 테스트, 설정 파일 정리—을 bash 루프로 하나씩 처리했고, 아침에 일어나 리포트 파일 하나를 읽었다. 통과, 실패, 소요 시간, 비용. 그는 이렇게 썼다: "이제 태스크 파일을 작성하는 것이 노력이 들어가는 부분인데, 그게 올바른 위치다."
이 문장이 CSS 이야기와 연결되는 지점이 있다. AI가 코드를 빠르게 생성해줄수록, 정작 중요한 것은 '무엇을 지시할 것인가'와 '어떤 구조로 설계할 것인가'로 옮겨간다. CSS도 마찬가지다. container-type: inline-size 한 줄을 AI가 뽑아주는 건 쉽다. 하지만 그 컨테이너가 왜 거기 있어야 하는지, 어떤 컴포넌트 경계에서 쿼리가 발동해야 하는지를 판단하는 건 여전히 개발자의 몫이다.
Modern CSS가 지운 것들
dev.to에 올라온 현장 노트 하나가 흥미롭다. 저자는 container queries, :has(), subgrid, cascade layers—네 가지 CSS 기능으로 레이아웃 관련 JavaScript 대부분을 삭제했다고 밝혔다. 삭제된 코드 목록이 구체적이다. ResizeObserver로 컨테이너 너비를 재서 .is-narrow 클래스를 토글하던 컴포넌트, 스크롤 위치를 저장하고 body에 position: fixed를 걸던 스크롤 락 유틸리티, 카드 중 가장 높은 것을 측정해 나머지에 픽셀 높이를 주입하던 JavaScript 패스. 이것들이 전부 CSS로 대체됐다.
왜 이게 단순한 'CSS 신기능 소개'가 아닌가. 이 코드들은 대부분의 프로젝트에 어느 정도는 살아있다. AI에게 "카드 레이아웃 만들어줘"라고 하면 높은 확률로 이런 JavaScript 패턴이 딸려온다. AI는 학습 데이터에 이 패턴들이 많으니까. 반면 subgrid를 정확하게 써서 카드 타이틀과 푸터를 맞추는 구조를 생성해달라고 요청하면, AI가 올바른 코드를 뱉어도 왜 grid-row: span 3과 grid-template-rows: subgrid를 동시에 써야 하는지 이해 없이는 검토할 수가 없다.
구조를 모르면 검증도 못 한다
container queries의 핵심 함정 하나. 컨테이너는 자기 자신을 쿼리할 수 없다. container-type이 .card에 선언되어 있으면, @container 규칙이 .card를 직접 타겟팅해도 절대 매칭되지 않는다. 컨테이너는 스타일링 대상의 조상이어야 한다. AI가 생성한 코드에 이 실수가 있어도, 구조를 모르면 왜 동작하지 않는지 알 방법이 없다.
:has()도 마찬가지다. .card:has(#hero)는 ID의 명시도를 그대로 가져온다. AI가 :has() 안에 클래스 대신 ID를 넣으면 명시도 계층이 의도치 않게 올라간다. 이를 낮추려면 인자를 :where()로 감싸야 한다는 것—이건 AI가 알려주는 경우도 있지만, 문제가 발생했을 때 원인을 추적하려면 개발자가 이 규칙을 알고 있어야 한다. :has()는 중첩 불가, 수도 요소 포함 불가라는 제약도 마찬가지다.
cascade layers의 가장 많이 걸리는 함정은 역전이다. 레이어 밖의 CSS는 모든 레이어를 이긴다. 써드파티 스타일시트를 @import url(...) layer(vendor) 없이 불러오면, 아무리 정교하게 @layer reset, base, components, utilities를 선언해도 그 파일 하나에 전부 뒤집힌다. 그리고 !important는 레이어 순서를 반전시킨다—이른 레이어의 !important가 늦은 레이어의 일반 선언을 이긴다. AI가 !important를 무심코 추가하면 디버깅이 미궁으로 빠지는 이유다.
실전 구현에서 드러나는 설계 판단
벵골 가정식 레스토랑 랜딩 페이지 'Rannaghar' 구현 사례가 이 판단들을 잘 보여준다. 개발자는 스크롤 기반 애니메이션에 animation-timeline: view()를 사용하되, IntersectionObserver + rAF 폴백을 함께 구현했다. :has()로 탭 상태 머신을 완전히 CSS로 처리했고, @property로 등록한 커스텀 프로퍼티로 conic gradient 애니메이션을 구현했다. 래스터 이미지는 단 하나도 없다—모든 시각 요소가 CSS와 SVG다.
이 구현에서 가장 눈에 띄는 것은 접근성 처리다. 탭 컴포넌트에서 숨겨진 라디오 버튼에서 레이블로 포커스 링이 이동하도록 설계했고, 스파이스 레벨은 "heat level 2 of 3"으로 읽힌다. prefers-reduced-motion을 전역으로 지원해서 불꽃과 스팀, 마퀴가 모두 정지된다. 이런 판단들은 AI가 생성한 코드를 검토하면서 "이 동작이 키보드 사용자에게 어떻게 경험되는가"를 물어볼 수 있는 개발자만이 할 수 있다.
AI 워크플로우와 CSS 설계 능력의 역설
Claude Code 무인 자동화 실험에서 핵심 통찰은 이것이다: "병목은 모델 능력이 아니라 태스크 명세다." GET /health 엔드포인트 추가는 밤새 처리된다. "관찰 가능성을 개선하라"는 처리되지 못한다—검증 명령이 판단할 수 없는 목표이기 때문이다.
CSS에서 같은 논리가 적용된다. "반응형 카드 그리드 만들어줘"는 AI가 처리한다. 하지만 이 카드가 사이드바에도, 풀 너비 그리드에도, 3칼럼 대시보드 위젯에도 동일하게 동작해야 한다는 맥락, 그리고 타이틀과 푸터가 시각적으로 정렬되어야 한다는 요구사항을 명세로 변환하는 것은 개발자의 일이다. 그 명세가 정확할수록 AI가 subgrid와 container queries를 올바르게 조합해서 내놓을 확률이 높아진다.
역설은 여기 있다. AI가 CSS를 빠르게 생성해줄수록, CSS 구조 설계 능력은 더 중요해진다. 생성된 코드를 검토하고, 구조적 실수를 잡아내고, 더 좋은 명세를 작성하려면 기초가 있어야 한다. :has()의 명시도 규칙을 모르는 개발자는 AI가 생성한 코드의 명시도 문제를 발견하지 못한다. subgrid의 두 줄—grid-row: span 3과 grid-template-rows: subgrid—이 왜 함께 필요한지 이해하지 못하면, AI가 둘 중 하나를 빠뜨려도 알아채지 못한다.
지금 당장 설계해야 할 것들
AI와 CSS를 함께 쓰는 워크플로우에서 개발자가 직접 결정해야 할 것들을 정리하면 이렇다.
컨테이너 경계 설계. 어떤 컴포넌트가 뷰포트가 아닌 컨테이너 기준으로 반응해야 하는지를 먼저 그려야 한다. 이 판단이 없으면 AI는 미디어 쿼리를 쓴다—그게 학습 데이터에 더 많으니까.
레이어 아키텍처 정의. @layer reset, base, components, utilities 순서와 써드파티 스타일시트의 레이어 귀속을 프로젝트 초기에 결정해야 한다. AI가 나중에 생성한 코드가 이 구조에 맞게 들어오려면, 구조가 먼저 있어야 한다.
접근성 체크포인트 설계. :has()로 탭을 구현할 때 포커스 관리, prefers-reduced-motion 처리 시점, aria-live 영역 선언—이것들은 AI가 자동으로 챙기지 않는다. 리뷰 체크리스트에 명시적으로 넣어야 한다.
점진적 향상의 경계 결정. CSS anchor positioning과 calc-size()는 아직 Chromium 전용이다. 어디까지 표준 기능으로, 어디서부터 점진적 향상으로 처리할지는 프로덕트 맥락을 아는 개발자만이 결정할 수 있다.
밤새 Claude Code를 돌린 개발자가 아침에 읽는 것은 결과 리포트다. 그 리포트가 의미 있으려면 검증 기준이 미리 설계되어 있어야 한다. CSS도 마찬가지다. AI가 생성한 코드를 검토하는 기준—구조, 명시도, 접근성, 브라우저 지원—을 설계하는 것이 지금 개발자에게 남겨진 핵심 역할이다.