BEM의 10가지 일반적인 문제와 이를 피하는 방법
본문은 Battling BEM CSS: 10 Common Problems And How To Avoid Them을 입맛대로 번역해 옮긴 글입니다. 원문 그대로 번역한 것이 아닌, BEM을 사용했을 때 발생할 수 있는 문제들을 해결하는 아이디어에 대해 개인의 생각도 일부 반영되어 있습니다. 그리고 본문에는 BEM 방법론에서 사용되는 용어들이 자주 등장하기에, BEM이 어떤 것인지 미리 알아두고 보면 좋습니다.
BEM은 충분히 매력적인 방법론이지만, 사람들은 저마다 취향이 다르듯, 이를 환영하지 않는 개발자도 많습니다. 대표적인 이유로 BEM은 클래스 이름을 통한 모듈화를 하기 좋지만, 태그의 중첩이 깊어질 수록 클래스 이름이 지나치게 길어지는 문제가 있습니다. 그래서 원문의 작성자는 BEM의 딜레마에 대한 조언을 소개함과 동시에, 기존에 BEM을 많이 사용하는 개발자들이 이를 더 효과적으로 사용할 수 있도록 하는 것을 목표로 작성했다 말합니다.
오역이 있을 수 있으니, 댓글로 남겨 주시면 감사하겠습니다 😀
1. 중첩이 깊어졌을 때는 어떻게 표현해야 합니까?
<div class="c-card">
<div class="c-card__header">
<!-- 중첩의 깊이가 깊어진 경우 -->
<h2 class="c-card__header__title">Title text here</h2>
</div>
<div class="c-card__body">
<img class="c-card__body__img" src="some-img.png" alt="description">
<p class="c-card__body__text">Lorem ipsum dolor sit amet, consectetur</p>
<p class="c-card__body__text">Adipiscing elit.
<a href="/somelink.html" class="c-card__body__text__link">Pellentesque amet</a>
</p>
</div>
</div>
이 문제는 BEM을 환영하지 않는 사람들이 대표적으로 내세우는 이유입니다. 중첩이 깊어질 수록 위 코드와 같이 클래스명이 너무나 길어집니다. 그렇기에 이는 효율적이지 못하다 생각하는데, Element는 오직 하나만 나타나는 것이 좋다 생각합니다. BEM에 의한 명명은 DOM 생성과 크게 관계가 없기 때문에, 가독성과 효율성 향상을 위해 최상위 Block과의 관계 정도만 표현해도 충분합니다.
위 코드를 개선하면 다음과 같습니다.
<div class="c-card">
<div class="c-card__header">
<h2 class="c-card__title">Title text here</h2>
</div>
<div class="c-card__body">
<img class="c-card__img" src="some-img.png" alt="description">
<p class="c-card__text">Lorem ipsum dolor sit amet, consectetur</p>
<p class="c-card__text">Adipiscing elit.
<a href="/somelink.html" class="c-card__link">Pellentesque amet</a>
</p>
</div>
</div>
최상위 Block인 c-card는 계속 유지가 되고 있기 때문에, Element를 하나만 작성해도 구조는 깨지지 않습니다.
2. 네임스페이스를 써야 하나요?
1번의 샘플 코드를 보면, 클래스 이름 앞에 c-라는 접두사가 붙은 것을 확인할 수 있습니다. 이는 .c-card 클래스를 가진 태그가 컴포넌트(Component)임을 의미하며, BEM 클래스에 대한 코드 가독성을 높일 수 있는 네임스페이스 활용법이라 합니다. 네임 스페이스란 개체를 구분할 수 있는 범위를 말합니다.
BEM에서 사용해 볼만한 접두사는 이런 것들이 있습니다.
타입 | 접두사 | 예시 | 설명 |
Component | c- | c-card c-checklist |
독립적으로 재사용 가능한 요소입니다. |
Layout | l- | l-grid l-container |
컴포넌트를 배치하고, 레이아웃의 용도로 사용합니다. |
Helpers | h- | h-show h-hide |
유틸적인 용도로 사용 가능하며, 종종 !important를 사용해 특수성을 높일 수 있습니다. 일반적으로 위치 지정 또는 가시성을 위한 목적으로만 사용합니다. |
States | is- has- |
is-visible has-loaded |
컴포넌트가 가질 수 있는 상태를 나타냅니다. |
JS Hooks | js- | js-tab-switcher | 자바스크립트에 의해 요소가 동작될 수 있음을 의미합니다. 이는 스타일이 아닌, 자바스크립트만을 위한 네임스페이스입니다. |
이러한 네임스페이스를 사용하면 코드를 더 쉽게 읽을 수 있고, 어떤 용도인지 파악하기도 좋습니다. 여기서 더 나아가 네임스페이스에 qa-를 붙여 품질 테스트용임을 알리거나, ss-를 붙여 서버에서 다뤄질 수도 있다는 의미를 적용해 볼 수도 있습니다. 다음 문제(3번)에서 네임스페이스를 활용한 케이스를 확인할 수 있습니다.
3. Wrapper 클래스의 이름은 어떻게 지어야 할까요?
일부 구성 요소들은 하위 항목들에 대해 레이아웃을 지정할 필요가 있습니다. 이 경우 위에서 알아본 l- 접두사를 붙여 레이아웃을 만들고, 요소를 내부에 포함시킬 수 있습니다.
<ul class="l-grid">
<li class="l-grid__item">
<div class="c-card">
<div class="c-card__header">
[…]
</div>
<div class="c-card__body">
[…]
</div>
</div>
</li>
<li class="l-grid__item">
<div class="c-card">
<div class="c-card__header">
[…]
</div>
<div class="c-card__body">
[…]
</div>
</div>
</li>
<li class="l-grid__item">
<div class="c-card">
<div class="c-card__header">
[…]
</div>
<div class="c-card__body">
[…]
</div>
</div>
</li>
</ul>
여기서 중요한 것은 이름이 아닌 명명 규칙을 준수하는 것이 핵심입니다. 만약 부모 요소들도 구조적으로 의미있는 이름으로 정하고 싶다면, l-card-list와 같은 식으로 자유롭게 지으면 됩니다.
4. 교차 컴포넌트(Cross-Component)?
일반적으로 직면하는 문제로 스타일링이나 위치가 상위 컨테이너에 의해 영향을 받는 경우를 생각해볼 수 있습니다. 한 번 다음과 같은 버튼 요소가 위 샘플 코드의 c-card__body 내부에 포함된다 가정해 보겠습니다.
<button class="c-button c-button--primary">Click me!</button>
일반 버튼 컴포넌트에 스타일 차이가 없다면, 별도의 문제가 없기 때문에 우리는 그냥 이대로 작성할 것입니다. 하지만 c-card 내부에서만 버튼에 대해 다른 스타일을 적용하고 싶다면 어떻게 해야 할까요? 아마 이런 식으로 생각할 것입니다.
<button class="c-button c-card__c-button">Click me!</button>
BEM에서 클래스 이름을 위와 같이 컴포넌트가 섞이는 현상을 mix라고 말합니다. 위 코드는 원하는 스타일을 충분히 적용시킬 수 있지만, 소스 코드 순서에 의존하게 됩니다. 만약 c-card__c-button의 스타일링이 c-button보다 앞에 있다면, 해당 블록에 정의한 스타일링은 무시될 것입니다.
진정한 모듈형 UI 요소들은 상위 컨테이너와 무관해야 하며, 어디에 놓아도 동일하게 보여야 합니다. 교차 컴포넌트에 의한 mix는 이를 위반하기 때문에, 되도록이면 최소한의 스타일링에 대해서는 클래스를 추가로 만드는 것이 좋습니다. 만약 모서리가 둥글고, 글자가 작은 버튼을 만들고 싶다면 다음과 같이 만드는 것이 좋습니다.
<button class="c-button c-button--rounded c-button--small">Click me!</button>
이렇게 만들게 되면 언제든 재사용할 수 있고, 상위 컨테이너에도 영향을 받지 않습니다.
5. 수정자(Modifier) or 새로운 컴포넌트(Component)?
만약 c-card와 비슷한 c-panel 요소가 있는 경우 우리는 새롭게 컴포넌트를 만들어야 하는지, 수정자(Modifier)를 사용해야 하는지 고민할 수 있습니다. 이런 경우 수정자부터 시작하는 것이 좋습니다. 그러나 그렇게 만든 요소의 CSS를 관리하기 어려워 진다면, 이 때는 새로운 컴포넌트로 만드는 것이 좋습니다.
웬만하면 가장 좋은 방법은 다른 개발자 또는 디자이너와 소통하며 결정하는 것입니다.
6. 상태(State)를 다루는 방법
이 문제는 활성화(able) 또는 열린(open) 상태에서 컴포넌트를 다룰 때 발생합니다. 샘플 코드에서 c-card가 활성화된 상태라 하고, 이 경우 테두리를 멋지게 스타일링한다 가정하겠습니다. 이런 경우 상태를 어떤 식으로 표현할 수 있을까요? 이 경우 독립적인 상태 클래스와 BEM의 수정자를 이용하는 방법이 있습니다.
<!-- standalone state hook -->
<div class="c-card is-active">
[…]
</div>
<!-- or BEM modifier -->
<div class="c-card c-card--is-active">
[…]
</div>
일관성을 위해 BEM의 수정자를 사용하는 것도 좋지만, 독립적인 상태 클래스의 장점은 어디에서나 사용 가능하단 점입니다. 동일한 작업을 하는 부분이 있다면, 이는 독립적으로 만들어 재사용하는 편이 좋을 것 같습니다.
7. 요소에 클래스를 추가하지 않는 경우도 있나요?
스타일링을 하지 않는 태그의 경우, 클래스명을 적지 않고 넘어가는 경우가 많습니다. 예를 들어 스타일링하지 않는 p 요소에 대해 .c-card__text가 아닌 .c- card p의 상태로 두는 것이지요. 물론 일반적으로 그렇게 하는 것이 맞지만, 중첩 관계를 표현하기 위해 클래스명은 적어주는 것이 좋습니다. 물론 초기의 불편함은 이해하지만, 궁극적으로 우리가 만든 컴포넌트들이 부작용 없이 독립적으로 존재하기 위한다면 충분한 가치가 있습니다.
8. 컴포넌트를 중첩하는 방법?
c-card 컴포넌트에 체크 리스트를 추가하면 다음과 같은 코드를 만날 수 있습니다.
<div class="c-card">
<div class="c-card__header">
<h2 class="c-card__title">Title text here</h3>
</div>
<div class="c-card__body">
<p>I would like to buy:</p>
<!-- 컴포넌트의 중첩? -->
<ul class="c-card__checklist">
<li class="c-card__checklist__item">
<input id="option_1" type="checkbox" name="checkbox" class="c-card__checklist__input">
<label for="option_1" class="c-card__checklist__label">Apples</label>
</li>
<li class="c-card__checklist__item">
<input id="option_2" type="checkbox" name="checkbox" class="c-card__checklist__input">
<label for="option_2" class="c-card__checklist__label">Pears</label>
</li>
</ul>
</div>
</div>
체크 리스트는 충분히 컴포넌트로 재사용할 수 있는데, 위와 같이 만들게 되면 재사용 불가능합니다. 그렇기 때문에 차라리 ul과 li 요소를 각각 레이아웃으로 만들고, 체크박스를 독립적인 컴포넌트로 만드는 것이 좋습니다.
<div class="c-card">
<div class="c-card__header">
<h2 class="c-card__title">Title text here</h3>
</div>
<div class="c-card__body">
<p>I would like to buy:</p>
<!-- Nice한 모듈화 -->
<ul class="l-list">
<li class="l-list__item">
<!-- 재사용 가능한 중첩 컴포넌트 -->
<div class="c-checkbox">
<input id="option_1" type="checkbox" name="checkbox" class="c-checkbox__input">
<label for="option_1" class="c-checkbox__label">Apples</label>
</div>
</li>
<li class="l-list__item">
<div class="c-checkbox">
<input id="option_2" type="checkbox" name="checkbox" class="c-checkbox__input">
<label for="option_2" class="c-checkbox__label">Pears</label>
</div>
</li>
</ul>
</div>
</div>
이렇게 만들면 외부에 체크 리스트를 추가했을 때, 반복적인 스타일링을 하지 않아도 됩니다. 초기 작업이 조금 더 불편하지만, 이후 가독성과 캡슐화, 그리고 재사용성까지 고려한다면 적은 비용입니다. 지금까지 문제들을 살펴보면서, 이런 문제들은 흔히 다뤄지는 주제라는 것을 알게 되셨을 겁니다!
9. 컴포넌트의 클래스 이름이 무한하게 길어지는 문제
어떤 컴포넌트는 스타일링을 위해 너무나 많은 수정자를 적용하여 클래스 이름이 길어질 수 있습니다. 하지만 개인적으로 이는 문제가 되지 않는다 생각합니다. 오히려 이것은 코드를 읽기에 더 쉽고, 무엇을 해야 하는지 정확히 파악할 수 있기 때문입니다.
다음의 버튼은 여러 스타일링이 적용된 컴포넌트의 예시입니다.
<button class="c-button c-button--primary c-button--huge is-active">Click me!</button>
물론 아름답지는 않지만, 코드가 명확하다는 것은 인정해야 합니다.
만약 위의 방법이 싫다면, 클래스 이름을 이렇게도 만들 수 있습니다.
<button class="c-button-primary-huge is-active">Click me!</button>
이렇게 만들고 .className [class^="className"], [class*="className"] 등의 속성 선택자를 이용해 스타일링하는 방법도 있습니다. 의미있는 성능 개선이 있을 지는 모르겠습니다만, 이론적으로는 그나마 위와 같은 방법이 대안이 될 수 있습니다. 멀티 클래스를 사용하는 방법도 좋지만, 대안을 선호하는 사람들에게는 언급할 만한 가치가 있다 생각합니다.
10. 컴포넌트에 반응형에 따른 스타일 적용하기
이 문제는 지정된 크기(반응형)에서 변환되는 메뉴들에 대해 발생할 수 있습니다. 예를 들어 어떤 이미지를 담고 있는 리스트 컴포넌트가 작은 화면에서는 세로 리스트로 이미지를 보여주고 있지만, 큰 화면에서는 회전목마(Carousel) 형태로 보여주고 싶은 경우가 있습니다. 물론 c-navigation과 같이 하나의 클래스 이름을 가지고, 미디어 쿼리를 통해 스타일링해도 되지만, 좀 더 명확한 이름으로 독립적인 컴포넌트를 만드는 것이 합리적이라 생각합니다.
Harry Roberts라는 사람은 이것을 다루기 위해 @기호를 일종의 breakpoint로 사용하여, 화면 크기에 따라 스타일링이 적용되도록 하는 네임스페이스를 제안했습니다. 이 방법을 적용한다면 화면의 크기 또는 형식(print, screen, speech)에 따라 다른 스타일을 적용할 수 있습니다.
우선 클래스 이름은 아래와 같이 만들 수 있습니다.
<ul class="c-image-list@small-screen c-carousel@large-screen">
@ 기호와 함께 화면 크기에 대한 정보를 표시하여, 화면 크기에 따라 다른 스타일을 적용할 수 있도록 합니다.
.c-image-list\@small-screen {
/* styles here */
}
CSS에서는 @ 기호를 인식할 수 없기 때문에, \(백슬래시)를 앞에 붙여 사용해야 합니다. 이런 식으로 나누어 만들 필요를 저는 크게 느끼지 못하고 있지만, 만약 그렇게 해야 한다면 위 방법이 그나마 개발자와 친숙한 방법이라 생각됩니다.
결국 본인의 취향에 맞게 결정하면 될 부분이라 생각합니다.
함께 읽어보면 좋은 글 📖
'🌈 기술스택 > Web Basic' 카테고리의 다른 글
웹 접근성과 표준의 중요성 (0) | 2021.10.22 |
---|---|
Flex 대신 Grid를 사용해 레이아웃 만들기 (0) | 2021.10.21 |
웹 브라우저의 렌더링 과정 알아보기 (0) | 2021.10.05 |
BEM 방법론에 따라 클래스명 짓기 (0) | 2021.10.04 |
script 태그는 어느 곳에 위치하는 것이 좋을까? (0) | 2021.10.02 |
댓글
이 글 공유하기
다른 글
-
웹 접근성과 표준의 중요성
웹 접근성과 표준의 중요성
2021.10.22 -
Flex 대신 Grid를 사용해 레이아웃 만들기
Flex 대신 Grid를 사용해 레이아웃 만들기
2021.10.21 -
웹 브라우저의 렌더링 과정 알아보기
웹 브라우저의 렌더링 과정 알아보기
2021.10.05 -
BEM 방법론에 따라 클래스명 짓기
BEM 방법론에 따라 클래스명 짓기
2021.10.04