模写修行メディア

【初心者向け】模写修行流CSS設計を紹介!

【初心者向け】模写修行流CSS設計を紹介!

この記事をシェア:

コーディングの練習が出来るサービス「模写修行」を制作している中で、どんなCSS設計を採用するか悩みました。

模写修行は、ある程度基礎を学び終えたくらいの方が対象なので、有名なCSS設計やそもそもCSS設計自体を知らなくても使えるようにしなければいけません。

OOCSS / SMACSS / BEM / … などの考え方を採用しつつ、webサイトからwebサービスまで、なるべく幅広く対応出来るように考えたつもりです。

どんなCSS設計を採用するかに正解は無いので、『模写修行流』ということで、1つのサンプルとして参考になれば幸いです。

【CSS設計入門】class名の決め方(命名規則)から具体的な書き方まで詳しく解説

CSS設計についてまだ全く勉強したことがない方は、こちらの記事を読んでからだと内容が入りやすいかもしれません。

雛形コードのダウンロード

雛形コードを見ながらこの記事を読んだ方が、より理解できると思うので、ダウンロードできるようにしました。どこに何を書くか、イメージがつくようにコメントを入れただけの雛形です。

  • scssディレクトリ内全て
  • css > style.css.map

SCSSを使わない方は、上のコードを消してしまって大丈夫です。

SCSSはDart Sassを使うことを前提にしています。

Dart SassをVSCodeの拡張機能(プラグイン)で利用する方法を紹介

VSCodeの拡張機能(プラグイン)を使って、Dart Sassをコンパイルする方法は、上の記事を参考にしてみてください。

コーディングする際の基本ルール

ガチガチのルールを設けるわけではなく、最低限にとどめています。重要な点だけ抑えて、それ以外の細かい点は好みもあるので自分の中でルール化すれば良いです。

classの命名

.service-title{...}
.service-list{...}
.service-item{...}

.c-button {...}
.c-button--primary {...}
.c-button--secondary {...}
.c-button--accent {...}

class名は全てハイフン区切りにしています。


✋ BEMを勉強していない方はここ読み飛ばしてください

BEMのようにブロックとエレメントで分ける命名は、一目でそれがブロックなのかエレメントなのかがわかる点がメリットです。しかし、最初のうちはclassをつける際にこれはブロックか?エレメントか?で悩むことにもなります。全てハイフン区切りであれば、classをつける際のその悩みがなくなります。ブロックなのかエレメントなのかは曖昧でも、迷わず先に進める方を優先しました。


カラーやサイズなどのバリエーションを実装するための、バリエーションクラス(Modifier)はハイフンを2つつけるルールにします。

<!-- 背景赤色のボタンになる -->
<button class="c-button c-button--accent">ボタン</button>
.c-button { /* 全ボタンに適応させたいスタイルを書く */ }

.c-button--primary {
    background-color: #000;
    color: #fff;
}

...

.c-button--accent {
    background-color: red;
    color: #fff;
}

バリエーションクラス(Modifier)はこのように使います。

  • class名の省略はなるべくしない
  • 連番が必要な場合は、1番目は番号は付けずに、2番目から付ける

上の点にも気をつけます。

シングルクラスの採用

種類説明
シングルクラス1つのタグに対して1つのclassがつくこと
マルチクラス1つのタグに対して複数のclassがつくこと

シングルクラスとマルチクラスの定義はこのようにしています。基本的にはシングルクラスで書きますが、例外もあります。


💡 例外(マルチクラスになる例)

<!-- 🙆‍♂️ OK -->
<h2 class="c-title c-title—-center">
  Modifierと一緒に使うケースはOK
</h2>

<!-- 🙆‍♂️ OK -->
<div class="service u-ptb-m">
  utilityと一緒に使うケースはOK
</div>

<!-- 🙆‍♂️ OK -->
<section class="service l-section">
  layoutと一緒に使うケースはOK
</section>

<!-- 🙅‍♂️ NG -->
<section class="service-title c-title">
  componentと一緒に使うケースはNG
</section>

<!-- 🙆‍♂️ OK -->
<h2 class="c-title c-title—-center js-page-title has-animation-title-fade-in is-title-fade-in-active">
  JavaScriptで操作する際にこのようになるケースもOK
</h2>

まだ紹介していない用語は後半で紹介します。意味が分からない場合は読み進めてから、最後に再度読むと理解できると思います。

  • Modifierを使用する
  • utilityを使用する
  • layoutを使用する
  • JavaScriptのフックを使用する
  • アニメーションを使用する
  • 状態によってJavaScriptで追加削除される

これらのケースではマルチクラスになっても良いことにします。


