본문 바로가기

CSS 아키텍처와 방법론

OOCSS·BEM·SMACSS의 분리와 이름 규칙을 비교하고 블로그 스타일을 예측 가능한 모듈 구조로 재편합니다.

지금까지 우리는 CSS의 기본 스타일링부터 Flexbox와 Grid 레이아웃, 트랜지션과 애니메이션, CSS 변수와 calc()를 통한 값 관리까지 학습했습니다.

개별 속성 활용도 중요하지만, 프로젝트 규모가 커지거나 협업 인원이 늘어나면 CSS 코드를 어떻게 구조화/관리할지가 더 중요해집니다.

아무런 규칙 없이 CSS를 작성하다 보면 다음과 같은 문제에 직면할 수 있습니다.

  • 재사용성 부족: 비슷한 스타일을 여러 번 반복해서 작성하게 됩니다.
  • 유지보수 어려움: 특정 스타일을 변경하면 예상치 못한 다른 곳에서 문제가 발생할 수 있습니다 (Side Effect).
  • 코드 충돌: 여러 개발자가 작업할 때 동일한 클래스 이름을 사용하여 스타일이 덮어쓰이는 문제가 발생합니다.
  • 가독성 저하: 코드가 복잡해지고 어떤 스타일이 어디에 적용되는지 파악하기 어려워집니다.

이러한 문제를 해결하고 확장 가능하며 유지보수하기 쉬운 CSS 코드를 작성하기 위해 등장한 것이 바로 CSS 아키텍처(Architecture)와 CSS 방법론(Methodology)입니다.

이 장에서는 CSS 아키텍처의 중요성을 이해하고, 대표적인 CSS 방법론인 BEM과 SMACSS, 그리고 CSS를 체계적으로 관리하는 OOCSS 원칙을 학습합니다.

CSS 방법론: 분리·명명·분류를 함께 사용한다

CSS ARCHITECTURE · RESPONSIBILITIES

CSS 방법론: 분리·명명·분류를 함께 사용한다

OOCSS는 재사용 책임을 나누고, BEM은 클래스 관계에 이름을 붙이며, SMACSS는 규칙의 목적을 분류한다. 세 방법은 서로 다른 설계 질문에 답한다.

세 방법론은 하나의 컴포넌트에 서로 다른 책임을 보탠다OOCSS 구조·스킨, BEM 관계 이름, SMACSS 목적·파일 분류의 세 노드가 같은 게시물 카드로 연결된다. 화살표는 기여 관계이며 OOCSS, BEM, SMACSS의 실행 순서가 아니다. 실제 CSS 우선순위와 상속을 별도로 확인하며 아래의 다섯 범주 표와 함께 읽는다.OOCSS구조 · 스킨 분리BEMpost-card · __contentSMACSSModule · State 분류같은 게시물 카드분리·이름·파일 책임을 함께 정함실제 cascade와 상속은 별도 확인
세 방법론은 하나의 컴포넌트에 서로 다른 책임을 보탠다OOCSS 구조·스킨, BEM 관계 이름, SMACSS 목적·파일 분류의 세 노드가 같은 게시물 카드로 연결된다. 화살표는 기여 관계이며 OOCSS, BEM, SMACSS의 실행 순서가 아니다. 실제 CSS 우선순위와 상속을 별도로 확인하며 아래의 다섯 범주 표와 함께 읽는다.OOCSS구조 · 스킨 분리BEMpost-card · __contentSMACSSModule · State 분류같은 게시물 카드분리·이름·파일 책임우선순위 자동격리 없음

OOCSS · 재사용 책임

구조와 스킨을 나누고 콘텐츠가 특정 컨테이너 선택자에 강하게 묶이지 않도록 한다. 예: button의 배치·간격과 색상 테마를 조합한다.

BEM · 관계를 이름으로

card는 Block, card__title은 Element, card--featured는 Modifier다. 이름으로 컴포넌트·부분·대안을 구분하며 modifier는 기본 클래스와 함께 쓴다.

SMACSS · 규칙의 범주

Base·Layout·Module·State·Theme로 규칙의 목적을 구분한다. BEM으로 이름 붙인 Module에도 상태와 테마 규칙을 조합할 수 있다.

