← 블로그 · · ConvertCaseHQ 팀 · 5분 분량

CSS 네이밍 규칙: kebab-case, BEM, 그리고 그 너머

CSS 클래스 이름은 대부분 소문자 kebab-case(.site-header, .price-tag)로 씁니다. CSS가 background-color, font-size, flex-direction처럼 자기 이름을 그렇게 짓기 때문입니다. BEM은 여기에 block__element--modifier라는 고정된 구조를 더합니다. 그래서 card__title--large 같은 클래스를 보면 어느 컴포넌트에 속하는지, 그중 어느 부분인지, 어떤 변형을 보여 주는지 알 수 있습니다.

이 글에서는 CSS에서 대소문자를 구분하는 것과 구분하지 않는 것, kebab-case가 기본이 된 이유, BEM 명명 규칙이 실제로 말하는 내용, 그리고 다른 방식을 쓰면 BEM이 필요 없어지는 경우를 살펴봅니다.

CSS에서 대소문자를 구분하는 것과 구분하지 않는 것

CSS 문법 자체는 ASCII 범위 안에서 대소문자를 구분하지 않으므로 COLOR: RED는 color: red와 똑같이 동작합니다. 다르게 동작하는 것이 세 가지 있습니다.

  • 클래스 이름은 대소문자를 구분합니다. .Card는 class="card"와 일치하지 않습니다. 유일한 예외는 쿼크 모드(quirks mode) 페이지로, 이때 브라우저는 클래스 이름을 대소문자 구분 없이 비교합니다. 모든 페이지를 <!DOCTYPE html>로 시작해야 할 이유가 하나 더 생기는 셈입니다.
  • 사용자 정의 속성은 대소문자를 구분합니다. --brand-color와 --Brand-Color는 서로 다른 변수입니다.
  • HTML 요소 이름과 속성 이름은 구분하지 않습니다. HTML 문서에서는 DIV와 div가 같은 요소를 선택합니다. XML이라면 달라집니다.

이 문제를 다시는 신경 쓰지 않는 가장 간단한 방법은 모든 클래스 이름과 사용자 정의 속성을 소문자로 쓰는 것입니다. 다른 언어와 파일 시스템이 대소문자를 어떻게 다루는지는 대소문자 구분 비교 글을 참고하세요.

CSS 클래스 이름에 kebab-case가 자리 잡은 이유

CSS에는 클래스 이름에 kebab-case를 강제하는 규칙이 없습니다. .cardTitle과 .card_title도 모두 유효합니다. 하이픈이 자연스러운 까닭은 클래스 이름 주변의 모든 것이 이미 하이픈을 쓰기 때문입니다. 속성 이름, space-between 같은 값, aria-label이나 data-user-id 같은 HTML 속성이 그렇습니다. 하이픈으로 가득한 스타일시트에 camelCase 클래스가 섞여 있으면 오타처럼 보입니다.

kebab-case가 불편해지는 곳은 JavaScript입니다. 식별자에 하이픈을 쓸 수 없기 때문입니다. 브라우저가 정한 이름은 브라우저가 알아서 처리합니다. data-user-id는 element.dataset.userId로 읽히고, background-color는 element.style.backgroundColor가 됩니다. 클래스 이름은 보통 문자열(classList.add("is-open"))로 다루므로 상관이 없습니다.

예외는 CSS Modules입니다. CSS Module은 클래스 이름을 JavaScript 객체로 내보내므로, .card-title은 styles["card-title"]로 써야 하지만 .cardTitle은 더 깔끔한 styles.cardTitle이 됩니다. 저희가 클래스 이름을 camelCase로 바꾸는 경우는 이 구성 하나뿐입니다.

BEM 명명법: 블록, 요소, 수정자

BEM(Block, Element, Modifier)은 Yandex에서 나왔습니다. 방법론의 역사 페이지에 따르면 그 출발점은 2005년으로 거슬러 올라가며, 2009년 3월 Yandex의 Lego 2.0 프레임워크 출시를 BEM의 시작으로, 2010년을 오픈 소스로 전환한 해로 기록하고 있습니다. 명명 규칙은 짧습니다.

  • 블록(Block): 그 자체로 의미가 있는 독립적인 컴포넌트입니다. .card, .search-form, .menu 등이 여기에 해당합니다.
  • 요소(Element): 블록의 일부로, 블록 밖에서는 의미가 없는 것입니다. 블록 이름, 밑줄 두 개, 요소 이름 순으로 짓습니다. 예: .card__title, .menu__item.
  • 수정자(Modifier): 모양, 동작, 상태를 바꾸는 플래그입니다. 블록이나 요소 이름, 하이픈 두 개, 수정자 이름 순으로 짓습니다. 예: .card--featured, .card__button--disabled. 수정자는 키와 값으로 이루어질 수도 있습니다. 예: .form--theme-xmas.

각 부분 안의 단어는 .search-form__submit-button처럼 하이픈 하나로 구분합니다. 컴포넌트 전체를 예로 들면 다음과 같습니다.

<article class="card card--featured">
  <h2 class="card__title">Spring sale</h2>
  <p class="card__body">Everything 20% off this week.</p>
  <a class="card__button card__button--disabled" href="/sale">Shop now</a>