💡 使用例(シングルクラスでの書き方)

<!-- 🙅‍♂️ NG -->
<button class="c-button contact-submit-button">…</button>

<!-- 🙆‍♂️ OK -->
<div class="contact-submit-button">
  <button class=“c-button>…</button>
</div>
.contact-submit-button{
  margin-top: 40px;
}

例えばボタンの上に40pxの余白を開けたい場合は、このように書きます。.c-buttonは色々なところで使い回すパーツ(コンポーネント)です。

NGの例は悪い書き方というわけではありません。今回はそのような書き方をしないルールにしただけです。どのclassに書いたスタイルが適応されているか分かりやすいように基本的にはシングルクラスにしました。


サイトによってはマルチクラスでの運用の方がスッキリ書けて、ページや要素の追加も楽かもしれません。しかし、マルチクラスは複雑なデザインの場合、破綻を招きやすい特徴があります。

ディレクトリ構成

ディレクトリ構成は雛形をダウンロードしてもらえると分かりやすいと思います。

小中規模なサイトを制作する想定なので、CSSは1ファイルにコメントで区切りながら書きます。SCSSの場合は、後述する役割ごとにディレクトリを切って、その中でブロックごとに分割しています。

ブロックとはそのサイトやページの中でのパーツや塊だと思ってもらえればOKです。

その他

基本的にスタイリングする際はclassを付与して、単体のclassセレクタに書きます。

/* 🙅‍♂️ NG */
.hoge h2{
  margin-top: 40px;
}

/* 🙆‍♂️ OK */
.hoge-title{
  margin-top: 40px;
}

/* 🙆‍♂️ 例外でOK */
.c-posts--col3 > .c-posts-item{
    ...
}

例外に関しては.m-posts-item.m-posts-item--col3のようにしても良いのですが、HTMLが見にくくなるので、例外的にOKにしています。

<!-- 少し見にくい... -->
<div class="c-posts c-posts--col3">
    <article class="c-posts-item c-posts-item--col3"></article>
    <article class="c-posts-item c-posts-item--col3"></article>
    ...
</div>

<!-- 見やすい -->
<div class="c-posts c-posts--col3">
    <article class="c-posts-item"></article>
    <article class="c-posts-item"></article>
    ...
</div>

あまり変わらないと感じる方もいるかもしれませんが、下の方がスッキリします。

classのカテゴリ分類とその詳細

class のカテゴリ役割
globalサイト内共通の設定
foundationリセットCSSとサイト内共通で効かせたいスタイル
utilityちょっとした使いわますスタイル
componentサイト内で使い回すパーツのスタイル
layoutレイアウトを作るスタイル
page各ページ固有のスタイル
animationアニメーションに関するスタイル

スタイルをこれらの7つのカテゴリに分類します。これ以降、どんな役割のスタイルか詳しく紹介します。

このような分け方をしたのはDart Sassとの兼ね合いもあります。構成を考える際に参考になった記事を載せておきます。

globalカテゴリ

globalカテゴリ
サイト内共通の設定が入る
  • カラー
  • コンテナの幅
  • フォント
  • z-index

このような値をカスタムプロパティで定義しておきます。SCSSを使う場合は、それぞれ別ファイルに書くので4ファイルできることになります。

  • ブレークポイントの設定
  • mixin

SCSSを使う場合は、上の2つも必ずglobalカテゴリに存在します。個人的にあまりmixinを使わないので、mixinは1ファイルにまとめて書きますが、よく使う場合はmixinディレクトリを作っても良いかもしれません。


💡 CSSの場合(SCSSを使わない場合)

/*!
global > color
------------------------------
*/
:root {
  --color-font-base: #333;
}

/*!
global > content-width
------------------------------
*/
:root {
  --width-content-s: 920px;
  --width-content: 1080px;
}

/*!
global > font
------------------------------
*/
:root {
  --font-family-base: "Helvetica Neue", Arial, "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
}

/*!
global > z-index
------------------------------
*/
:root {
  --z-index-modal: 100;
  --z-index-header: 30;
  --z-index-menu: 10;
  --z-index-default: 1;
}

このようにコメントで区切って入れています。


💡 SCSSの場合

/*!
global > color
------------------------------
*/
:root {
    --color-font-base: #333;
}

_color.scssのコードです。他のファイルも同じように書いています。

@use "sass:map";

// global > breakpoint
// ------------------------------
$breakpoints: (
    "sm": 500px,
    "md": 768px,
    "lg": 1080px,
    "xl": 1200px,
);

@mixin mq($breakpoint: md) {
    @media screen and (min-width: #{map-get($breakpoints, $breakpoint)}) {
        @content;
    }
}