SMACSS의 다섯 범주: 우선순위가 아니라 변경 목적
범주담당하는 변경예시
Base요소 기본값·resetbody · a · img
Layout페이지 주요 영역l-header · l-main-content
Module재사용 컴포넌트post-card · button
State현재 활성·오류 상태is-active · is-new
Theme시각 테마·스킨theme-dark · 색상 토큰

Base

요소 기본값·reset · body · a · img

Layout

페이지 주요 영역 · l-header · l-main-content

Module

재사용 컴포넌트 · post-card · button

State

현재 활성·오류 상태 · is-active · is-new

Theme

시각 테마·스킨 · theme-dark · 색상 토큰

방법론 이름만으로 CSS 우선순위가 정해지지는 않는다

BEM 클래스와 SMACSS 범주가 자동으로 cascade layer를 만들지는 않는다. 실제 선언의 cascade·상속·specificity를 확인하면서 팀의 이름 규칙과 파일 소유 범위를 일관되게 적용한다.


CSS 아키텍처의 필요성

CSS 아키텍처는 CSS 코드를 구조화하고 조직화하는 방법을 의미합니다.

마치 건물을 짓기 전에 설계도를 그리는 것과 같습니다.

이 장의 세 접근은 하나를 골라 나머지를 버리는 경쟁 규칙이 아닙니다. OOCSS는 무엇을 분리해 재사용할지, BEM은 어떤 컴포넌트의 어느 부분인지, SMACSS는 규칙을 어떤 변경 목적/파일로 관리할지 묻습니다. 버튼 하나에 구조·스킨을 나누고 BEM으로 이름을 붙인 뒤 Module/State 파일에 나눠 두는 조합도 가능합니다. 좋은 CSS 아키텍처는 다음과 같은 이점을 제공합니다.

  • 확장성(Scalability): 새로운 기능을 추가하거나 페이지를 확장할 때 기존 CSS 구조를 쉽게 확장할 수 있습니다.
  • 유지보수성(Maintainability): 코드 변경이 용이하고, 버그 발생 시 문제 해결이 빠릅니다.
  • 재사용성(Reusability): 이미 작성된 CSS 코드를 다른 컴포넌트나 페이지에서 효율적으로 재사용할 수 있습니다.
  • 협업(Collaboration): 여러 개발자가 일관된 규칙과 구조 아래에서 작업하여 코드 충돌을 줄이고 생산성을 높입니다.
  • 가독성(Readability): 코드의 흐름을 쉽게 파악할 수 있어 새로운 개발자가 빠르게 프로젝트에 적응할 수 있습니다.

OOCSS (Object-Oriented CSS)

OOCSS는 CSS를 객체 지향 프로그래밍(OOP)처럼 생각하여, 재사용 가능한 객체(Object) 단위로 CSS를 구성하는 원칙입니다.

주요 원칙은 두 가지입니다.

구조(Structure)와 스킨(Skin)의 분리
  • 구조: 레이아웃, 너비, 높이, 마진, 패딩 등 요소의 크기와 위치를 결정하는 속성.
  • 스킨: 색상, 폰트, 테두리, 그림자 등 시각적인 스타일 속성.
  • 이 두 가지를 분리하여 클래스를 만들고 조합함으로써 재사용성을 높입니다.
<button class="btn btn--large btn--primary">클릭</button>
<button class="btn btn--small btn--secondary">취소</button>
/* 구조 (재사용 가능한 기본 버튼 스타일) */
.btn {
    display: inline-block;
    padding: 10px 20px;
    border: none;
    cursor: pointer;
    text-align: center;
    border-radius: 5px;
}
.btn--large {
    padding: 15px 30px;
    font-size: 1.2em;
}
.btn--small {
    padding: 8px 15px;
    font-size: 0.9em;
}

/* 스킨 (색상 테마) */
.btn--primary {
    background-color: #3498db;
    color: white;
}
.btn--secondary {
    background-color: #2ecc71;
    color: white;
}
콘텐츠와 컨테이너의 분리
  • 특정 요소의 스타일이 특정 컨테이너에 종속되지 않도록 합니다.
  • 예를 들어, sidebar-menu li와 같이 컨테이너에 강하게 결합된 선택자 대신, menu-item과 같은 독립적인 클래스를 사용합니다.
