• [CSS 방법론] BEM 방식

    2020. 5. 17.

    by. 나나 (nykim)



    오늘은 CSS 방법론을 다뤄보겠습니다 ;-)

    말이 거창하긴 한데 쉽게 풀어쓰면 'CSS 클래스네임을 어떻게 지으면 좋을지' 고민해보는 거죠.
    방법론에는 여러 가지가 있는데, 최근 BEM을 실무에 도입하면서 매력을 느끼고 있어요!

     

    그래서 이번 포스트는 BEM에 대한 내용입니다. 한 번 읽어 보시면 도움이 되리라 생각합니다.

     

    (*이 포스트의 틀린 부분이나 고칠 점이 있다면 알려주세요! 감사히 배우겠습니다.)

     

     


     

     

    1. BEM의 기본 구조

    BEM은 Blcok, Element, Modifier를 뜻합니다. 저 세 가지로 구성된 이름을 짓는 거죠! 그리고 각각 __--로 구분합니다.
    이렇게 생긴 클래스네임, 어디선가 본 적 있지 않으신가요? (너의 이름은...?!)

     

    .header__navigation--navi-text {
      color: red;
    }

     

    위 코드에서 headerBlock, naviagtionElement, navi-textModifier가 됩니다.

     

    BEM은 기본적으로 ID를 사용하지 않으며, class만을 사용합니다.
    또, '어떻게 보이는가'가 아니라 '어떤 목적인가'에 따라 이름을 짓습니다. 예를 들어, 에러 메시지를 띄우는 P 태그에게는 .red가 아닌, .error라는 이름을 줘야합니다.
    이름을 연결할 때는 block-name과 같이 하이픈 하나만 써서 연결합니다

     

    그럼 각 B.E.M이 정확히 뭘 의미하는지 파고들어봅시다 :>

     

     


     

     

    2. Block / Element / Modifier

     

     

    Block

    블럭은... 블럭입니다! 그냥 우리가 블럭하면 떠오르는 형태있잖아요? 고 네모난 거. 약간 독립적이고 분리할 수 있는... 근데 좀 덩어리가 있는... 그런 느낌의 무언가...?

     

     

     

     

     

     

    ...라고 설명이 끝나면 안되겠죠(허허허)

     

    조금 구체적으로 설명하자면, 재사용 가능한 기능적으로 독립적인 페이지 컴포넌트(A functionally independent page component that can be reused)을 블럭이라고 부릅니다.
    즉, 똑! 떼어다가 어딘가에 쓸 수 있는 단위를 말합니다.

    예를 들어, 위 이미지의 Logo 블럭은 어딘가에 종속되지 않습니다. 헤더에 쓰일 수도 있고, 푸터에 쓰일 수도 있고, 여기저기 붙였다 떼었다 할 수 있어요. 이렇게 재사용할 수 있는 요소를 블럭이라고 합니다.

     

    또, 블럭은 블럭을 감쌀 수 있습니다. .header>.logo는 header라는 블럭 안에 logo라는 블럭이 들어간 형태입니다.

     

     

     

    Element

    엘리먼트는 블럭을 구성하는 단위입니다.
    블럭은 독립적인 형태인 반면, 엘리먼트는 의존적인 형태입니다. 자신이 속한 블럭 내에서만 의미를 가지기 때문에 블럭 안에서 떼어다 다른 데 쓸 수 없습니다.

     

     

     

     

    <form class="search-form">
        <input class="search-form__input"/>
        <button class="search-form__button">Search</button>
    </form>

     

    위 예시에서 .search-form은 블럭이고, .search-form__input.search-form__button은 엘리먼트입니다.
    저 search-form이란 블럭은 떼어내서 요기조기 마음껏 붙여도 됩니다. 하지만 내부의 input과 button은 검색을 위한 인풋창이자 버튼이기 때문에, search-form 안에서만 존재 의미가 있는 엘리먼트입니다.

     

    엘리먼트 또한 중첩이 가능합니다. .block > .block__element1 > .block__element2도 가능하다는 거죠!

    제 생각엔 BEM의 재미있는 점은, .block_element2.block_element1의 하위 엘리먼트로 보지 않고 둘 다 똑같이 .block의 엘리먼트로 취급한다는 점 같아요.
    그래서 클래스네임에 캐스케이딩을 여러번 표시할 필요가 없죠.

     

    따라서 BEM은 아래와 같이 사용하지 않습니다.

     

    <form class="search-form">
      <div class="search-form__content">
          <input class="search-form__content__input"/>
          <button class="search-form__content__button">Search</button>
      </div>
    </form>

     

    위 형태는 block-name__element-name이란 형식을 따르고 있지 않거든요! 대신에 아래와 같이 사용합니다.

     

    <form class="search-form">
      <div class="search-form__content">
          <input class="search-form__input"/>
          <button class="search-form__button">Search</button>
      </div>
    </form>

     

     

    Modifier

    모디파이어는 블럭이나 엘리먼트의 속성을 담당합니다. 생긴 게 조금 다르거나, 다르게 동작하는 블럭이나 엘리먼트를 만들 때 사용하면 됩니다.

     

     

     

     

    <ul class="tab">
      <li class="tab__item tab__item--focused">탭 01</li>
      <li class="tab__item">탭 02</li>
      <li class="tab__item">탭 03</li>
    </ul>

     

    위 코드에서 --focused가 수식어에 해당합니다. 저렇게 작성된 걸 불리언(boolean) 타입이라고 하는데, 그 값이 true라고 가정하고 사용합니다.
    그럼 다른 타입도 있을까요? 네, 키-밸류(key-value) 타입도 있어요! 이건 하이픈으로 성질-내용을 작성합니다.

     

    <div class="column">
      <strong class="title">일반 로그인</strong>
      <form class="form-login form-login--theme-normal">
        <input type="text" class="form-login__id"/>
        <input type="password" class="form-login__password"/>
      </form>
    </div>
    
    <div class="column">
      <strong class="title title--color-gray">VIP 로그인 (준비중)</strong>
      <form class="form-login form-login--theme-special form-login--disabled">
          <input type="text" class="form-login__id"/>
          <input type="password" class="form-login__password"/>
      </form>
    </div>

     

    위 예시에서 color-graytheme-normal가 key-value 타입에 해당합니다.

     

     


     

     

    3. BEM 연습

    여기까지 BEM에 대해 가볍게 훑어보았습니다! 하지만 아직까지는 이런 느낌이네요.

     

     

    한 번 직접 써보면 감이 오지 않을까요!?
    UYEONG님께서 친절히 데모를 올려주셨기에 따라서 작성해보기로 했습니다. (감사합니다!)

     

    우선은 이 페이지의 헤더 부분만 따라서 작성해봅니다.

     

     

    가장 큰 블럭 나누기

    헤더를 큼직한 블럭 단위로 쪼개보면 이렇게 될 거 같아요! 실제로는 최대폭 조절을 위한 inner가 들어가 있기 때문에, 마크업은 아래와 같습니다.

     

     

    <div class="header">
      <div class="header__inner">
        <div class="tabzilla"></div>
        <div class="header__logo"></div>
        <div class="header__auth"></div>
        <div class="nav"></div>
        <div class="header__search"></div>
      </div>
    </div>

     

    위 코드에서 .header는 블럭이며, .header__로 시작하는 태그들은 모두 엘리먼트에 해당합니다.
    한편, .tabzilla.nav는 엘리먼트가 아닌 블럭이네요. 아무래도 다른 곳에서도 독립적으로 쓰일 수 있기 때문에 블럭으로 지정한 것으로 보입니다.

     

     

    로고 아래 링크 넣기

    MDN 로고와 mozilla 로고에는 링크가 들어갑니다. 우선 .header__logo의 마크업입니다.

     

    <div class="header__logo">
      <div class="logo">
        <a href="#" class="logo__link">
          <h1 class="blind">MDN</h1>
        </a>
      </div>
    </div>

     

    .header__logo라는 요소 아래 logo라는 블럭이 나왔네요 (뜨든) 이 역시 .logo는 별도로 쓰일 수 있기 때문으로 보입니다. 그럼 어떻게 스타일링 해야 할지 감이 오네요.
    .header__logo헤더 내에서 로고 위치를 잡는 데 쓰고, .logo로고 이미지를 지정하는 데 쓰면 되겠죠! 만약에 이 로고를 푸터에도 써야한다면, .footer__logo > .logo로 마크업하고 .footer__logo에 css로 위치만 잡아주면 될 거고요.

     

    mozilla 로고 역시 __link가 붙는 엘리먼트가 들어갑니다.

     

    <div class="tabzilla">
      <a href="#" class="tabzilla__link">
        <span class="blind">mozilla</span>
      </a>
    </div>

     

     

    Sign in with 부분 작성하기

    <div class="header__auth">
      <div class="auth">
        <div class="auth-means"></div>
        <div class="auth-picker"></div>
      </div>
    </div>

     

    이어서 .header__auth 부분입니다. 이 부분도 auth라는 블럭으로 떼어내겠습니다.
    한편, 마우스를 오버하면 아래 리스트가 나오기 때문에 auth를 크게 위/아래로 나눠주었습니다. 이때, __를 써서 요소로 나눈 게 아니라 -를 이용해 블럭으로 나누셨더라구요!

     

    <div class="auth-means">
      <p class="auth-means__text">Sign in with

    <ul class="auth-means__list"> <li class="auth-means__item auth-menas__item--persona"> <a href="#"> <i class="fa fa-user"></i> <span class="blind">Persona</span> </a> </li> <li class="auth-means__item auth-menas__item--github"> <a href="#"> <i class="fa fa-github"></i> <span class="blind">Github</span> </a> </li> </ul> </div>

     

    auth-mean 부분은 텍스트+리스트로 나누어 줍니다. 리스트 아래에는 아이템이란 이름의 엘리먼트가 있어요.

     

    코드를 잘 보시면 드디어 Modifier가 나온 걸 볼 수 있습니다! 아이콘이 2개인데, --persona--github 따로따로 스타일을 지정해주기 위함입니다.

     

    <div class="auth-picker">
      <ul class="auth-picker__list">
        <li class="auth-picker__item">
          <a href="#">
            <i class="fa fa-user"></i>
            <span>Persona</span>
          </a>
        </li>
        <li class="auth-picker__item">
          <i class="fa fa-github"></i>
          <span>Github</span>
        </li>
      </ul>
    </div>

     

    auto-picker 부분입니다. .auth-picker__item은 아이콘만 다르고 서로 동일하기 때문에 굳이 모디파이어를 지정해주지 않은 게 보입니다.

     

     

    Search 부분 작성하기

    다음은 검색 영역입니다. 이 검색 영역은 별도로 분리될 수 있다고 판단해 .search라는 블럭 아래에서 작성합니다. 참고로 이 input은 focus 시 확장되어야 해서, --extend라는 모디파이어를 별도로 만든 다음 클릭했을 때 추가되는 스크립트도 필요합니다.

     

    <div class="header__search">
      <div class="search">
        <form>
          <div class="search__inner">
            <div class="search__title">
              <label for="main-search">
                <i class="fa fa-search"></i>
                <span class="blind">Search</span>
              </label>
            </div>
            <input type="text" id="main-search" class="search__input"/> <!--focus시 .search__input--extend 클래스를
            추가합니다-->
            <input type="submit" class="search__button" value="Search"/>
          </div>
        </form>
      </div>
    </div>

     

     

    Navigation 부분 작성하기

    <div class="nav">
      <ul class="nav__list">
        <li class="nav__item">
          <a href="#" class="nav__link">
            <span>Web Platfrom</span>
            <i class="fa fa-caret-down"></i>
          </a>
          <div class="nav__submenu nav__submenu--col2"></div>
          <div class="nav__submenu"></div>
          <div class="nav__submenu"></div>
        </li>
      </ul>
    </div>

     

    마지막으로 네비 부분입니다! 서브메뉴가 있는 네비 아이템도 있기 때문에, 우선 .nav__link + .nav__submenu로 나눠줍니다.
    아, 첫 번째 아이템은 서브메뉴가 2줄이라서 --col2라는 모디파이어를 추가했습니다.

     

    <div class="nav__submenu">
      <div class="nav-submenu">
        <div class="nav-submenu__column"></div>
        <div class="nav-submenu__column"></div>
        <button class="nav-submenu__close">
          <span class="blind">Close submenu</span>
          <i class="fa fa-times-circle"></i>
        </button>
      </div>
    </div>
    <div class="nav-submenu__column">
      <strong class="nav-submenu__title">Technologies</strong>
      <ul class="nav-submenu__list">
        <li class="nav-submenu__item">
          <a href="#" class="nav-submenu__link">HTML</a>
        </li>
      </ul>
    </div>

     

    .nav__submenu아래에 .nav-submenu라는 블럭이 나왔네요. 그럼 .nav__submenu와 .nav-submenu가 할 일은 달라질 거에요.

    .nav__submenunav 아래의 엘리먼트이기 때문에 네비 안에서의 위치를 잡아주는 용도로 씁니다.
    한편, .nav-submenu는 블럭이기 때문에 스타일링(배경색 지정, 폰트색 지정 등)을 하는 용도로 사용하면 되겠습니다.

     

     

    스타일링

    마크업 작업이 모두 끝났습니다 😎 이제 CSS로 스타일링할 차례에요.
    저는 SCSS 방식으로 빠르게 작성했습니다. 스타일 내부보다는 스타일 작성 방식을 보시면 좋겠습니다.

     

    See the Pen BEM 작성법 연습 by NY KIM (@nykim_) on CodePen.

     

     


     

     

    4. 정리

     

    좋았던 점

    1. 클래스네임만으로 마크업 구조를 알 수 있습니다.
    블럭과 엘리먼트로 구분되기 때문에 어디서부터 떼어다 쓸 수 있는지, 어디부터 종속되는지 알 수 있습니다.

     

    2. SASS의 부모참조자(&)와 찰떡궁합!
    쓰기가 무척 편해요!!

     

    3. 작성된 SASS에서 요소를 쉽게 찾을 수 있습니다.
    예를 들어, .header 아래에 &__logo, &__search로 작성하기 때문에 "아, 저게 헤더 아래 로고고 저건 헤더 아래 검색이구나"란 걸 바로 알 수가 있어요.

     

    4. SASS 작성 시, 늘어지는 셀렉팅을 막아줍니다.
    기존에 nested 방식으로 SASS를 작성하면, 컴파일 시 셀렉팅이 끝도 없이 길어지는 경우가 있었거든요 (.header .nav .list .item .link )처럼요.
    그런데 BEM 방식을 쓰면, 너도나도 엘리먼트라서 굳이 깊게 nested할 필요가 없어집니다.

     

    /*기존에 쓰던 방식*/
    .header {
       .nav {
          position: absolute;
          .list {
             list-style: none;
          }
          .item {
             /*너무 길어진다 싶을 때만 밖으로(근본없는 들여쓰기!)*/
             outline: 0;
             .link {
                color: red;
             }
          }
       }
    }
    
    /*BEM*/
    .header {
       &__nav {
          position: absolute;
       }
       &__list {
          clor: red;
       }
       &__item {
          /*여기 있는 모든 요소들은 블럭 내의 엘리먼트이기 때문에 캐스케이딩과 무관하게 병렬 작성*/
          outline: 0;
       }
       &__link {
          color: red;
       }
    }

     

     

    아쉬웠던 점

    1. 클래스네임이 너무 길어요ㅠ.ㅠ
    마크업이 한눈에 들어오지 않는다는 단점이 있습니다.
    특히 스크립트로 모디파이어를 변경해야할 때, classList.add("block-name__element-name--modifier")처럼 길게 작성해야하는 건 불편해보입니다.

     

    2. 더블클릭 선택이 불편해요.
    하이픈과 언더바가 혼재되어 있어, 더블클릭해서 클래스네임을 선택할때 한 번에 선택이 안 되는 문제가 있어요.

     

     

    정리

    이렇게 BEM에 대해 살펴보았습니다. 살짝이나마 감이 오셨으면 좋겠네요.

    장점도 단점도 있지만, 장점이 더 많기에 디자인~마크업~프론트엔드 간의 의사소통이 활발하고, 마크업 단계부터 구조를 탄탄하게 짜야한다면 BEM을 추천드려요 :)

     

    그럼 오늘도 화이팅! 🤘


    댓글 5

    • 프로필사진
      gusto 2019.12.19 17:14

      잘보고갑니당~

    • 프로필사진
      새날다 2020.05.19 13:29

      너무 쉽게 설명을 잘해놓으셧네요. ^^

    • 프로필사진
      나그네 2020.06.22 10:22

      지금까지 본 BEM 포스팅 중 가장 정확하게 가르쳐주는 거 같습니다.
      특히 하위 엘리멘탈 취급과 중첩을 어떻게 해야할지가 가장 중요한 점인데 이걸 자세히 설명해주셔서 좋았습니다.