본문 바로가기
StyleSheet/CSS

[CSS] CSS 아키텍처와 방법론 (BEM, Sass/SCSS)

반응형

CSS 방법론(Architecture) - BEM과 OOCSS, 덮어쓰기 지옥에서 살아남는 클래스 네이밍의 예술

프로그래머 역사상 가장 해결하기 어려운 두 가지 난제는 "캐시 무효화(Cache Invalidation)"와 "이름 짓기(Naming Things)"라는 유명한 격언이 있습니다. HTML/CSS를 처음 배우는 초보자들은 .btn, .box, .title처럼 직관적이고 짧은 클래스 이름을 짓는 데 행복을 느낍니다. 하지만 실무에 투입되어 수만 줄의 CSS가 얽혀있는 대규모 프로젝트를 마주하게 되면, 이 짧고 낭만적인 클래스 이름들은 끔찍한 재앙의 씨앗이 됩니다. 어제 로그인 페이지의 .title 글씨를 빨간색으로 바꿨더니, 오늘 메인 페이지의 상품 목록 .title 마저 모조리 피눈물을 흘리며 빨갛게 물들어버리는 '전역 스코프(Global Scope) 충돌 지옥'이 열리기 때문입니다. 겁에 질린 주니어 개발자는 기존 코드를 건드리지 않기 위해 .main-page .content .box .title { color: blue !important; } 같은 끔찍한 구체성(Specificity) 억지 코드를 덕지덕지 발라대기 시작하고, 사이트의 CSS 파일은 1년 만에 손댈 수 없는 쓰레기통(Legacy)으로 전락합니다. 이 글로벌 스코프의 끔찍한 오염을 막고 컴포넌트 단위의 철저한 격리를 이루기 위해 전 세계 프론트엔드 석학들이 만들어낸 구조적 약속, 그것이 바로 'CSS 방법론(Methodologies)'입니다.

이번 글에서는 초창기 OOCSS의 뼈와 살 분리 철학부터, 현재 전 세계 실무 표준으로 군림하고 있는 러시아 Yandex 발명품 'BEM (Block, Element, Modifier)'의 다소 기괴하지만 압도적으로 안전한 네이밍 규칙에 대해 상세히 알아보겠습니다.

1. 뼈와 살을 분리하라: OOCSS (객체 지향 CSS)

가장 먼저 등장한 구조적 방법론은 OOCSS(Object Oriented CSS)입니다. 이 방법론의 핵심 철학은 두 가지입니다. 첫째, 구조(뼈대)와 스킨(살)을 분리할 것. 둘째, 컨테이너(위치)와 콘텐츠(내용)를 분리할 것.

기존에는 '빨간색 경고 버튼'을 만들 때 .red-alert-btn { width: 100px; padding: 10px; background: red; } 처럼 구조와 색상을 한 클래스에 몽땅 때려 박았습니다. OOCSS는 이를 혐오합니다. 대신 .btn { width: 100px; padding: 10px; }라는 형태(구조)를 담당하는 클래스와, .btn-red { background: red; }라는 스킨(색상)을 담당하는 클래스로 철저히 쪼갭니다. HTML에는 <button class="btn btn-red">처럼 두 개의 클래스를 조립해서 씁니다. 코드가 레고 블록처럼 재사용 가능해지며, 훗날 등장할 'Tailwind CSS' 같은 유틸리티 퍼스트 프레임워크의 사상적 밑거름이 된 위대한 철학입니다.

2. 글로벌 표준이 된 언어: BEM (Block, Element, Modifier)

하지만 OOCSS만으로는 "내가 방금 지은 .title이라는 클래스 이름이 다른 사람이 짠 .title과 충돌하면 어쩌지?"라는 근본적인 두려움을 없앨 수 없었습니다. 이때 러시아의 포털 사이트 얀덱스(Yandex) 개발팀이 들고나온 BEM은 CSS 클래스 네이밍에 '언더스코어(__)'와 '하이픈(--)'을 활용한 엄격한 문법을 부여하여 이 두려움을 완벽하게 박살 내버렸습니다.

① Block (블록: 덩어리 본체)

스스로 독립적으로 의미를 가지는 컴포넌트의 최상위 부모입니다. (예: header, menu, card, login-form). 블록의 이름은 짧고 명확하게 적습니다.

② Element (엘리먼트: 뱃속의 부품 __)

