CSS設計~BEMを学ぼう~

この記事では、Webサイト制作でとても重要なCSS設計手法の一つ「BEM」について、高校生のOZくんとベテランWeb開発者のプロ太先生との会話形式で詳しく解説していきます。

OZ
OZ

プロ太先生、最近Webサイトのコーディングをはじめたんですけど、CSSの書き方がバラバラになっちゃって困ってるんです…

プロ太
プロ太

なるほど。具体的にどんな問題が起きてるんですか?

はい…例えば、ボタンのスタイルを書くときに、あるページでは.btn-primaryって書いて、別のページでは.primary-buttonって書いたり。チーム開発だと、他のメンバーは.button-mainって書いたり…スタイルの管理が大変になってきちゃいました。

なるほど。よくある悩みですね。そんなときに役立つのが「BEM」という設計手法です。じゃあ、今日は、「BEM」について詳しく説明していきますね。

BEMって何?

BEMってなんですか?なんだか難しそう…

BEMは「Block」「Element」「Modifier」の頭文字をとった命名規則なんです。簡単に言うと、HTMLの要素にクラス名をつけるときのルールブックみたいなものですね。

ルールブック?具体的にはどんなルールなんですか?

例えば、カードのデザインを作るときを考えてみましょう。

<div class="card">
    <h2 class="card__title">見出し</h2>
    <p class="card__text">本文</p>
    <button class="card__button card__button--primary">詳しく見る</button>
</div>

この例で解説すると、

  • card → Block(独立した意味を持つ要素)
  • card__title → Element(Blockの一部)
  • card__button--primary → Modifier(見た目や状態の違い)

おー!なんとなく分かってきました。
__--を使って関係性を表すんですね!

実践的な例で理解を深めよう

では、もう少し実践的な例を見てみましょう!ナビゲーションメニューを作る場合を考えてみますね。

<nav class="nav">
    <ul class="nav__list">
        <li class="nav__item">
            <a href="#" class="nav__link nav__link--active">ホーム</a>
        </li>
        <li class="nav__item">
            <a href="#" class="nav__link">about</a>
        </li>
        <li class="nav__item">
            <a href="#" class="nav__link nav__link--disabled">お問い合わせ</a>
        </li>
    </ul>
</nav>
.nav {
    background: #f8f9fa;
    padding: 1rem;
}

.nav__list {
    display: flex;
    list-style: none;
    margin: 0;
    padding: 0;
}

.nav__item {
    margin: 0 1rem;
}

.nav__link {
    color: #333;
    text-decoration: none;
}

.nav__link--active {
    color: #007bff;
    font-weight: bold;
}

.nav__link--disabled {
    color: #6c757d;
    pointer-events: none;
}

なるほど!これなら他の開発者が見ても、どの要素がどんな役割を持っているのか分かりやすそうですね。

その通り!BEMの大きな利点は:

  1. クラス名を見るだけで要素の関係性が分かる
  2. CSSの詳細度が統一される
  3. 拡張性が高い
  4. チーム開発でも統一的なコーディングができる

という点なんです。

よくある間違いと気をつけるポイント

ただ、このBEM記法には、気をつけないといけない点があるので、それをこれから説明していきますね。

BEM注意点1:ネストが深すぎる

❌ 悪い例:

<div class="card">
    <div class="card__body">
        <div class="card__body__content">
            <p class="card__body__content__text">...</p>
        </div>
    </div>
</div>

⭕ 良い例:

<div class="card">
    <div class="card__body">
        <div class="card__content">
            <p class="card__text">...</p>
        </div>
    </div>
</div>

なるほど!__は一回だけにした方が良いんですね。

そうです!要素の関係性を示すのは大事だけど、あまり複雑にしすぎると逆に分かりづらくなっちゃうんです。

2.Block名の付け方

Block名を付けるときに気をつけることはありますか?

Block名は、その要素が何なのかを表す名詞を使うのがおすすめです。

❌ 悪い例:

.red-box { ... }
.left-area { ... }

⭕ 良い例:

.alert { ... }
.sidebar { ... }

見た目や位置ではなく、役割で名前をつけるんですね!

実践的な演習:フォームを作ってみよう

じゃあ、実際にフォームをBEMを使って作ってみましょう。

<form class="form">
    <div class="form__group">
        <label class="form__label">お名前</label>
        <input type="text" class="form__input">
        <span class="form__error">名前を入力してください</span>
    </div>
    
    <div class="form__group">
        <label class="form__label">メールアドレス</label>
        <input type="email" class="form__input form__input--error">
        <span class="form__error">正しいメールアドレスを入力してください</span>
    </div>
    
    <div class="form__actions">
        <button class="form__button form__button--primary">送信</button>
        <button class="form__button form__button--secondary">キャンセル</button>
    </div>