_breakpoints.scssのコードです。

// 矢印
// ex)
// <li class="side-item">
//     <a class="side-link">サイドメニュー</a>
// </li>
// .side-link{
//     @include arrow(45deg, #000, 8px, 8px, 0, 8px, 0, auto);
// }
@mixin arrow($color: var(--baseFontColor), $rotate: 45deg, $w: 6px, $h: 6px, $left: 0, $right: auto) {
    position: relative;

    &::before {
        content: "";
        position: absolute;
        top: 0;
        bottom: 0;
        margin: auto;
        left: $left;
        right: $right;
        width: $w;
        height: $h;
        border-top: 2px solid $color;
        border-right: 2px solid $color;
        transform: rotate($rotate);
    }
}

...

_mixin.scssのコードです。分かりにくいものはコメントで使用例も載せています。

foundationカテゴリ

foundationカテゴリ
リセットCSSとサイト内共通で効かせたいスタイルが入る
分類役割
foundation > resetリセットCSS
foundation > baseプロジェクト固有のサイト内共通で効かせたいスタイル

foundationカテゴリ内はresetとbaseに分類します。分類の基準は表の通りです。

/*!
foundation > reset
------------------------------
*/
html {
  color: #000;
  background: #fff;
}

body,
div,
dl,
dt,
dd,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5,
h6,
pre,
code,
form,
fieldset,
legend,
input,
textarea,
p,
blockquote,
th,
td {
  margin: 0;
  padding: 0;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
  width: 100%;
}

fieldset,
img {
  border: 0;
}

address,
caption,
cite,
code,
dfn,
em,
strong,
th,
var {
  font-style: normal;
  font-weight: normal;
}

ol,
ul {
  list-style: none;
}

caption,
th {
  text-align: left;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: 100%;
  font-weight: normal;
}

q:before,
q:after {
  content: "";
}

abbr,
acronym {
  border: 0;
  font-variant: normal;
}

sup {
  vertical-align: text-top;
}

sub {
  vertical-align: text-bottom;
}

input,
textarea,
select,
button {
  color: inherit;
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
  *font-size: 100%;
  border-radius: 0;
  border: none;
  appearance: none;
  -webkit-appearance: none;
  background-color: inherit;
}

input,
textarea,
select {
  font-size: 16px;
}

textarea {
  resize: vertical;
  display: block;
}

button {
  padding: 0;
  cursor: pointer;
}

legend {
  color: #000;
}

main {
  display: block;
}

a {
  text-decoration: none;
  color: inherit;
}

img {
  width: 100%;
  height: auto;
  vertical-align: bottom;
}

svg {
  display: block;
}

* {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  box-sizing: border-box;
}

*::before,
*::after {
  box-sizing: border-box;
}

/*!
foundation > base
------------------------------
*/
body {
  line-height: 1.8;
  font-size: 14px;
  color: var(--color-font-base);
  font-family: var(--font-family-base);
}
@media screen and (min-width: 768px) {
  body {
    font-size: 16px;
  }
}

SCSSではfoundationディレクトリの中に、_reset.scss_base.scssが入ることになります。

utilityカテゴリ

utilityカテゴリ
余白や隠しテキストなどのちょっとした使いわますコードが入る
  • 表示切り替え用のクラス
  • 余白関連
  • 隠しテキスト

例えばこれらなど、必要最低限にします。class名にはプレフィックスとしてu-を付けるようにします。

プレフィックスをつけることで、他の箇所でも使っていることを念頭に置きながらコードを書けます。これで思わぬところにも影響してしまうようなリスクも減らせます。

/*!
utility > utility
------------------------------
*/
@media screen and (min-width: 768px) {
  .u-sp {
    display: none !important;
  }
}

.u-pc {
  display: none !important;
}
@media screen and (min-width: 768px) {
  .u-pc {
    display: block !important;
  }
}

.u-visually-hidden {
  position: absolute !important;
  white-space: nowrap !important;
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
  border: 0 !important;
  padding: 0 !important;
  clip: rect(0 0 0 0) !important;
  clip-path: inset(50%) !important;
  margin: -1px !important;
}

utilityカテゴリは最優先で効いて欲しいので!importantを付けます。!importantをさらに上書きする必要があるケースは、そもそもutility classを使うべきでない箇所なので、上書きに上書きを重ねることはありません。

utilityカテゴリ内のコードはあまり増やさないようにしているので、SCSSの場合は、_utility.scssの1ファイルに全て書きます。

componentカテゴリ

componentカテゴリ
サイト内で使い回すパーツ(=コンポーネント)が入る

class名にはプレフィックスとしてc-を付けるようにします。