블록 안에 종속되어 있어서, 블록 밖으로 꺼내면 아무 의미가 없어지는 부속품들입니다. 블록 이름 뒤에 언더스코어 2개(__)를 붙여서 소속을 명확히 밝힙니다.

  • ❌ 나쁜 예: .card .title { ... } (후손 선택자 남용, 구체성 꼬임의 주범)
  • ✅ BEM 규칙: .card__title { ... }, .card__image { ... }

이제 이 제목 요소의 클래스명은 .title이 아니라 .card__title이라는 아주 길고 고유한 이름이 되었습니다. 덕분에 다른 개발자가 만든 .news__title이나 .banner__title과 절대! 네버! 이름표가 겹치지 않는 완벽한 캡슐화(격리)를 이루어냅니다.

③ Modifier (모디파이어: 상태/변형 --)

기본 블록이나 엘리먼트의 모양(크기, 색상 등)이나 상태(활성화, 비활성화 등)가 아주 살짝 변할 때 사용합니다. 이름 뒤에 하이픈 2개(--)를 붙입니다.

  • .card--dark (다크 테마가 적용된 카드 블록)
  • .card__btn--disabled (카드 뱃속에 있는 버튼 부품인데, 현재 비활성화된 상태)

3. BEM의 치명적 단점과 Sass(&)의 구원

BEM 방법론을 처음 본 디자이너나 주니어 개발자들의 반응은 한결같습니다. "클래스 이름이 .navigation-menu__item--active라니... 너무 길고 기괴하고 못생겼어요! 눈깔이 빠질 것 같아요!" 맞습니다. BEM의 유일한 단점은 코드가 징그럽게 길어지고 HTML 껍데기가 지저분해진다는 것입니다.

하지만 실무 프론트엔드 엔지니어들은 이 고통을 맨몸으로 겪지 않습니다. 다음 포스팅에서 배울 'Sass(SCSS)' 같은 CSS 전처리기를 활용하면, 이 길고 징그러운 이름들을 타이핑할 필요가 전혀 사라지기 때문입니다. SCSS의 부모 치환 연산자(&)를 사용하면 .card { &__title { ... } } 처럼 마치 폴더 트리 구조를 짜듯이 우아하고 아름답게 BEM 문법을 자동 찍어낼 수 있습니다. 이 막강한 시너지 때문에 'BEM + SCSS' 조합은 리액트(React)나 뷰(Vue) 환경에서 CSS Module이나 Styled-components가 대중화되기 전까지 거의 10년간 전 세계 웹 생태계를 평정했던 절대 군주로 군림했습니다.

정리하며

이번 글에서는 수만 줄의 CSS가 겹치고 폭발하는 대형 프로젝트의 유지보수 지옥을 막아내는 캡슐화의 미학, OOCSS 철학과 BEM 방법론의 __, -- 맹세에 대해 상세히 살펴보았습니다. BEM 네이밍을 숨 쉬듯 자연스럽게 구사할 수 있다면, 당신이 짠 CSS 코드는 5년 뒤 다른 동료가 유지보수하러 들어와도 절대 깨지지 않는 강철 같은 생명력을 가지게 됩니다. 자, 그런데 CSS가 아무리 발전해도 프로그래밍 언어처럼 변수를 쉽게 묶어 쓰고, 함수를 만들어 반복 계산을 처리하고, if 조건문을 거는 기능은 부족하기 짝이 없습니다. 브라우저가 이해하지 못하는 프로그래밍 로직을 CSS에 억지로 쑤셔 넣는 기적의 컴파일러! 다음


CSS 아키텍처 BEM 방법론 - 클래스 이름 짓기의 지옥에서 탈출하라

코딩을 처음 배울 때 가장 어려운 것은 알고리즘이 아니라 "변수 이름 짓기"라는 농담이 있습니다. 프론트엔드 세계에서 이 농담은 끔찍한 현실입니다. 웹사이트가 거대해질수록 .title, .box, .wrap 같은 단순한 CSS 클래스 이름들은 필연적으로 다른 파일 어딘가에 있는 똑같은 이름과 충돌하여 레이아웃을 폭파시킵니다. "분명 여기엔 폰트 크기를 안 줬는데 왜 커졌지?"하며 3시간 동안 버그를 찾다가 남이 짜놓은 공통 클래스 .title을 덮어써버린 참사를 발견하고 절망에 빠지게 됩니다. 이런 글로벌 충돌(Global Scope) 문제를 근본적으로 차단하고, 수만 줄의 CSS를 수십 명의 개발자가 협업하여 유지보수할 수 있게 만든 러시아 Yandex사의 혁명적인 네이밍(Naming) 규칙! 바로 BEM(Block, Element, Modifier) 방법론입니다. 이번 글에서는 촌스러워 보이지만 실무에서 절대 포기할 수 없는 BEM의 작명 철학과 규칙을 상세히 알아보겠습니다.

