CSS :where()와 :is() 선택자로 중복 스타일 제거하기
CSS `:where()`와 `:is()` 선택자를 활용하여 중복 스타일을 효과적으로 제거하는 패턴을 알아봅니다. 이 글에서는 각 선택자의 특징, 중복 제거 코드 예제, 주의할 점 등을 다룹니다.
CSS :where()와 :is() 선택자로 중복 스타일 제거하기
웹 개발에서 CSS는 사용자 인터페이스를 시각적으로 구성하는 핵심 요소입니다. 하지만 프로젝트 규모가 커질수록 동일한 스타일이 여러 곳에서 반복적으로 사용되는 중복 문제가 발생하기 쉽습니다. 이러한 중복은 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만들며, 불필요한 CSS 용량 증가로 이어져 페이지 로딩 성능에도 영향을 줄 수 있습니다. 다행히 CSS는 이러한 문제를 해결하기 위한 새로운 선택자들을 제공합니다. 이번 글에서는 :where()와 :is() 선택자를 활용하여 CSS 중복 스타일을 효과적으로 제거하는 패턴을 알아보고, 실제 코드에 적용하는 방법을 살펴보겠습니다.
1. :where()와 :is() 선택자란 무엇인가?
:where()와 :is()는 동적으로 선택자 리스트를 받아 해당 선택자 중 하나라도 일치하는 요소를 선택하는 함수형 선택자입니다. 이 둘은 매우 유사하지만, 가장 큰 차이점은 **가중치(Specificity)**입니다.
:is()::is()안에 포함된 선택자 중 가장 높은 가중치를 가진 선택자의 가중치를 상속받습니다. 이는 기존 선택자와의 충돌을 줄이고 예상치 못한 스타일 덮어쓰기를 방지하는 데 도움이 됩니다.:where()::where()안에 포함된 모든 선택자의 가중치가 0입니다. 이는:where()를 사용한 스타일이 다른 어떤 선택자보다 우선순위가 낮아, 다른 스타일 규칙에 의해 쉽게 덮어쓰여짐을 의미합니다. 따라서:where()는 주로 스타일의 범위를 지정하거나, 특정 그룹에 대한 기본 스타일을 정의할 때 유용하게 사용됩니다.
이 두 선택자는 브라우저 호환성도 점차 좋아지고 있어, 최신 웹 개발 환경에서 적극적으로 활용할 만한 가치가 있습니다.
2. 중복 스타일 제거 패턴 및 코드 예제
CSS 중복 스타일은 주로 여러 컴포넌트나 요소에 걸쳐 동일한 디자인 패턴이 적용될 때 발생합니다. 예를 들어, 버튼, 카드, 폼 요소 등은 자주 유사한 스타일을 공유합니다. :where()와 :is()를 사용하면 이러한 중복을 효과적으로 제거할 수 있습니다.
예제 1: 여러 컴포넌트에 공통된 스타일 적용하기
버튼, 링크, 폼 입력 필드 등 여러 요소에 대해 cursor: pointer;와 text-decoration: none; 같은 공통 스타일을 적용해야 하는 경우를 가정해 보겠습니다. 기존에는 각 요소별로 스타일을 중복해서 작성해야 했습니다.
중복 코드 (기존 방식)
1.button:hover { 2 cursor: pointer; 3 text-decoration: none; 4} 5 6a:hover { 7 cursor: pointer; 8 text-decoration: none; 9} 10 11.input-field { 12 cursor: pointer; 13 text-decoration: none; 14}
:is()를 사용한 개선 코드
:is()를 사용하면 이러한 공통 스타일을 한 번만 작성하여 여러 선택자에 적용할 수 있습니다. :is()는 선택자 리스트를 인자로 받으며, 이 중 가장 높은 가중치를 가진 선택자의 가중치를 따릅니다. 이 경우 .button:hover, a:hover, .input-field 모두 가중치가 다르므로, :is()는 가장 높은 가중치를 가진 선택자의 가중치를 상속하게 됩니다. 이는 예상치 못한 스타일 덮어쓰기를 방지하는 데 도움이 됩니다.
1/* .button:hover, a:hover, .input-field 중 가장 높은 가중치를 상속받음 */ 2:is(.button:hover, a:hover, .input-field) { 3 cursor: pointer; 4 text-decoration: none; 5}
이 코드는 .button:hover, a:hover, .input-field 중 어떤 요소가 실제로 선택되든 동일한 스타일이 적용됩니다. 가중치 측면에서 a:hover가 가장 가중치가 높으므로, 이 스타일의 가중치는 a:hover의 가중치를 따르게 됩니다.
예제 2: 특정 컨텍스트 내에서만 스타일 적용하기 (:where() 활용)
:where()는 가중치가 0이므로, 다른 스타일 규칙에 의해 쉽게 덮어쓰여집니다. 이는 특정 영역(예: article 태그 내부)에서만 적용되는 기본 스타일을 정의할 때 유용합니다. 예를 들어, article 내부의 모든 p, h1, ul 태그에 대해 기본적인 여백을 설정하고 싶을 때 사용할 수 있습니다.
중복 코드 (기존 방식)
1article p { 2 margin-bottom: 1em; 3} 4 5article h1 { 6 margin-bottom: 1em; 7} 8 9article ul { 10 margin-bottom: 1em; 11}
:where()를 사용한 개선 코드
:where()를 사용하면 article 내부의 모든 대상 요소에 대해 단 한 번의 규칙으로 스타일을 적용할 수 있습니다. :where()의 가중치가 0이기 때문에, 만약 article p에 더 구체적인 다른 스타일이 정의되어 있다면 해당 스타일이 우선 적용됩니다. 이는 :where()를 사용한 스타일이 '기본값' 또는 '폴백(fallback)'처럼 작동하게 하여, 다른 명시적인 스타일을 방해하지 않으면서도 전반적인 스타일을 관리할 수 있게 합니다.
1/* article 내부의 p, h1, ul에 대한 기본 margin-bottom 설정 */ 2/* 가중치 0이므로 다른 p, h1, ul 스타일 규칙에 의해 쉽게 덮어쓰여짐 */ 3:where(article) p, 4:where(article) h1, 5:where(article) ul { 6 margin-bottom: 1em; 7} 8 9/* 또는 더 간결하게 */ 10:where(article) { 11 p, 12 h1, 13 ul { 14 margin-bottom: 1em; 15 } 16}
이 방식은 article 태그 안에 포함된 모든 p, h1, ul 요소에 대해 margin-bottom: 1em;을 적용합니다. 만약 article h1에 대해 별도의 더 높은 가중치의 스타일이 있다면, :where()로 설정한 margin-bottom은 무시되고 별도의 스타일이 적용될 것입니다. 이는 코드의 중복을 줄이면서도 유연성을 유지하는 강력한 방법입니다.
예제 3: :is()와 :where() 조합 사용하기
:is()와 :where()는 함께 사용하여 더욱 복잡하고 유연한 스타일링 규칙을 만들 수 있습니다. 예를 들어, card 컴포넌트의 다양한 내부 요소(h2, p, img)에 공통 스타일을 적용하되, 특정 클래스(.featured)를 가진 카드에만 다른 스타일을 적용하고 싶을 때 활용할 수 있습니다.
1/* 모든 카드 요소에 대한 기본 스타일 */ 2/* :where()의 가중치가 0이므로, 다른 규칙에 의해 쉽게 덮어쓰여짐 */ 3:where(.card) { 4 border: 1px solid #ccc; 5 padding: 1rem; 6 margin-bottom: 1rem; 7} 8 9/* 카드 내부의 제목, 본문, 이미지에 대한 공통 스타일 */ 10/* :is()는 .card의 가중치를 상속받음 */ 11.card :is(h2, p, img) { 12 margin-bottom: 0.5rem; 13} 14 15/* .featured 클래스를 가진 카드에만 적용되는 추가 스타일 */ 16/* :where()는 가중치 0 */ 17:where(.card.featured) { 18 border-color: gold; 19 background-color: #fffbeb; 20} 21 22/* .featured 카드 내부의 제목에만 다른 스타일 */ 23/* :is()는 .card.featured의 가중치를 상속받음 */ 24.card.featured :is(h2) { 25 color: darkgoldenrod; 26}
이 예제에서는:
:where(.card)를 사용하여 모든 카드에 대한 기본 스타일을 정의하며, 이 스타일은 다른 명시적인.card관련 규칙에 의해 쉽게 덮어쓰일 수 있습니다. (가중치 0).card :is(h2, p, img)는.card의 가중치를 상속받아, 카드 내부의 제목, 본문, 이미지에 대한 공통 마진을 적용합니다. (가중치 1):where(.card.featured)는 특별히 강조된 카드에 대한 스타일을 정의합니다. (가중치 0).card.featured :is(h2)는 강조된 카드의 제목에만 적용되는 스타일을 정의하며,.card.featured의 가중치를 상속받습니다. (가중치 2)
이처럼 :where()와 :is()를 조합하면 코드의 중복을 줄이면서도 스타일의 우선순위와 적용 범위를 세밀하게 제어할 수 있습니다.
3. 주의할 점 및 권장사항
:where()와 :is()는 강력한 도구이지만, 몇 가지 주의할 점과 함께 사용하면 더욱 효과적입니다.
- 브라우저 호환성:
:where()와:is()는 비교적 최신 기능으로, 모든 브라우저에서 완벽하게 지원되지 않을 수 있습니다. 특히 Internet Explorer와 같은 구형 브라우저에서는 지원되지 않습니다. 따라서 프로덕션 환경에서 사용할 때는 caniuse.com과 같은 사이트를 통해 브라우저 지원 현황을 반드시 확인하고, 필요하다면 폴리필(polyfill)을 사용하거나 대체 방법을 고려해야 합니다. (최근에는 대부분의 주요 브라우저에서 잘 지원됩니다.) - 가중치 이해:
:is()의 가중치가 내부 선택자 중 가장 높은 것을 따른다는 점,:where()의 가중치가 항상 0이라는 점을 명확히 이해해야 합니다. 이를 잘못 이해하면 예상치 못한 스타일 충돌이나 우선순위 문제가 발생할 수 있습니다. 특히:is()를 사용할 때, 내부 선택자 중 가장 가중치가 높은 선택자가 무엇인지 인지하고 있어야 합니다. 예를 들어:is(div, .class)는.class의 가중치를,:is(div, #id)는#id의 가중치를 상속받습니다. - 가독성: 과도하게 복잡한 선택자 리스트를
:is()나:where()안에 넣으면 오히려 코드 가독성이 떨어질 수 있습니다. 직관적으로 이해할 수 있는 범위 내에서 사용하는 것이 좋습니다. 예를 들어,p, h1, h2, h3, h4, h5, h6과 같이 너무 많은 요소를 나열하기보다는,.content p, .content h1, .content h2와 같이 좀 더 구체적인 선택자 또는 CSS 변수 등을 활용하는 것이 나을 수 있습니다. - 대안: 만약 브라우저 호환성이 매우 중요하거나,
:where()/:is()의 가중치 동작 방식이 복잡하게 느껴진다면, BEM(Block, Element, Modifier)과 같은 CSS 방법론을 사용하거나 CSS 변수(Custom Properties)를 활용하여 중복을 관리하는 것도 좋은 대안이 될 수 있습니다.
4. 정리 및 참고
CSS :where()와 :is() 선택자는 중복되는 스타일 규칙을 효과적으로 통합하고 관리할 수 있게 해주는 강력한 기능입니다. :is()는 가장 높은 가중치를 상속받아 기존 스타일과의 충돌을 최소화하면서 유연성을 제공하고, :where()는 가중치 0을 이용하여 스타일의 범위를 지정하거나 기본값을 설정하는 데 유용합니다. 이 두 선택자를 적절히 활용하면 CSS 코드의 중복을 줄이고, 유지보수성을 높이며, 더 깔끔하고 효율적인 스타일링을 구현할 수 있습니다.
참고 자료