/* 나쁜 예시 (컨테이너 종속적) */
.sidebar .menu-list li a {
    color: blue;
}

/* 좋은 예시 (재사용 가능한 컴포넌트) */
.menu-item {
    display: block;
    padding: 10px;
}
.menu-item-link {
    color: blue;
}

이를 통해 menu-item은 사이드바뿐만 아니라 헤더, 푸터 등 어떤 컨테이너에서도 동일하게 재사용될 수 있습니다.

OOCSS에서는 구조를 담당하는 클래스와 스킨을 담당하는 클래스를 분리해 버튼에 조합합니다.


BEM (Block, Element, Modifier)

BEM은 CSS 클래스 명명 규칙에 대한 가장 널리 사용되는 방법론 중 하나입니다.

HTML과 CSS 코드를 구조화하여 개발자가 CSS의 적용 범위와 의도를 쉽게 이해할 수 있도록 돕습니다.

BEM은 모든 것을 블록(Block), 요소(Element), 수정자(Modifier)의 세 가지 개념으로 나눕니다.

  • 블록 (Block): 독립적으로 재사용 가능한 UI 컴포넌트.

    • 예: header, menu, button, card
    • 클래스 이름: block-name
    • HTML: <div class="card">...</div>
  • 요소 (Element): 블록의 일부이며, 독립적으로 사용될 수 없는 블록의 하위 부분.

    • 예: menu-item, button-icon, card-title, card-image
    • 클래스 이름: block-name__element-name (두 개의 언더스코어 __)
    • HTML: <h2 class="card__title">...</h2>
  • 수정자 (Modifier): 블록 또는 요소의 모양, 상태, 동작을 변경하는 플래그.

    • 예: button--primary, menu--disabled, card--featured
    • 클래스 이름: block-name--modifier-name 또는 block-name__element-name--modifier-name (두 개의 하이픈 --)
    • HTML: <button class="button button--primary">...</button>
BEM 예시
<div class="card">
    <img class="card__image" src="image.jpg" alt="제품 이미지">
    <h3 class="card__title">제품명</h3>
    <p class="card__description">제품 설명입니다.</p>
    <button class="card__button card__button--primary">구매</button>
</div>

<div class="card card--featured"> <img class="card__image" src="image2.jpg" alt="특별 제품 이미지">
    <h3 class="card__title">특별 제품</h3>
    <p class="card__description">특별 할인 중!</p>
    <button class="card__button card__button--secondary">자세히 보기</button>
</div>
/* Block */
.card {
    border: 1px solid #ccc;
    padding: 15px;
    border-radius: 8px;
    background-color: white;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    width: 300px;
}

/* Element */
.card__image {
    max-width: 100%;
    height: auto;
    display: block;
    margin-bottom: 10px;
}
.card__title {
    font-size: 1.5em;
    color: #333;
    margin-bottom: 5px;
}
.card__description {
    font-size: 0.9em;
    color: #666;
    margin-bottom: 15px;
}
.card__button {
    padding: 8px 15px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 1em;
}

/* Modifier (Block level) */
.card--featured {
    border-color: #f1c40f;
    box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}

/* Modifier (Element level) */
.card__button--primary {
    background-color: #3498db;
    color: white;
}
.card__button--secondary {
    background-color: #2ecc71;
    color: white;
}
BEM의 장점
  • 명확성: 클래스 이름만으로 요소의 역할과 관계를 즉시 파악할 수 있습니다.
  • 이름으로 범위 드러내기: 블록별 이름을 구별해 우연한 충돌을 줄입니다. 일반 CSS 클래스는 여전히 문서 전체에서 매칭하므로 BEM 이름 자체가 브라우저의 격리 경계를 만들지는 않습니다.
  • 재사용성: 독립적인 블록과 요소는 쉽게 재사용할 수 있습니다.
  • 유지보수: 변경이 필요한 부분을 정확히 찾아낼 수 있습니다.

SMACSS: 모듈화된 CSS 아키텍처

SMACSS는 CSS 코드를 카테고리별로 나누어 정리하는 아키텍처 가이드라인입니다.

이는 CSS 파일의 구조를 체계화하고, 코드의 재사용성과 확장성을 높이는 데 기여합니다.