1. BEM의 3대 핵심 기둥: Block, Element, Modifier

BEM은 세상의 모든 UI 컴포넌트를 이 3가지 덩어리로 쪼개어 바라봅니다.

① Block (블록): 독립적인 생명체

재사용 가능하고 문맥에 종속되지 않는 독립적인 컴포넌트 덩어리입니다. (예: header, menu, search-form, card). 블록의 이름은 항상 명확한 명사로 짓습니다.

② Element (엘리먼트): 블록의 충실한 부속품 ( 언더스코어 2개 `__` )

블록을 구성하는 내부 부속품들입니다. 스스로는 자립할 수 없고 오직 블록 안에서만 의미를 가집니다. **블록 이름 뒤에 밑줄 두 개(`__`)**를 붙여서 연결합니다. (예: 카드의 썸네일은 card__image, 카드의 제목은 card__title)

③ Modifier (모디파이어): 블록/엘리먼트의 상태나 외형 ( 하이픈 2개 `--` )

블록이나 엘리먼트의 모양(색상, 크기)이 변하거나 상태(활성화, 에러)가 변했을 때 사용합니다. **기존 이름 뒤에 하이픈 두 개(`--`)**를 붙여서 의미를 더합니다. (예: 활성화된 탭은 tab--active, 에러가 난 입력창은 input__field--error)

2. 실전 코드로 보는 BEM의 위력

여러분에게 "회원 로그인 폼"을 만들라는 미션이 주어졌습니다. BEM이 없다면 어떻게 짰을지 상상해 보며 아래 코드를 보십시오.

<!-- 💡 BEM 방법론이 적용된 로그인 폼의 완벽한 뼈대 -->
<form class="login-form"> <!-- Block -->
    
    <!-- Element 1: 제목 -->
    <h2 class="login-form__title">로그인</h2> 
    
    <!-- Element 2: 아이디 입력창 -->
    <input class="login-form__input" type="text" placeholder="아이디" />
    
    <!-- Element 2-Modifier: 비밀번호 틀렸을 때 빨간 테두리 띄우기! -->
    <input class="login-form__input login-form__input--error" type="password" />
    
    <!-- Element 3: 제출 버튼 -->
    <button class="login-form__submit-btn">로그인</button>
</form>

클래스 이름이 너무 길고 흉측해 보입니까? 맞습니다. BEM의 유일한 단점이 바로 이름이 더럽게 길어진다는 것입니다. 하지만, 이 길고 흉측한 이름 덕분에 여러분의 코드는 "절대 다른 요소의 스타일을 침범하지 않는 100% 안전한 무균실(격리 캡슐)"이 됩니다. .login-form__title이라는 괴상한 클래스명은 전 우주에 오직 저곳 한 군데에만 존재하기 때문입니다!

3. BEM 작성 시 🚨절대 금지🚨 3가지 철칙

  • 태그 이름 쓰지 않기: .card h2 { ... } (X) -> .card__title { ... } (O). 태그에 직접 스타일을 주면 나중에 기획이 바뀌어 h2가 div로 변할 때 대형 사고가 터집니다.
  • 손자(Grandchild) 엘리먼트 금지: .card__body__title (X). BEM에서 계층은 무조건 한 단계(`Block__Element`)로만 씁니다. DOM 트리가 얼마나 깊든 .card__title처럼 블록 직속으로만 이름을 짓습니다.
  • id 선택자 금지: BEM의 철학은 재사용입니다. 무조건 하나만 존재해야 하는 #id는 BEM과 상극이므로 스타일링에 절대 사용하지 않습니다.

정리하며