</form>
.form {
    max-width: 600px;
    margin: 0 auto;
    padding: 20px;
}

.form__group {
    margin-bottom: 1rem;
}

.form__label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: bold;
}

.form__input {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #ddd;
    border-radius: 4px;
}

.form__input--error {
    border-color: #dc3545;
}

.form__error {
    color: #dc3545;
    font-size: 0.875rem;
    margin-top: 0.25rem;
}

.form__actions {
    display: flex;
    gap: 1rem;
    margin-top: 2rem;
}

.form__button {
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.form__button--primary {
    background-color: #007bff;
    color: white;
}

.form__button--secondary {
    background-color: #6c757d;
    color: white;
}

おぉ!すごくきれいなコードになりましたね!これなら修正も簡単そうです!ありがとうございました!!!BEMを使って、頑張ってきます!!!

大規模なプロジェクトでのBEMの活用

数日後、、、、

プロ太先生!!最近、大きなECサイトの制作を任されたんです。たくさんのページやコンポーネントがあって、BEMの管理が大変そうなんです…

なるほど!大規模プロジェクトならではの工夫がいくつかありますよ。紹介していきますね!!

1.命名空間(Namespace)の活用

大規模プロジェクトでは、コンポーネントの役割をより明確にするために、命名空間を使うことがあります。

/* コンポーネント */
.c-button { ... }
.c-card { ... }

/* ユーティリティ */
.u-text-center { ... }
.u-margin-top { ... }

/* レイアウト */
.l-header { ... }
.l-sidebar { ... }

/* JavaScript用フック */
.js-modal-trigger { ... }
.js-accordion { ... }

おぉ!!プレフィックスをつけるんですね!

そうです!よく使われるプレフィックスには、

  • c- : Components(再利用可能なコンポーネント)
  • l- : Layout(レイアウト要素)
  • u- : Utilities(ユーティリティクラス)
  • js- : JavaScript(JavaScriptのフック)
  • p- : Project(プロジェクト固有のスタイル)

がありますよ!!

2.マルチクラスパターンの活用

でも、似たようなスタイルを持つボタンがたくさんあるときは、毎回違うクラス名を付けるんですか?

いい質問ですね!そんなときは、マルチクラスパターンを使うと効率的ですよ!!

<!-- 基本のボタンスタイル + バリエーション -->
<button class="c-button c-button--large c-button--primary">送信</button>
<button class="c-button c-button--small c-button--secondary">キャンセル</button>

<!-- 基本のカードスタイル + バリエーション -->
<div class="c-card c-card--shadow c-card--hover">
    <div class="c-card__body">...</div>
</div>

3.BEMの実践的な命名例集

それでは、実際のプロジェクトでよく使う命名例をたくさん見ておきましょう!

ヘッダーナビゲーション

<header class="p-header">
    <div class="p-header__logo">
        <img class="p-header__logo-image" src="logo.png" alt="サイトロゴ">
    </div>
    <nav class="p-header__nav">
        <ul class="p-header__nav-list">
            <li class="p-header__nav-item">
                <a class="p-header__nav-link p-header__nav-link--active" href="#">ホーム</a>
            </li>
        </ul>
    </nav>
    <div class="p-header__user">
        <button class="p-header__user-button p-header__user-button--cart">
            <span class="p-header__user-count">3</span>
        </button>
    </div>
</header>

商品カード

<div class="c-product-card">
    <div class="c-product-card__image-wrapper">
        <img class="c-product-card__image" src="product.jpg" alt="商品名">
        <span class="c-product-card__badge c-product-card__badge--sale">SALE</span>
    </div>
    <div class="c-product-card__content">
        <h3 class="c-product-card__title">商品名</h3>
        <p class="c-product-card__price">
            <span class="c-product-card__price-original">¥5,000</span>
            <span class="c-product-card__price-current c-product-card__price-current--discount">¥3,980</span>
        </p>
        <div class="c-product-card__rating">
            <div class="c-product-card__stars c-product-card__stars--four"></div>
            <span class="c-product-card__review-count">(42件)</span>
        </div>
    </div>
</div>

モーダルウィンドウ

<div class="c-modal">
    <div class="c-modal__overlay js-modal-close"></div>
    <div class="c-modal__container">
        <header class="c-modal__header">
            <h2 class="c-modal__title">確認</h2>
            <button class="c-modal__close-btn js-modal-close">×</button>
        </header>
        <div class="c-modal__content">
            <p class="c-modal__text">内容を保存しますか?</p>
        </div>
        <footer class="c-modal__footer">
            <button class="c-modal__button c-modal__button--cancel js-modal-close">キャンセル</button>
            <button class="c-modal__button c-modal__button--submit">保存する</button>
        </footer>
    </div>
</div>

記事一覧

<div class="p-article-list">
    <article class="p-article-list__item">
        <div class="p-article-list__image-wrapper">
            <img class="p-article-list__image" src="article1.jpg" alt="記事サムネイル">
            <span class="p-article-list__category p-article-list__category--tech">テクノロジー</span>
        </div>
        <div class="p-article-list__content">
            <time class="p-article-list__date" datetime="2024-02-15">2024.02.15</time>
            <h3 class="p-article-list__title">記事タイトル</h3>
            <p class="p-article-list__excerpt">記事の抜粋テキストがここに入ります...</p>
            <div class="p-article-list__meta">
                <span class="p-article-list__author">著者名</span>
                <span class="p-article-list__read-time">読了時間: 5分</span>
            </div>
        </div>
    </article>
</div>

4.ファイル構成の工夫

大規模プロジェクトでは、ファイル構成も重要になってきます。

styles/
├── base/
│   ├── _reset.scss
│   └── _typography.scss
├── components/
│   ├── _button.scss
│   ├── _card.scss
│   └── _modal.scss
├── layout/
│   ├── _header.scss
│   ├── _footer.scss
│   └── _sidebar.scss
├── utilities/
│   ├── _spacing.scss
│   └── _text-align.scss
└── main.scss

ファイルを分けることで、管理が楽になりそうですね。

そうですね!特に以下の点に気をつけると良いですよ。

  • コンポーネント単位でファイルを分ける
  • 共通の変数やミックスインは別ファイルにまとめる
  • BEMの構造に合わせてディレクトリを構成する

5.命名のベストプラクティス

最後に、命名でよく使われるパターンをまとめておこう!

状態を表すModifier

/* 表示・非表示 */
.c-element--hidden
.c-element--visible

/* 有効・無効 */
.c-button--disabled
.c-button--enabled

/* サイズバリエーション */
.c-button--small
.c-button--medium
.c-button--large

/* 重要度 */
.c-alert--success
.c-alert--warning
.c-alert--error

/* レイアウト */
.c-grid--centered
.c-grid--spaced
.l-container--full-width

よく使うElement名

/* コンテナ系 */
.block__container
.block__wrapper
.block__group

/* テキスト系 */
.block__title
.block__subtitle
.block__text
.block__description

/* 画像系 */
.block__image
.block__icon
.block__thumbnail

/* アクション系 */
.block__button
.block__link
.block__trigger

すごく参考になりました!これだけあれば、だいぶ命名に困らなくなりそうです!

そうだね!でも覚えておいてほしいのは、これらは例であって、プロジェクトの文脈に合わせて適切な名前を考えることが大切だということです。

命名空間(Namespace)の使い分け方

プロ太先生!さっきの例で、ヘッダーにp-headerって書いてありましたけど、レイアウトだからl-headerの方が良いんじゃないですか?

いい質問ですね!確かにここがとても迷うポイントです。各プレフィックスの使い分けについて、具体例を交えて詳しく説明していきますね。

1.Component(c-):再利用可能なパーツ

汎用的に使えるUIパーツにはc-をつけます。

使う場面

<!-- どのプロジェクトでも使い回せるボタン -->
<button class="c-button c-button--primary">送信</button>

<!-- 汎用的なカード UI -->
<div class="c-card">
    <h3 class="c-card__title">タイトル</h3>
    <p class="c-card__text">テキスト</p>
</div>

<!-- どのサイトでも使えそうなアラート -->
<div class="c-alert c-alert--success">
    保存しました
</div>

💡 ポイント

  • プロジェクトに依存しない
  • 別のサイトでも使い回せる
  • シンプルで汎用的な見た目

2.Project(p-):プロジェクト固有のパーツ

そのプロジェクトならではの見た目や機能を持つパーツにはp-をつけます。

使う場面

<!-- このプロジェクト特有のヘッダーデザイン -->
<header class="p-header">
    <div class="p-header__logo">
        <!-- プロジェクト特有のロゴレイアウト -->
    </div>
    <nav class="p-header__nav">
        <!-- このサイト独自のナビゲーション構造 -->
    </nav>
</header>

<!-- このECサイト特有の商品カード -->
<div class="p-product-card">
    <div class="p-product-card__badge">SALE</div>
    <div class="p-product-card__price-wrap">
        <!-- この ECサイト独自の価格表示 -->
    </div>
</div>

💡 ポイント

  • このプロジェクトでしか使わない
  • サイト独自のデザインが入っている
  • 他のプロジェクトではそのまま使えない

3.Layout(l-):レイアウトのみを担当

ページの大枠のレイアウトを決めるものにl-をつけます。

使う場面

<!-- 全体のコンテナ -->
<div class="l-container">
    <!-- 2カラムレイアウト -->
    <div class="l-content">
        メインコンテンツ
    </div>
    <div class="l-sidebar">
        サイドバー
    </div>
</div>

<!-- グリッドレイアウト -->
<div class="l-grid">
    <div class="l-grid__item">...</div>
    <div class="l-grid__item">...</div>
</div>

💡 ポイント

  • 位置や余白のみを担当
  • 見た目の装飾は持たない
  • 主にグリッドやカラム構造に使用

4.Utility(u-):調整用のヘルパークラス

微調整やユーティリティとして使うスタイルにu-をつけます。

使う場面

<!-- テキストの位置調整 -->
<p class="u-text-center">中央揃え</p>
<p class="u-text-right">右揃え</p>

<!-- マージンの調整 -->
<div class="u-mt-2">上に余白</div>
<div class="u-mb-4">下に余白</div>

<!-- 表示制御 -->
<div class="u-hidden-sp">SPでは非表示</div>

💡 ポイント

  • 単一の目的
  • 装飾や構造を持たない
  • 上書きとして使用

5.JavaScript(js-):JavaScriptのフック

JavaScriptで操作する要素にjs-をつけます。

使う場面

<!-- モーダル関連 -->
<button class="c-button js-modal-trigger">開く</button>
<div class="c-modal js-modal">
    <button class="c-modal__close js-modal-close">閉じる</button>
</div>

<!-- タブパネル -->
<div class="p-tab-panel">
    <button class="p-tab-panel__tab js-tab-trigger">タブ1</button>
    <div class="p-tab-panel__content js-tab-content">内容1</div>
</div>

💡 ポイント

  • スタイルは持たない
  • JavaScriptの参照用のみ
  • 他のクラスと併用する

実践例:プレフィックスの組み合わせ

なるほど!でも、実際のコードでは組み合わせて使うこともありますよね?

その通り!実践的な例を見てみましょう。

<!-- ECサイトの商品一覧ページの例 -->
<div class="l-container">
    <!-- 2カラムレイアウト -->
    <main class="l-content">
        <!-- プロジェクト特有の商品リスト -->
        <div class="p-product-list">
            <!-- 汎用的なローディング表示 -->
            <div class="c-loading js-loading">
                読み込み中...
            </div>
            
            <!-- 商品カード -->
            <div class="p-product-card">
                <!-- 汎用的な画像コンポーネント -->
                <div class="c-image-wrapper">
                    <img class="c-image" src="product.jpg" alt="">
                </div>
                <!-- プロジェクト特有の商品情報 -->
                <div class="p-product-card__info">
                    <!-- 位置調整用 -->
                    <h3 class="p-product-card__title u-mt-2">商品名</h3>
                </div>
            </div>
        </div>
    </main>
    
    <aside class="l-sidebar">
        <!-- プロジェクト特有のフィルター -->
        <div class="p-product-filter js-filter">
            <!-- 汎用的なチェックボックス -->
            <label class="c-checkbox">
                <input type="checkbox" class="c-checkbox__input">
                <span class="c-checkbox__label">フィルター1</span>
            </label>
        </div>
    </aside>
</div>

わあ!こうして見ると、それぞれの役割がはっきりしていますね!

そうですね!覚えておくといいポイントをまとめておきます。

  • Component(c-) は「どのプロジェクトでも使える」パーツ
  • Project(p-) は「このプロジェクトならでは」のパーツ
  • Layout(l-) は「配置だけ」を担当
  • Utility(u-) は「調整用」の便利クラス
  • JavaScript(js-) は「JavaScript用」のフック

そして、迷ったときは、

  • 他のプロジェクトでも使えそう? → c-
  • このサイトでしか使わない? → p-
  • 位置だけを指定する? → l-
  • ちょっとした調整? → u-
  • JavaScriptで操作する? → js-

を考えてみるといいでしょう。

まとめ:BEMのメリット

最後に、BEMを使うメリットをまとめてきましょう!

  1. 保守性が高い
    • クラス名から要素の役割が明確に分かる
    • コードの見通しが良くなる
    • 修正や機能追加が容易
  2. 再利用性が高い
    • コンポーネント単位で考えやすい
    • 別のプロジェクトでも使いまわしやすい
  3. チーム開発に適している
    • 命名規則が統一される
    • コードレビューがしやすい
    • 新メンバーも理解しやすい

プロ太先生、今日はありがとうございました!
BEMを使えば、CSSがもっと楽しくなりそうです!

そうですね!最初は慣れるまで大変かもしれないけど、使いこなせるようになると本当に便利ですよ!がんばってください!

おわりに

BEMは確かにはじめは少し難しく感じるかもしれません。でも、一度理解してしまえば、CSS設計がぐっと楽になります。特に規模の大きなプロジェクトや、チームでの開発では非常に効果を発揮します。

みなさんも、ぜひBEMを使って、より良いCSSコーディングを目指してみてください!