SMACSS는 CSS를 다음 5가지 주요 카테고리로 분류합니다.

Base (기본)
  • HTML 요소 자체에 적용되는 기본 스타일 (reset 또는 normalize CSS 포함).
  • html, body, h1, a, p, ul 등의 요소 선택자를 중심으로 자손·자식 선택자와 가상 클래스도 사용할 수 있습니다.
  • reset뿐 아니라 글꼴, 기본 색상, 링크 상태처럼 요소의 기본 모습도 정합니다. 아래 _base.css가 그 예입니다.
  • 예: a { color: blue; text-decoration: none; }
Layout (레이아웃)
  • 페이지의 주요 영역(헤더, 푸터, 사이드바, 메인 콘텐츠 등)을 정의하는 큰 틀의 레이아웃 스타일.
  • 주로 ID 선택자나 l- 또는 layout- 프리픽스를 가진 클래스 사용.
  • 예: #header { width: 100%; }, .l-sidebar { float: left; width: 200px; }
Module (모듈)
  • 재사용 가능한 독립적인 UI 컴포넌트(버튼, 카드, 폼 요소, 메뉴 등).
  • BEM의 블록과 유사합니다. 프리픽스 없음.
  • 예: .card { ... }, .button { ... }, .tabs { ... }
  • 가장 많은 CSS 코드가 이 카테고리에 속합니다.
State (상태)
  • 특정 모듈이나 레이아웃이 특정 상태에 있을 때의 스타일 (숨김, 활성화, 비활성화, 오류 등).
  • is- 프리픽스를 가진 클래스 사용. JavaScript로 토글되는 경우가 많습니다.
  • 예: .is-hidden { display: none; }, .is-active { font-weight: bold; }, .is-error { border-color: red; }
