CSS가 커질수록 필요한 것은 선택자 싸움이 아니라 우선순위 구조다

여러 화면과 팀이 함께 관리하는 웹 서비스에서는 같은 버튼, 폼, 안내 문구에 서로 다른 CSS가 적용되면서 수정 범위가 예측되지 않는 일이 반복됩니다. 이를 피하려고 선택자를 길게 만들거나 !important를 추가하면 당장은 해결되지만, 다음 변경에서 더 강한 규칙이 필요해지는 악순환이 생깁니다. CSS Cascade Layers의 @layer는 어떤 스타일 묶음이 먼저 이겨야 하는지를 선택자 구체성보다 앞선 단계에서 설계하게 해 줍니다.

레이어는 기존 UI 라이브러리, 공통 디자인 시스템, 페이지별 스타일, 긴급 수정 코드가 섞인 프로젝트에 유용합니다. 팀은 레이어 이름과 선언 순서를 하나의 운영 규칙으로 합의하고, 화면 구현자는 불필요하게 복잡한 선택자를 만들지 않아도 됩니다.

우선순위의 핵심

일반 스타일 규칙에서는 레이어 목록에서 뒤에 선언한 레이어가 앞의 레이어보다 우선합니다. 따라서 초기화, 외부 라이브러리, 공통 컴포넌트, 화면 전용 스타일 순으로 두면 뒤쪽의 업무 코드가 자연스럽게 앞쪽을 덮어쓸 수 있습니다. 같은 레이어 안에서는 평소와 동일하게 선택자 구체성, 소스 순서가 적용됩니다.

레이어 밖에 작성한 일반 CSS는 레이어 안의 일반 CSS보다 우선합니다. 전환 중 기존 스타일을 레이어 밖에 남기면 새 규칙이 기대대로 작동하지 않습니다. 또한 !important가 붙으면 레이어 우선순위가 반대가 되므로, 긴급 규칙을 남용하면 설계 의도가 흐려집니다.

권장 레이어 설계

진입 CSS 파일에 전체 레이어 순서를 한 번만 선언합니다. 외부 패키지의 로드 위치가 달라져도 프로젝트의 우선순위 계약은 유지됩니다. 아래 구조는 reset을 가장 약하게 두고 화면 단위 수정이 공통 컴포넌트를 덮을 수 있게 만듭니다.

@layer reset, vendor, base, components, utilities, pages;

@import url("./reset.css") layer(reset);
@import url("./vendor-datepicker.css") layer(vendor);

@layer base {
  :root { --color-primary: #1769e0; --space-3: 0.75rem; }
  body { margin: 0; font-family: system-ui, sans-serif; color: #1f2937; }
}

@layer components {
  .button { display: inline-flex; gap: var(--space-3); padding: 0.625rem 1rem; border: 1px solid transparent; border-radius: 0.5rem; background: var(--color-primary); color: white; }
}

@layer pages {
  .order-page .button { width: 100%; }
}

reset에는 브라우저 기본값을 정리하는 코드, vendor에는 수정하기 어려운 외부 CSS, base에는 색상·글꼴·문서 기본값, components에는 재사용 UI, utilities에는 작은 보조 클래스, pages에는 특정 화면의 배치를 둡니다. 프로젝트마다 이름은 바꿔도 되지만, 레이어 수를 과도하게 늘리기보다 책임 경계가 분명해야 합니다.

기존 프로젝트에 안전하게 도입하는 순서

첫 단계에서는 모든 CSS를 한꺼번에 옮기지 않습니다. 현재 정상 동작하는 전역 초기화 파일을 reset 또는 base에 넣고, 새로 만드는 컴포넌트부터 components 레이어에 작성합니다. 그다음 충돌 이력이 많은 외부 라이브러리를 vendor로 감싸고 실제 주요 화면에서 회귀 테스트를 합니다. 마지막으로 페이지 전용 파일을 pages로 옮기면 변경 원인을 좁히기 쉽습니다.

CSS Modules, CSS-in-JS, Tailwind 같은 도구를 쓴다고 해도 레이어가 무의미해지는 것은 아닙니다. 도구가 생성한 CSS가 어느 레이어에 들어가는지 확인하고, 전역 스타일 및 서드파티 CSS와의 관계만 명확히 정하면 됩니다. 빌드 결과와 사용자 정의 레이어의 순서를 확인한 뒤 같은 이름을 중복 선언하지 않는 규칙을 정하세요.

충돌을 진단하는 실전 방법

스타일이 예상과 다르면 브라우저 개발자 도구의 Styles 패널에서 취소선이 생긴 규칙을 확인합니다. 선택자 점수만 비교하지 말고 규칙이 어느 레이어에 속하는지, 레이어 밖 전역 CSS가 남아 있는지, !important가 있는지를 차례로 봅니다. 수정할 때는 선택자를 더 길게 만드는 대신 해당 코드가 올바른 레이어에 있는지 먼저 판단해야 합니다.

  • 진입 파일에서 레이어 전체 순서를 한 곳에 고정한다.
  • 외부 라이브러리 CSS는 vendor 레이어로 격리한다.
  • 공통 컴포넌트보다 화면 전용 CSS가 뒤 레이어에 오도록 둔다.
  • 레이어 밖 CSS와 !important는 예외로 기록하고 줄인다.
  • 도입 전후 핵심 화면의 버튼, 폼, 팝업, 반응형 구간을 시각 회귀 테스트한다.

배포 전 체크리스트

레이어는 CSS를 자동으로 정리하는 기능이 아니라 우선순위를 의도적으로 문서화하는 장치입니다. 레이어 순서가 진입점에 선언되어 있는지, 새 스타일의 책임 위치가 분명한지, 레이어 밖 규칙이 의도된 예외인지 확인하세요. 이 세 가지를 지키면 선택자 경쟁과 임시 덮어쓰기를 줄이고 화면 변경의 영향 범위를 안정적으로 관리할 수 있습니다.