"이름 짓기가 흉측하다"는 이유로 BEM을 거부하던 개발자들도, 실무에서 남이 짜놓은 스파게티 CSS 코드와 충돌을 한 번 겪고 나면 다음 프로젝트부터는 자진해서 BEM 신도(Cult)가 되어버립니다. 이 명확하고 단단한 아키텍처 규칙이 있기에 우리는 수만 줄의 CSS를 두려움 없이 컨트롤할 수 있습니다. 하지만, 매번 .login-form__input 같은 긴 이름을 복사 붙여넣기 하려니 손가락에 쥐가 날 것 같습니다. 이 긴 타이핑의 고통을 줄이고 프로그래밍의 강력한 함수(Mixin) 기능까지 더해줄 구원자가 우리를 기다리고 있습니다. 다음이번 포스팅에서는 현대 프론트엔드의 대명사이자 CSS 작성 속도를 3배로 끌어올리는 전처리 언어, "Sass(SCSS) 기초: 변수, 중첩, 믹스인(Mixin)"에 대해 상세히 파헤쳐 보겠습니다!


CSS 아키텍처의 황제 Sass/SCSS - CSS에 프로그래밍의 영혼을 불어넣다

우리는 지난이번 포스팅에 서 브라우저가 기본 지원하는 Native Nesting(중첩)의 발전으로 SCSS의 입지가 좁아지고 있다는 사실을 다루었습니다. 하지만 그럼에도 불구하고 전 세계의 내로라하는 대기업과 실무 프론트엔드 팀의 80% 이상은 여전히 Sass(Syntactically Awesome Style Sheets), 정확히는 그 파생 문법인 SCSS를 굳건하게 사용하고 있습니다. 왜 그럴까요? 순수 CSS는 태생적으로 디자인을 꾸미기 위한 '마크업 문서'일 뿐, 자바스크립트나 파이썬처럼 로직을 처리하는 '프로그래밍 언어'가 아니기 때문입니다. 수만 줄짜리 대규모 웹 어플리케이션을 만들다 보면 "이 색상 코드를 10%만 어둡게 계산해서 칠해줘", "버튼을 10개 만들어야 하는데, for 반복문을 돌려서 자동으로 1번부터 10번까지 코드를 찍어내줘" 같은 고도의 로직이 뼈저리게 필요해집니다. 이 프로그래머들의 타는 목마름을 해결하기 위해 등장한 구원자가 바로 'CSS 전처리기(Preprocessor)'인 SCSS입니다. SCSS는 변수, 함수, 조건문(if), 반복문(for), 믹스인(Mixin)이라는 프로그래밍 언어의 5대 무기를 CSS에 완벽하게 이식해 주었습니다. 이번 글에서는 브라우저가 알아먹지 못하는 SCSS 코드가 어떻게 순수 CSS로 번역(Compile)되는지 그 작동 원리와, 실무에서 코딩 생산성을 100배로 펌핑시켜 주는 궁극의 무기 @mixin의 활용법까지 상세히 알아보겠습니다.

1. 컴파일러의 마법: 브라우저는 SCSS를 모른다

가장 먼저 명심해야 할 절대 규칙이 있습니다. 구글 크롬이든 애플 사파리든 세상의 어떠한 웹 브라우저도 .scss 파일을 직접 읽고 해석할 수 없습니다. 브라우저의 엔진은 오직 순수하고 정적인 .css 파일만 파싱 할 수 있는 멍청한 기계입니다. 그렇다면 우리가 짠 화려한 SCSS 코드는 어떻게 화면에 그려질까요?

바로 중간에 '통역사(컴파일러)'가 존재하기 때문입니다. 개발자가 Webpack이나 Vite 같은 번들러 환경에서 SCSS 코드를 저장하는 순간, Node.js 기반의 통역사(Dart-sass)가 그 코드를 낚아챕니다. 통역사는 반복문을 풀어서 길게 늘여 쓰고, 색상을 계산하고, 괄호 중첩을 전부 풀어헤친 뒤 완벽하게 합법적인 순수 .css 파일로 변환(Compile)하여 브라우저에게 던져줍니다. 즉 SCSS는 브라우저를 위한 언어가 아니라, 오직 '개발자의 코딩 생산성'만을 위해 존재하는 가상의 프로그래밍 언어인 셈입니다.

2. CSS Variables(--) vs SCSS Variables($)