Theme (테마)
  • 사이트의 시각적인 테마나 색상 스킨을 정의하는 스타일. (OOCSS의 스킨과 유사)
  • theme- 프리픽스를 가진 클래스 사용.
  • 예: .theme-dark { background-color: #333; color: white; }, .theme-blue button { background-color: blue; }
SMACSS 파일 구조 예시
_reset.css
_typography.css
_base.css
_grid.css
_header-footer.css
_button.css
_card.css
_form.css
_navigation.css
_states.css
_dark-theme.css
style.css (모든 파일을 import하는 메인 파일)
SMACSS의 장점
  • 명확한 구조: CSS 코드의 목적과 범위를 명확히 구분하여 이해하기 쉽습니다.
  • 재사용성: 모듈을 중심으로 스타일을 정의하여 재사용성을 높입니다.
  • 확장성: 새로운 기능을 추가할 때 어떤 카테고리에 CSS를 추가해야 할지 명확합니다.
  • 유지보수: 스타일 간의 충돌을 줄이고, 문제 발생 시 디버깅을 용이하게 합니다.

기타 CSS 방법론/접근 방식

  • ITCSS (Inverted Triangle CSS): SMACSS와 유사하게 CSS를 계층별로 정리하지만, 더 엄격한 순서(General to Specific)를 따릅니다. CSS의 계단식 특성(Cascade)을 효율적으로 활용하는 데 중점을 둡니다.
  • Atomic CSS (Functional CSS): .text-center, .margin-top-10, .flex와 같이 단일 CSS 속성-값 쌍을 나타내는 작은 유틸리티 클래스를 HTML에 직접 적용하는 방식입니다. Tailwind CSS와 같은 프레임워크에서 많이 사용됩니다. 빠른 개발 속도와 일관된 디자인을 얻을 수 있지만, HTML이 길어지고 클래스 이름이 많아질 수 있습니다.
  • CSS-in-JS: JavaScript 코드 내에서 CSS를 작성하는 방식입니다. React와 같은 JavaScript 프레임워크와 함께 사용되며, 컴포넌트 기반 개발에서 CSS의 캡슐화를 극대화하고 동적인 스타일링에 강점을 가집니다. (이 책의 범위 밖이므로 개념만 소개)

실습: BEM과 SMACSS를 적용한 블로그

배운 CSS 아키텍처 및 방법론을 활용하여 간단한 블로그 게시물 카드 컴포넌트를 만들어 봅시다.

프로젝트 폴더 구조
css_architecture.html
_base.css
_layout.css
_module.css
_state.css
style.css (이 파일에서 모든 CSS를 import)
css_architecture.html 파일 작성
css_architecture.html
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CSS 아키텍처 실습</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header class="l-header">
        <h1 class="l-header__title">블로그</h1>
        <nav class="main-nav">
            <ul class="main-nav__list">
                <li class="main-nav__item"><a href="#" class="main-nav__link is-active">홈</a></li>
                <li class="main-nav__item"><a href="#" class="main-nav__link">카테고리</a></li>
                <li class="main-nav__item"><a href="#" class="main-nav__link">문의</a></li>
            </ul>
        </nav>
    </header>

    <main class="l-main-content">
        <section class="l-section">
            <h2 class="l-section__title">최신 게시물</h2>
            <div class="post-grid">
                <article class="post-card">

                    <div class="post-card__content">
                        <h3 class="post-card__title">CSS 아키텍처의 중요성</h3>
                        <p class="post-card__excerpt">대규모 프로젝트에서 CSS를 효과적으로 관리하는 방법론을 알아봅니다.</p>
                        <a href="#" class="post-card__read-more button button--primary">더 읽기</a>
                    </div>
                </article>

                <article class="post-card post-card--featured is-new">
                    <div class="post-card__content">
                        <h3 class="post-card__title">Flexbox와 Grid 비교 분석</h3>
                        <p class="post-card__excerpt">언제 Flexbox를 쓰고, 언제 Grid를 써야 할까?</p>
                        <a href="#" class="post-card__read-more button button--secondary">더 읽기</a>
                    </div>
                </article>

                <article class="post-card">

                    <div class="post-card__content">
                        <h3 class="post-card__title">자바스크립트 비동기 프로그래밍</h3>
                        <p class="post-card__excerpt">콜백, Promise, Async/Await 완벽 이해하기.</p>
                        <a href="#" class="post-card__read-more button button--primary">더 읽기</a>
                    </div>
                </article>
            </div>
        </section>
    </main>

    <footer class="l-footer">
        <p class="l-footer__text">&copy; 2025 CSS 아키텍처 실습. 모든 권리 보유.</p>
    </footer>
</body>
</html>
css/style.css 파일 작성 (모든 CSS 파일 임포트)
css/style.css
/* style.css */

@import url('_base.css');
@import url('_layout.css');
@import url('_module.css');
@import url('_state.css');

/* CSS 변수는 :root에 정의하는 것이 일반적이며, 이곳이나 _base.css에 포함 가능 */
:root {
    --primary-color: #3498db;
    --secondary-color: #2ecc71;
    --accent-color: #e67e22;
    --text-color: #333;
    --bg-color: #f0f2f5;
    --card-bg: white;
    --border-color: #ddd;
    --shadow-color: rgba(0,0,0,0.1);
    --box-shadow-light: 0 2px 10px rgba(0,0,0,0.1);
    --gap-size: 20px;
}
css/_base.css 파일 작성 (Base)
_base.css
/* _base.css */

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: var(--bg-color);
    color: var(--text-color);
    line-height: 1.6;
    padding-bottom: 50px;
}

a {
    color: var(--primary-color);
    text-decoration: none;
    transition: color 0.2s ease;
}
a:hover {
    color: #2980b9;
}

ul {
    list-style: none;
}

img {
    max-width: 100%;
    height: auto;
    display: block;
}
css/_layout.css 파일 작성 (Layout)
_layout.css
/* _layout.css */

.l-header {
    background-color: var(--primary-color);
    color: white;
    text-align: center;
    padding: calc(var(--gap-size) * 1.5) var(--gap-size);
    box-shadow: 0 3px 6px var(--shadow-color);
    margin-bottom: var(--gap-size);
}
.l-header__title {
    font-size: 2.8em;
    margin-bottom: calc(var(--gap-size) / 2);
}

.l-main-content {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 var(--gap-size);
}

.l-section {
    background-color: var(--card-bg);
    padding: calc(var(--gap-size) * 1.5);
    margin-bottom: var(--gap-size);
    border-radius: 8px;
    box-shadow: 0 2px 10px var(--shadow-color);
}
.l-section__title {
    color: var(--primary-color);
    font-size: 2em;
    margin-bottom: calc(var(--gap-size) * 1.5);
    text-align: center;
}

