良い CSS 設計とは? (Philip Waltson)

  1. 予測しやすい: クラス名を見るだけでどんなクラスであるかを大体理解できる.

  2. 再利用しやすい: プロジェクト中の複数箇所で必要になった際に使い回すことができる.

  3. 保守しやすい: スタイルが重複したり、意図せずにスタイルが当たったりしない.

  4. 拡張しやすい: 複数人での作業を全体に、誰が作業したとしても同じような品質のコードを得るために共通のルールを定める.

FLOCSS: Foundation Layout Object CSS (フロックス)

  • CSS を 3 つのレイヤーと 3 つの子レイヤーに分けて管理する.

    • 分類ごとにファイルを分けて管理する.

|
|- Foundation : 基本的なスタイル (リセット CSS、基本指定、変数定義、ファンクション etc.)
|- Layout     : サイト共通で使用するブロック (ヘッダー、メイン、フッター、サイドバー etc.)
`- Object     : 再利用可能なモジュールを 3 つの子レイヤーに分けて管理.
    |- Component : 最小単位のモジュール (ボタン、タイトル、フォーム、アイコン etc.)
    |- Project   : 複数の Component やそれに該当しない要素 (プロフィール、カード、モーダル etc.)
    `- Utility   : スタイルの調整のための便利クラス