/*!
component > button
------------------------------
*/
.c-button {
    ...
}

.c-button--primary {
    ...
}

/*!
component > title
------------------------------
*/
.c-title {
    ...
}

.c-title--center {
    ...
}

SCSSではコンポーネントごとにファイル分割します。上の例だと、_button.scss_title.scssで2つのファイルに分割します。

デザインのルールがしっかり決まっているサイトでは、componentに属するスタイルが多くなります。

layoutカテゴリ

layoutカテゴリ
レイアウトを作るコードが入る
  • header
  • footer
  • sidebar
  • container

例えばこれらがレイアウトを作るためのスタイルです。

header / footer / sidebarはレイアウトと分りやすいので、プレフィックスは付けませんが、それ以外には基本的にプレフィックスを付けます。

/*!
layout > container
------------------------------
*/
.l-container, .l-container-s {
  width: 90%;
  margin: 0 auto;
}

.l-container-s {
  max-width: var(--width-content-s);
}

.l-container {
  max-width: var(--width-content);
}

/*!
layout > header > header
------------------------------
*/
.header { ... }

/*!
layout > header > header-nav
------------------------------
*/
.header-nav { ... }

/*!
layout > footer > footer
------------------------------
*/
.footer { ... }

/*!
layout > footer > footer-nav
------------------------------
*/
.header-nav { ... }

SCSSではファイル分割します。上の例だと5つのファイルに分割します。シンプルなデザインではheaderやfooterは1ファイルにまとめてしまうこともあります。その辺りは臨機応変に対応します。

pageカテゴリ

pageカテゴリ
各ページ固有のコードが入る

class名には各ページの名前を必ず入れます。

example.com/service/の場合

service-title{…}
service-text{…}
service-button{…}

example.com/about/の場合

about-title{…}
about-text{…}
about-button{…}

各ページの名前をプレフィックスにしておけば、他のページとclass名が被ることもありません。

コンポーネントが少なく、pageカテゴリのスタイルが1番多くなることも少なくありません。

SCSSで書く場合は、ページ名でディレクトリを切って、その中にページのブロック(塊)ごとにファイル分割することになります。

animationカテゴリ

animationカテゴリ
演出目的のアニメーションに使うためのコードが入る

演出目的とは、例えばスクロールに連動してフェードインさせるようなアニメーションなどのことです。

  • タブメニュー
  • モーダル
  • ハンバーガーメニュー

上のような演出目的ではなく、機能目的のUIを実装する際は、animationカテゴリに入れません。


アニメーションでの使用例

<h2 class="service-title js-page-title has-animation-title-fade-in">
  サービス
</h2>
/* SCSSで書く場合のディレクトリは、 */
/* page > service > service.scss */
/* ----------------------------- */
.service-title{
  ...
}

/* SCSSで書く場合のディレクトリは、 */
/* animation > title-animation.scss */
/* ----------------------------- */
.has-animation-title-fade-in{
    opacity: 0;
    transition: opacity .3s;
}

.is-animation-title-fade-in-active{
    opacity: 1;
}

タイトルが表示領域に入ると、透明度を0から1にアニメーションをするためのサンプルです。見た目のスタイルとアニメーションのためのスタイルは分離した方が分かりやすいので、このようにしています。

.js-hogeはJavaScriptのフックに使うもので、このclassに対してスタイリングしてはいけません。


ハンバーガーメニューでの使用例(animation カテゴリに属さない)

<div class="header-menu js-header-menu">
  ...
</div>
/* SCSSで書く場合のディレクトリは、 */
/* layout > header > header-menu.scss */
/* ----------------------------- */
.header-menu{
  ...
  display: none;
}

/* ハンバーガーメニューactive時に付与するclass */
/* layout > header > header-menu.scss */
.is-header-menu-active{
    display: block;
}

このような場合、animationカテゴリには属さず、.header-menuの近くにコメントをつけて書きます。

CSS設計の勉強がしたい方は「模写修行」を!

模写修行

コーディングの練習が出来るサービス「模写修行」を作りました。

模写修行はこんな方におすすめ!

  • 基礎学習を終えて実践的な経験を積みたい
  • 破綻しない書き方を学びたい(= CSS設計を意識したコーディングを学びたい)
  • 実務と同じようにXDのデータを見ながらコーディングの練習をしたい
  • 現役で制作をやっている人のコードを見たい

この記事で紹介したCSS設計を使用した教材になっています。基礎学習を終えて実践的な"練習"がしたい方は、是非覗いてみてください。

この記事をシェア:

模写修行のトップページのスクリーンショット
模写修行

駆け出しエンジニアのためのコーディング練習教材