.post-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: var(--gap-size);
}

.l-footer {
    background-color: var(--primary-color);
    color: white;
    text-align: center;
    padding: var(--gap-size);
    margin-top: 50px;
    font-size: 0.9em;
}
css/_module.css 파일 작성 (Module - BEM 적용)
_module.css
/* _module.css */

/* Button Module */
.button {
    display: inline-block;
    padding: 10px 15px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 0.9em;
    font-weight: bold;
    transition: background-color 0.2s ease, transform 0.2s ease;
}
.button--primary {
    background-color: var(--primary-color);
    color: white;
}
.button--secondary {
    background-color: var(--secondary-color);
    color: white;
}
.button:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.button--primary:hover { background-color: #2980b9; }
.button--secondary:hover { background-color: #27ae60; }

/* Main Navigation Module */
.main-nav {
    background-color: #34495e;
    padding: 10px 0;
    text-align: center;
}
.main-nav__list {
    display: flex;
    justify-content: center;
    gap: 20px;
}
.main-nav__item {}
.main-nav__link {
    color: white;
    padding: 8px 15px;
    border-radius: 5px;
    transition: background-color 0.2s ease;
}
.main-nav__link:hover {
    background-color: rgba(255, 255, 255, 0.2);
}

/* Post Card Module */
.post-card {
    background-color: var(--card-bg);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    overflow: hidden; /* 이미지 밖으로 나가지 않도록 */
    box-shadow: var(--box-shadow-light);
    transition: transform 0.2s ease-out;
    display: flex;
    flex-direction: column;
}
.post-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 5px 15px rgba(0,0,0,0.15);
}
.post-card__image {
    width: 100%; /* 이미지가 부모 너비를 꽉 채우도록 */
    height: 200px;
    object-fit: cover; /* 이미지 비율 유지 */
}
.post-card__content {
    padding: var(--gap-size);
    display: flex;
    flex-direction: column;
    flex-grow: 1; /* 콘텐츠 영역이 남은 공간 차지 */
}
.post-card__title {
    font-size: 1.3em;
    color: var(--primary-color);
    margin-bottom: calc(var(--gap-size) / 2);
}
.post-card__excerpt {
    font-size: 0.95em;
    color: var(--text-color);
    margin-bottom: var(--gap-size);
    flex-grow: 1; /* 발췌 부분이 남은 공간 차지 */
}
.post-card__read-more {
    align-self: flex-start; /* 버튼만 왼쪽 정렬 */
    margin-top: auto; /* 항상 하단에 붙도록 */
}

/* Post Card Modifier */
.post-card--featured {
    border-color: var(--accent-color);
    box-shadow: 0 5px 20px rgba(230, 126, 34, 0.3);
}
css/_state.css 파일 작성 (State)
_state.css
/* _state.css */

.is-active {
    background-color: rgba(255, 255, 255, 0.3); /* 활성 상태 */
    font-weight: bold;
}

.is-new::after {
    content: "NEW!";
    background-color: #e74c3c;
    color: white;
    font-size: 0.7em;
    padding: 3px 8px;
    border-radius: 3px;
    margin-left: 10px;
    vertical-align: super; /* 텍스트 상단 정렬 */
}
결과 확인
  • Live Server를 통해 css_architecture.html 파일을 열어 보세요.
  • 페이지의 헤더, 내비게이션, 게시물 카드들이 SMACSS와 BEM 규칙에 따라 구조화된 CSS로 스타일링된 것을 확인합니다.
  • 개발자 도구(F12)를 열어 HTML 요소들의 클래스 이름을 확인하고, 해당 클래스들이 어떤 CSS 파일(_base.css, _layout.css, _module.css, _state.css)에서 왔는지 확인해 보세요.
  • .post-card--featured 클래스와 .is-new 클래스가 어떻게 함께 사용되어 특별한 스타일과 NEW! 텍스트를 추가하는지 관찰해 보세요.

이번 장에서는 CSS 코드를 대규모 프로젝트에서 효율적으로 관리하고 확장하며 유지보수하기 위한 중요한 개념인 CSS 아키텍처와 CSS 방법론에 대해 학습했습니다.

  • OOCSS(Object-Oriented CSS) 원칙을 통해 구조와 스킨을 분리하고, 콘텐츠와 컨테이너를 분리함으로써 CSS 재사용성과 유연성을 높이는 방법을 이해했습니다.
  • 가장 널리 사용되는 명명 규칙인 BEM(Block, Element, Modifier)을 통해 클래스 이름만으로도 요소의 역할과 관계를 명확히 파악하고, CSS 충돌을 방지하며 캡슐화를 강화하는 방법을 배웠습니다.
  • SMACSS(Scalable and Modular Architecture for CSS)를 통해 CSS 코드를 Base, Layout, Module, State, Theme의 5가지 카테고리로 분류하여 체계적인 파일 구조를 만들고, 코드의 목적과 범위를 명확히 하는 방법을 익혔습니다.

이러한 CSS 아키텍처와 방법론은 단순히 CSS 속성을 아는 것을 넘어, 복잡한 웹 프로젝트를 안정적으로 운영하는 데 필요한 역량입니다.

파일을 따라 규칙 찾기

HTML은 css/style.css를 불러오고, 그 CSS의 _module.css import는 같은 css 폴더를 기준으로 해석됩니다. 언더스코어는 여기서는 파일 이름 규칙일 뿐 Sass를 사용하거나 자동으로 합치는 기능이 아닙니다. 모든 CSS import는 일반 스타일 규칙보다 앞에 둡니다. _base.css가 먼저 로드되어도 그 안에서 쓰는 :root 변수는 최종 cascade를 통해 참조할 수 있으므로 토큰을 JavaScript처럼 한 줄씩 실행한다고 생각하지 마세요.

두 번째 article은 .post-card의 기본 모듈에 .post-card--featured의 고정 대안과 .is-new의 현재 상태를 조합합니다. HTML에 사진은 없어서 .post-card__image 규칙만으로 그림이 생기지 않습니다. .is-new::after는 content 뒤의 가상 요소이며, 이 article은 column flex 컨테이너이므로 단순히 제목 옆의 위첨자 위치가 되는 것은 아닙니다. nav의 active 클래스도 클릭 시 바뀌는 코드는 아직 없고 href="#"는 이 실습의 자리 표시자입니다.

모바일과 다른 컨테이너에서도 확인하기

320px에서는 main padding 40px과 section padding 60px을 제외한 Grid 공간이 220px입니다. 최소 300px 트랙은 이 공간을 넘으므로 _layout.css 끝에 .post-grid { grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); }를 추가해 좁은 공간에 맞춥니다. _module.css 끝에는 .main-nav__list { flex-wrap: wrap; }를 추가해 메뉴도 줄바꿈을 허용하세요. 먼저 원래 화면을 확인하고 이 두 변형 전후의 가로 넘침을 비교합니다.