SCSS에서 변수를 선언할 때는 달러 기호($)를 사용합니다. $primary-color: #3498db; 처럼 말이죠.이번 포스팅에 서 배웠던 순수 CSS 변수(--primary-color)와 무슨 차이가 있을까요? 이는 '정적(Static)'과 '동적(Dynamic)'의 차이라는 어마어마한 렌더링 아키텍처의 분기점을 만들어냅니다.

  • CSS 변수 (--): 브라우저가 직접 메모리에 쥐고 있는 '동적 런타임 변수'입니다. 자바스크립트로 언제든지 값을 바꿔치기 할 수 있어서 실시간 다크 모드(테마 스위칭)에 100% 최적화되어 있습니다.
  • SCSS 변수 ($): 서버에서 통역사가 번역을 마치는 순간, 값이 전부 고정된 문자열(예: #3498db)로 치환되어 물리적으로 영원히 굳어버립니다(정적 컴파일). 자바스크립트로 바꿀 수 없으므로 테마 스위칭에는 부적합하지만, SCSS 내부에서 darken($primary, 10%)처럼 색상을 수학적으로 조작하는 프로그래밍 함수를 돌리기 위해서는 반드시 달러($) 변수를 써야만 합니다. (※ 최근 실무에서는 $변수를 뼈대로 잡고 그 값을 --변수에 주입하는 혼합 방식을 씁니다.)

3. 코딩 노가다의 종식자: @mixin 과 @include

SCSS를 쓰는 가장 강력하고 본질적인 이유 중 하나입니다. 자바스크립트에 '함수(Function)'가 있다면, SCSS에는 @mixin(믹스인)이 있습니다. 믹스인은 수십 줄짜리 CSS 코드 덩어리를 하나의 캡슐로 묶어놓고, 필요할 때마다 @include 명령어로 도장 찍듯이 찍어내는 극강의 코드 재사용 도구입니다.

/* 1. 캡슐 만들기: 플렉스 정중앙 정렬 코드를 'flex-center'라는 이름으로 묶어둔다 */
@mixin flex-center {
    display: flex;
    justify-content: center;
    align-items: center;
}

/* 2. 호출하기: 5줄 쳐야 할 코드를 단 1줄로 찍어낸다! */
.modal-box {
    width: 300px;
    @include flex-center; /* 컴파일러가 이 자리에 flex 코드를 풀어헤쳐 줍니다 */
}

진짜 마법: 파라미터(인자) 전달

믹스인은 심지어 괄호 ()를 통해 프로그래밍 함수처럼 변수 값을 밖에서 주입받을 수도 있습니다. 둥근 모서리의 픽셀 값을 디자인마다 다르게 넘겨줄 수 있다는 뜻입니다.

/* 반경(radius) 값을 외부에서 주입($radius)받는 유연한 믹스인! (기본값은 5px) */
@mixin shape-box($radius: 5px) {
    border-radius: $radius;
    box-shadow: 0 5px 10px rgba(0,0,0,0.1);
}

.profile-img {
    @include shape-box(50%); /* 동그랗게 50% 주입! */
}
.btn-normal {
    @include shape-box; /* 괄호를 비우면 기본값 5px 적용! */
}

4. 내장 함수: 어두운 그림자를 계산하라

버튼에 마우스를 호버 했을 때, 원래 배경색보다 '딱 10%만 어두워지게' 만들려면 어떻게 해야 할까요? 과거 디자이너들은 포토샵 스포이드로 어두운 색상 코드를 직접 뽑아내서 #1a6699처럼 CSS에 복붙 해야 했습니다. SCSS는 자체적인 색상 조작 내장 함수를 제공하여 이 노가다를 끝장냈습니다.

background-color: darken($primary-color, 10%); 단 한 줄이면, 컴파일러가 메인 컬러의 명도를 수학적으로 10% 깎아내린 새로운 헥스(Hex) 코드를 기가 막히게 산출하여 적용해 줍니다. lighten(), rgba() 변환 등 프론트엔드 테마 시스템을 구축하는 데 없어서는 안 될 심장 같은 기능입니다.

정리하며

이번 글에서는 죽어있는 정적 마크업 언어 CSS에 프로그래밍의 생명수와 수학 연산을 들이부어, 10만 줄의 코드를 1만 줄로 압축해 내는 SCSS의 @mixin과 컴파일 파이프라인의 원리를 상세히 살펴보았습니다. SCSS를 다룰 줄 안다는 것은 단순히 괄호를 중첩해서 치는 것을 넘어, '코드 재사용성을 위한 컴포넌트 아키텍처'를 설계할 수 있다는 것을 의미합니다. 자, 이렇게 화려하고 거대한 아키텍처를 짰는데, 막상 브라우저에 띄워보니 우측에 흉측한 '가로 스크롤바(Horizontal Scroll)'가 생기며 레이아웃이 박살 나버렸습니다. 대체 어떤 놈이 범인일까요? 콘솔(Console)에 에러조차 띄우지 않는 침묵의 살인마를 찾아내는 탐정술! 다음

반응형