Sass プロジェクトのファイル構成
/sass
|
|- /foundation
|   |- _base.scss
|   |- _variables.scss (サイト全体で使える変数)
|   |- _mixin          (サイト全体で使える mixin を管理)
|   `- _reset.scss
|- /layout
|   |- _header.scss
|   |- _main.scss
|   |- _footer.scss
|   `- _sidebar.scss
`- /object
    |- /component
    |   |- _button.scss
    |   `- _tab.scss
    |- /project
    |   |- _profile.scss
    |   `- _articles.scss
    `- /utility
        |- _margin.scss
        `- _position.scss

Foundation

  • 基本的なスタイルを定義する. リセット CSS やサイト内で使用する色の定義、ミックスインなど.

    • リセット CSS

    • 基本の指定

    • 変数の定義

    • ファンクション

    • etc.

// リセット CSS
* { margin: 0; }

// 基本の指定
body, a

// 変数の定義
:root {}
$primary : #0cf;

// ファンクション
@function, @mixin

Layout

  • サイト共通で使用するブロック (単位で唯一の存在である要素) を定義する.

    • ヘッダー

    • メイン

    • フッター

    • サイドバー

    • etc.

// ヘッダー
l-header

// メイン
l-main

// フッター
l-footer

// サイドバー
l-sidebar

Object

  • 再利用可能なモジュールを 3 つの子レイヤーに分けて管理する.

Component

  • 最小単位のモジュール (ボタン、タイトル、フォーム、アイコン etc.) を定義.

// ボタン
c-button

// タイトル
c-title

// フォーム
c-form

// アイコン
c-icon

Project

  • 複数の Component やそれに該当しない要素 (プロフィール、カード、モーダル etc.) を定義.

    • 複数の Component を組み合わせることで作ったパーツを Project と呼ぶ.

// プロフィール
p-profile

// カード
p-card

// モーダル
p-modal

Utility

  • スタイルの調整のための便利クラスを定義. マージン、ポジション、揃え位置、サイズ etc.

// マージン
u-margin

// ポジシヨン
u-position

// 揃え位置
u-align

// サイズ
u-size
  • この他、 Object の子レイヤーとして Theme が含まれる場合もある.

    • テーマによる色の切り替えや、ページ単位の色違いなどの設定.

    • プリフィックスとしては t- を用いて t-blue.scss のようにファイルを命名する.

FLOCSS の命名規則

  • 各レイヤーのアルファベットの頭文字とハイフンをクラス名の先頭に付与する.

    • ただし、Foundation は例外であり、命名規則のルールからは除外される.

    • したがって、Foundation は f- のような接頭辞を付与せずに記述する.

FLOCSS の命名規則
// Layout レイヤーに配置されるクラスは l- を接頭辞として付与する.
.l-header {
  background: #fff;
}
.l-main {
  flex: auto;
}

// Component レイヤーに配置されるクラスは c- を接頭辞として付与する.
.c-button {
  font-size: 1rem;
}
.c-title {
  font-size: 1.5rem;
}

// Project レイヤーに配置されるクラスは p- を接頭辞として付与する.
.p-profile {
  background: #ccc;
}
.p-article {
  border: 1px solid;
  }

// Utility レイヤーに配置されるクラスは u- を接頭辞として付与する.
.u-mt10 {
  margin-top: 10px;
}
.u-df {
  display: flex;
  }
  • このような命名規則により、クラス名を見るだけでどのファイルに定義されている、どんな用途のクラスなのかがだいたい分かるようになる.

  • なお、JavaScript で操作して状態が変更されることを表すためには、FLOCSS では is- をプレフィクスとするクラスを使用する.

    • 例えばクリックされた時の状態を表すクラスとして is-click といったクラスを使用する.

      • この is-click クラス自体にはスタイル情報は全く入っておらず、単なるマーカー目的で使用する.

    • 他のオブジェクトの状態変化に対して誤ってスタイルを当ててしまうことが無いように、 is-click 自体に対してはスタイルを定義しない.

    • 例えば Component であるボタンに対してクリック時のスタイルを指定したければ、 .c-button.is-click のようにスタイルを定義する.

      • .c-button.is-click の 2 つのクラスを両方持つ要素に対してだけスタイルが設定される.

BEM: Block Element Modifier (ベム)

  • Block 、 Element 、 Modifier という 3 つの要素を組み合わせて使用する命名規則.

- Block    : 大きな括り. 大枠.
- Element  : Block 中の要素.
- Modifier : Block、Element のパターン.
BEM を使った HTML の例
<div class="block">
  <div class="block__element">
    コンテンツ
  </div>
  <div class="block__element">
    コンテンツ
  </div>
  <div class="block__element--modifier">
    コンテンツ
  </div>
</div>
  • Block は大枠を表しており、Block 中の要素は Element として扱われる.

    • Block と Element はアンダースコア 2 つ __ で繋げられる.

      • Element 中にさらに Element が現れる場合もアンダースコア 2 つ __ で繋げられる.

  • Block や Element についてスタイルを変えたい場合 (パターンを増やしたい場合) には Modifier を使用する.

    • Modifier はハイフン 2 つ -- の後ろに記述する.

    • なお、Modifier の後ろに __ で Element を付与することは禁止.

      • よって、 cardtext—​inverse は OK だが、 card—​inversetext は禁止.

Block
<!-- Block は大枠 -->
<article class="card">
  テキスト
</article>
Element
<!-- Block 中の要素は Element として扱われる -->
<article class="card">
  <div class="card__text">
    <h3 class="card__text__title">
      タイトル
    </h3>
  </div>
Modifier
<!-- Block や Element のスタイルを変えたい場合には Modifier を使用する -->
<article class="card">
  <div class="card__text">
    <h3 class="card_text__title">
      タイトル
    </h3>
  </div>
  <div class="card__image--round">
    <img src="">
  </div>
</article>

<article class="card--inverse">
  <div class="card__text">
    <h3 class="card__text__title">
      ...
    </h3>
  </div>
</article>
  • なお、 Block や Element 名が 2 単語以上となる場合には、 単語-単語 のように区切り文字としてハイフンを使用する.

  • BEM を使った場合と使わない場合の比較

    • BEM を使うとぱっと見たときに構成がわかりやすくなる.

BEM を使っていない場合
<header class="header">
  <a class="logo">
    <img src="logo.svg">
  </a>
  <nav class="nav">
    <a class="item">会社概要</a>
    <a class="item">サービス</a>
    <a class="item accent">お知らせ</a>
  </nav>
</header>
BEM を使う場合
<header class="header">
  <a class="header__logo">
    <img src="logo.svg">
  </a>
  <nav class="header__nav">
    <a class="header__nav__item">会社概要</a>
    <a class="header__nav__item">サービス</a>
    <a class="header__nav__item--accent">お知らせ</a>
  </nav>
</header>
  • サイトの規模が大きくなればなるほど BEM の恩恵は大きくなる.

    • クラス名の重複を防ぎ、どこにあるどんな要素か想像できるようになる.

    • ただし、クラス名が長くなってしまう.

  • BEM も FLOCSS と同様に Sass との相性が良いため、上記のスタイルは Sass では非常にわかりやすく記述することができる.

    • HTML の構造とスタイルシートの構造がほぼ対応するようになり、読みやすくなる.

Sass での BEM の記述
.header {
  &__logo {
    img {
      height: 24px;
    }
  }
  &__nav {
    &__item {
      color: #000;
      &--accent {
        color: #ff0000;
      }
    }
  }
}
対応する HTML
<header class="header">
  <a class="header__logo">
    <img src="logo.svg">
  </a>
  <nav class="header__nav">
    <a class="header__nav__item">会社概要</a>
    <a class="header__nav__item">サービス</a>
    <a class="header__nav__item--accent">お知らせ</a>
  </nav>
</header>

Sass では &親要素セレクタ自体 を参照することができ、 { } の中で更に { } を記述する際にセレクタ名の一部として使用することができる. (https://prograshi.com/design/css/ampersand-in-selector/)

元となる Sass コード
.test {
  &--red {
    color: red;
  }
}
生成される CSS コード
.test-red {
  color: red;
}

FLOCSS と BEM の併用

HTML のヘッダーコード例
<header>
  <a>
    <img src="logo.svg">
  </a>
  <nav>
    <a>会社概要</a>
    <a>サービス</a>
    <a>お知らせ</a>
  </nav>
  <div>
    <a>お問い合わせ</a>
  </div>
</header>
  • まず FLOCSS ではヘッダーは Layout の要素に分類される.

    • FLOCSS のルールに従い、 l-header というクラス名が付与される.

  • ヘッダー内に配置される要素は BEM のルールに従ってクラス名を付与する.

    • l-header が BEM における Block に相当するため、その後ろに __ を使って Element を付与する.

    • 特定の要素だけデザインが異なる場合には -- を使って Modifier を付与する.

  • 他の場所で使い回せるようなスタイルは FLOCSS の Component として定義するとよい.

FLOCSS と BEM による HTML のスタイリング
<header class="l-header">
  <a class="l-header__logo">
    <img src="logo.svg">
  </a>
  <nav class="l-header__nav">
    <a class="l-header__nav__item">会社概要</a>
    <a class="l-header__nav__item">サービス</a>
    <a class="l-header__nav__item--accent">お知らせ</a>
  </nav>
  <div class="l-header__action">
    <a class="c-button">お問い合わせ</a>
  </div>
</header>
Sass プロジェクトのファイル構造
sass
|- foundation
|- layout
|   `- _header.scss あるいは _l-header.scss
`- object
    |- component
        `- _button.scss あるいは _c-button.scss 、 _btn.scss など.
    |- project
    `- utility
sass/layout/_header.scss
.l-header {
  &__logo {
    img {
      height: 1.5rem;
    }
  }
  &__nav {
    display: flex;
    &__item {
      color: #000;
      &--accent {
        color: #f00;
      }
    }
  }
  &__action {
    .c-button {
      font-size: 14px;
    }
  }
}
sass/object/component/_button.scss
.c-button {
  color: #fff;
  font-size: 16px;
  display: block;
  padding: 1rem;
  background: #000;
}
  • 作業の流れとしては、 HTML を BEM の構成で記述し、その後 FLOCSS の構成で Sass ファイルを作成するという順序となる.

サンプルプロジェクト

$ npm init -y
$ npm install --save-dev gulp gulp-sass sass
$ vim gulpfile.js
$ vim package.json

$ mkdir sass
$ mkdir -p sass/foundation
$ mkdir -p sass/layout
$ mkdir -p sass/object/component
$ mkdir -p sass/object/project
$ mkdir -p sass/object/utility
gulpfile.js
const gulp = require('gulp');
const sass = require('gulp-sass')(require('sass'));

exports.sass = () => {
  return gulp.src('./sass/**/*.scss')
    .pipe(sass({ outputStyle: 'expanded' }))
    .pipe(gulp.dest('./css'));
};

exports.watch = () => {
  return gulp.watch('./sass/**/*.scss', ['sass']);
};
package.json
{
  "name": "hello-flocss-bem",
  "version": "1.0.0",
  "description": "=  CSS 設計: FLOCSS と BEM について - 破綻しにくい CSS 命名規則",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "gulp sass",
    "watch": "gulp watch"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "gulp": "^4.0.2",
    "gulp-sass": "^5.1.0"
  }
}
index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>FLOCSS & BEM テスト</title>
    <link rel="stylesheet" href="./css/style.css">
  </head>
  <body>
    <header class="l-header">
      <a class="l-header__logo">
        <img src="logo.svg">
      </a>
      <nav class="l-header__nav">
        <a class="l-header__nav__item">会社概要</a>
        <a class="l-header__nav__item">サービス</a>
        <a class="l-header__nav__item--accent">お知らせ</a>
      </nav>
      <div class="l-header__action">
        <a class="c-button">お問い合わせ</a>
      </div>
    </header>
  </body>
</html>
sass/foundation/_normalize.scss
// https://github.com/necolas/normalize.css/blob/master/normalize.css

/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */

/* Document
   ========================================================================== */

/**
 * 1. Correct the line height in all browsers.
 * 2. Prevent adjustments of font size after orientation changes in iOS.
 */

html {
  line-height: 1.15; /* 1 */
  -webkit-text-size-adjust: 100%; /* 2 */
}

/* Sections
   ========================================================================== */

/**
 * Remove the margin in all browsers.
 */

body {
  margin: 0;
}

/**
 * Render the `main` element consistently in IE.
 */

main {
  display: block;
}

/**
 * Correct the font size and margin on `h1` elements within `section` and
 * `article` contexts in Chrome, Firefox, and Safari.
 */

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

/* Grouping content
   ========================================================================== */

/**
 * 1. Add the correct box sizing in Firefox.
 * 2. Show the overflow in Edge and IE.
 */

hr {
  box-sizing: content-box; /* 1 */
  height: 0; /* 1 */
  overflow: visible; /* 2 */
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd `em` font sizing in all browsers.
 */

pre {
  font-family: monospace, monospace; /* 1 */
  font-size: 1em; /* 2 */
}

/* Text-level semantics
   ========================================================================== */

/**
 * Remove the gray background on active links in IE 10.
 */

a {
  background-color: transparent;
}

/**
 * 1. Remove the bottom border in Chrome 57-
 * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
 */

abbr[title] {
  border-bottom: none; /* 1 */
  text-decoration: underline; /* 2 */
  text-decoration: underline dotted; /* 2 */
}

/**
 * Add the correct font weight in Chrome, Edge, and Safari.
 */

b,
strong {
  font-weight: bolder;
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd `em` font sizing in all browsers.
 */

code,
kbd,
samp {
  font-family: monospace, monospace; /* 1 */
  font-size: 1em; /* 2 */
}

/**
 * Add the correct font size in all browsers.
 */

small {
  font-size: 80%;
}

/**
 * Prevent `sub` and `sup` elements from affecting the line height in
 * all browsers.
 */

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

/* Embedded content
   ========================================================================== */

/**
 * Remove the border on images inside links in IE 10.
 */

img {
  border-style: none;
}

/* Forms
   ========================================================================== */

/**
 * 1. Change the font styles in all browsers.
 * 2. Remove the margin in Firefox and Safari.
 */

button,
input,
optgroup,
select,
textarea {
  font-family: inherit; /* 1 */
  font-size: 100%; /* 1 */
  line-height: 1.15; /* 1 */
  margin: 0; /* 2 */
}

/**
 * Show the overflow in IE.
 * 1. Show the overflow in Edge.
 */

button,
input { /* 1 */
  overflow: visible;
}

/**
 * Remove the inheritance of text transform in Edge, Firefox, and IE.
 * 1. Remove the inheritance of text transform in Firefox.
 */

button,
select { /* 1 */
  text-transform: none;
}

/**
 * Correct the inability to style clickable types in iOS and Safari.
 */

button,
[type="button"],
[type="reset"],
[type="submit"] {
  -webkit-appearance: button;
}

/**
 * Remove the inner border and padding in Firefox.
 */

button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

/**
 * Restore the focus styles unset by the previous rule.
 */

button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
  outline: 1px dotted ButtonText;
}

/**
 * Correct the padding in Firefox.
 */

fieldset {
  padding: 0.35em 0.75em 0.625em;
}

/**
 * 1. Correct the text wrapping in Edge and IE.
 * 2. Correct the color inheritance from `fieldset` elements in IE.
 * 3. Remove the padding so developers are not caught out when they zero out
 *    `fieldset` elements in all browsers.
 */

legend {
  box-sizing: border-box; /* 1 */
  color: inherit; /* 2 */
  display: table; /* 1 */
  max-width: 100%; /* 1 */
  padding: 0; /* 3 */
  white-space: normal; /* 1 */
}

/**
 * Add the correct vertical alignment in Chrome, Firefox, and Opera.
 */

progress {
  vertical-align: baseline;
}

/**
 * Remove the default vertical scrollbar in IE 10+.
 */

textarea {
  overflow: auto;
}

/**
 * 1. Add the correct box sizing in IE 10.
 * 2. Remove the padding in IE 10.
 */

[type="checkbox"],
[type="radio"] {
  box-sizing: border-box; /* 1 */
  padding: 0; /* 2 */
}

/**
 * Correct the cursor style of increment and decrement buttons in Chrome.
 */

[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

/**
 * 1. Correct the odd appearance in Chrome and Safari.
 * 2. Correct the outline style in Safari.
 */

[type="search"] {
  -webkit-appearance: textfield; /* 1 */
  outline-offset: -2px; /* 2 */
}

/**
 * Remove the inner padding in Chrome and Safari on macOS.
 */

[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

/**
 * 1. Correct the inability to style clickable types in iOS and Safari.
 * 2. Change font properties to `inherit` in Safari.
 */

::-webkit-file-upload-button {
  -webkit-appearance: button; /* 1 */
  font: inherit; /* 2 */
}

/* Interactive
   ========================================================================== */

/*
 * Add the correct display in Edge, IE 10+, and Firefox.
 */

details {
  display: block;
}

/*
 * Add the correct display in all browsers.
 */

summary {
  display: list-item;
}

/* Misc
   ========================================================================== */

/**
 * Add the correct display in IE 10+.
 */

template {
  display: none;
}

/**
 * Add the correct display in IE 10.
 */

[hidden] {
  display: none;
}

// https://webdesign-trends.net/entry/8137#Normalizecss
*,
*::before,
*::after {
  box-sizing: border-box;
}
sass/layout/_header.scss
.l-header {
  &__logo {
    img {
      height: 1.5rem;
    }
  }

  &__nav {
    display: flex;
    &__item {
      color: #000;
      &--accent {
        color: #f00;
      }
    }
  }

  &__action {
    .c-button {
      font-size: 14px;
    }
  }
}
sass/object/component/_button.scss
.c-button {
  color: #fff;
  font-size: 16px;
  display: block;
  padding: 1rem;
  background: #000;
}
sass/style.scss
// Foundation
@use './foundation/normalize';

// Layout
@use './layout/header';

// Object - Component
@use './object/component/button';

// Object - Project
//@use './object/project/xxx';

// Object - Utility
//@use './object/utility/xxx';
  • ビルド

$ npm run build