</article>
.card { border: 1px solid #ddd; padding: 1rem; }
.card--featured { border-color: gold; }
.card__title { font-size: 1.25rem; }
.card__button--disabled { opacity: 0.5; pointer-events: none; }

클래식 BEM과 하이픈 두 개 스타일

방법론의 공식 사이트인 bem.info를 읽어 보면 수정자의 모양이 다릅니다. 클래식 방식은 수정자를 밑줄 하나로 구분합니다. 예: menu_hidden, menu__item_visible, menu__item_type_radio. -- 버전은 그곳에서 대안인 “Two Dashes style”(하이픈 두 개 스타일)로 소개되며, getbem.com이 문서화한 것이 바로 이 방식입니다. 둘 다 정식 BEM이니, 한 코드베이스 안에서 섞지만 마세요. bem.info는 하이픈 방식의 작은 함정도 언급합니다. HTML 주석 안에 하이픈 두 개가 있으면 유효성 검사에 실패할 수 있다는 점입니다.

틀리기 쉬운 BEM 규칙

  1. 요소의 요소는 없습니다. 카드 헤더 안의 제목도 card__header__title이 아니라 card__title입니다. HTML에서는 요소를 얼마든지 깊이 중첩해도 되지만, 이름은 평평하게 유지합니다. bem.info가 드는 이유는, 이름을 이어 붙이면 기존 코드를 바꾸지 않고는 요소를 옮기거나 추가하거나 제거할 수 없다는 것입니다.
  2. 수정자는 단독으로 쓰지 않습니다. class="button--primary"가 아니라 class="button button--primary"로 쓰세요. 수정자는 기본 클래스에 덧붙는 것일 뿐입니다.
  3. 클래스 선택자 하나만 쓰세요. .card .card__title이나 h2.card__title이 아니라 .card__title입니다. 널리 인정되는 예외는 블록 수정자가 그 안의 요소를 바꾸는 경우입니다. 예: .card--featured .card__title.
  4. 수정자 이름은 목적에 따라 지으세요. 이것은 BEM 규칙이 아니라 저희 의견입니다. --featured나 --error는 디자인을 바꿔도 살아남지만, --yellow는 브랜드 색상이 바뀌는 날 거짓말이 됩니다.
  5. 스타일용 클래스에 JavaScript 훅을 걸지 마세요. 이것도 저희 의견입니다. 스크립트에서는 .card__button 대신 data-action="close" 같은 data 속성으로 요소를 선택하세요. 그래야 스타일 때문에 클래스 이름을 바꿔도 동작이 깨지지 않습니다.

나중에 블록 이름을 바꾸는 일은 대부분 찾아 바꾸기로 해결됩니다. 템플릿을 찾기 및 바꾸기 도구에 붙여넣고 ‘정규식’ 옵션을 켠 뒤 card(__|--)를 product-card$1로 바꾸세요. 모든 요소 클래스와 수정자 클래스가 새 블록 이름으로 옮겨 가고, 일치 건수로 몇 개가 바뀌었는지 알 수 있습니다. 상관없는 단어까지 걸리지 않도록, 접미사가 없는 card 클래스는 따로 바꾸세요.

Sass와 네이티브 CSS 중첩으로 BEM 작성하기

Sass의 부모 선택자는 바깥 선택자에 접미사를 붙일 수 있으며, Sass 문서도 BEM을 예로 듭니다. 네이티브 CSS 중첩은 그렇게 할 수 없고, MDN도 이 점을 분명히 짚습니다. 중첩된 &__title은 타입 선택자로 읽히므로 .card__title이 만들어지지 않습니다.

// Sass: works
.card {
  &__title { font-size: 1.25rem; }    // compiles to .card__title
  &--featured { border-color: gold; } // compiles to .card--featured
}
/* Native CSS nesting: write the full class names */
.card {
  border: 1px solid #ddd;
  &.card--featured { border-color: gold; } /* .card.card--featured */
}
.card__title { font-size: 1.25rem; }

BEM 너머: BEM이 필요 없을 때

BEM은 전역 스타일시트에서 클래스 이름이 충돌하지 않게 하고 구조를 눈에 보이게 하려고 존재합니다. 충돌 문제를 다른 무언가가 해결해 준다면, 긴 이름으로 얻는 이점은 줄어듭니다.

  • CSS Modules는 기본적으로 클래스 이름의 범위를 로컬로 한정하므로, 한 컴포넌트의 평범한 .title이 다른 컴포넌트의 .title과 충돌하지 않습니다.
  • Tailwind 같은 유틸리티 우선 CSS는 직접 만든 클래스 이름 대부분을 font-bold 같은 미리 정의된 유틸리티로 대신하므로, 이름 짓는 일은 컴포넌트 쪽으로 옮겨 갑니다.
  • 컴포넌트가 몇 개뿐인 작은 사이트는 평범한 kebab-case에 컴포넌트별 접두사만 붙여도 충분한 경우가 많습니다.

그런 경우에도 직접 짓는 클래스 이름에는 kebab-case가 여전히 무난한 기본값입니다.

실전 원칙

  • 클래스 이름과 사용자 정의 속성은 언제나 소문자 kebab-case로 씁니다.
  • 여러 사람이 전역 스타일시트 하나를 함께 쓴다면 BEM을 씁니다. block__element--modifier 구조, 평평한 요소 이름, 기본 클래스 옆에 붙이는 수정자.
  • camelCase 클래스 이름은 CSS Modules 같은 도구가 이름을 JavaScript에 노출할 때만 씁니다.

컴포넌트 이름을 클래스로 바꾸고 싶다면 대소문자 변환기에 “Product Card”를 입력하고 kebab-case 버튼을 누르세요. product-card가 나옵니다. 환경 변수부터 URL까지 프로젝트의 다른 모든 층위에 쓰이는 규칙은 개발자용 대소문자 스타일 치트 시트를 참고하세요.

© 2026 ConvertCaseHQ. All rights reserved.