혼자 책임을 나누어 보기

‘모든 게시물 카드의 안쪽 간격을 24px로 바꾸기’, ‘특집 카드만 테두리색 바꾸기’, ‘두 번째 카드의 NEW 표시를 없애기’를 각각 어느 파일과 선택자/클래스에서 바꿀지 먼저 정하세요. 그다음 두 번째 카드를 같은 main 안의 다른 section으로 옮겨도 카드 기본 스타일이 유지되는 이유를 설명해 보세요.

변경 책임 풀이

공통 안쪽 간격은 _module.css의 .post-card__content입니다. 이것만 24px로 바꾸면 다른 역할의 간격 토큰 전체를 건드리지 않습니다. 특집 테두리는 같은 파일의 .post-card--featured가 담당합니다. NEW가 끝난 카드에서는 .is-new를 HTML에서 제거하고 공통 상태 규칙은 남깁니다. 기본 카드 선택자는 .l-section .post-card처럼 특정 조상에 묶이지 않았으므로 다른 section에도 매칭합니다. 옮긴 곳의 폭·상속·다른 override까지 항상 같다는 뜻은 아닙니다.

잘 구조화된 CSS는 변경할 위치와 영향을 예측하기 쉽게 만듭니다. 이름 규칙만으로 정확성이나 접근성이 보장되지는 않으므로 실제 cascade와 화면 동작을 함께 확인합니다.