ツクリンクのCSS設計と書き方


はじめに

この記事はツクリンクのエンジニアを対象に、サービスで利用されているCSS設計の概要を知り、実際にマークアップできるようになるためのドキュメントです。
社外にも公開することで、CSS設計と運用の一例として参照していただいたり、改善意見の募集、今後ツクリンクで一緒に開発していただく可能性のある方に開発体制をよく知っていただくことも期待しています。
疑問点、改善点などあればお気軽にコメントしてください。

※ここで紹介した設計、運用は執筆現在のものです。記事の更新状況によっては実際の運用と異なる可能性があります。

CSS設計の変遷

現状のCSS設計の詳細の前にこれまでどのようなCSSが書かれてきたのかを説明します。

v1, 規則無し

初回リリース時はid, clsss, 要素型セレクタなどを複数使ったマークアップをしていました。
一部の古いページ、SP版のレイアウトで極稀に残っている可能性があります。

#main .articles p {  }
#main .articles .header {  }

v2, MindBEMding

チームで開発することになりMindBEMdingをベースとしたCSS設計を導入、ファイルはBlockごとに分け、クラスの衝突を防いでいました。

_block.scss
.block {
    &__element {
        &--modifier {}
    }
}
# ディレクトリ構造
├── application.scss
├── _mixin.scss
├── _valiables.scss
└── blocks
  ├── _articles.scss
  ├── _block.scss
  ├── _beer.scss
  └── _pizza.scss

v3, FLOCSS

CSS設計にFLOCSSを採用。
デザインにはAtomic Designを採用し、デザインからCSSへの連携を考えるようになりました。

FLOCSSについて

FLOCSSはレイヤー分けしてUIコンポーネントを管理する、マルチクラスパターンのCSS設計手法です。
基本的に、複数セレクタを使ったカスケーディングはせず、1つのクラスセレクターのみを使いモジュールを作ります。

FLOCSS(フロックス) は、OOCSSやSMACSS、BEM、SuitCSSのコンセプトを取り入れた、モジュラーなアプローチのためのCSS構成案です。
hiloki/flocss: CSS organization methodology.

/* OK */
.c-button {}

/* NG */
.c-button .c-text {}

レイヤー構造

レイヤー構造と合わせて、実際にどの様に使っているかを説明します。

FLOCSSは次の3つのレイヤーと、Objectレイヤーの子レイヤーで構成されます。
- Foundation - reset/normalize/base...
- Layout - header/main/sidebar/footer...
- Object
 - Component - grid/button/form/media...
 - Project - articles/ranking/promo...
 - Utility - clearfix/display/margin...

hiloki/flocss: CSS organization methodology.

Foundation

Reset.cssNormalize.cssなどが入っています。
当時のリニューアルのタイミングや思想の違いがあり、PC版ではNormalize.css、SP版ではReset.cssが使われています。
これは若干の負債となっていて、コンポーネントなどでは基本的に margin: 0 など初期化を明示的にする必要がある場面があります。

Layout

ヘッダー、フッターなどサイト内共通のパーツが入っています。

Object

Object内に3つの子レイヤーがあります。

Component

ボタンやラベルなど再利用できるモジュールです。
再利用可能なものは基本的にここに入ります。

Project

ページ固有のモジュールです。
サーバーサイドはRailsを利用しているため、基本的にはコントローラー/アクション名に合わせて命名しています。
例:UsersController#show -> .p-users-show
コントローラー名などが長い場合、他と被らなければ短い命名をしても問題ありません。
例:StaticPageController#guide -> .p-guide

Utility

ComponentとProjectレイヤーのObjectのモディファイアで解決することが難しい・適切では無い、わずかなスタイルの調整のための便利クラスなどを定義します。

marginfont-weight: bold などの便利クラスを入れています。
特にmarginをコンポーネントには持たせたくないためutilitiesを使う、もしくはProjectで吸収することを推奨しています。

objects/utilities/_margin.scss
.u-margin-t8p {
    margin-top: 8px !important;
}

命名規則

MindBEMdingを利用します。
コンポーネントをBlock、コンポーネント内でしか生きられない要素をElement、特定のプロパティを上書きする属性をModifierと呼び .block-name__element-name といった形式でクラス名を付けます。
※詳細については公式ドキュメントを見たほうが早いです

Modifierの命名の派生パターンとして、JavaScriptで操作されるような「状態」を表すようなModifierについては、SMACSSのStateパターンの命名を拝借し、is-*プレフィックスを付与し、.is-activeというようにすることもできます。
hiloki/flocss: CSS organization methodology.

objects/componets/_form.scss
.c-form {
    &__label {}
    &__input {
        &.is-disabled {}
    }
}

ツクリンクでの具体的な運用方法

前提
現在はAtomic Designをベースにデザインし、CSS設計にはFLOCSSを利用したデザインシステムを作っている最中です。
このデザインシステムを TDS(Tsukulink Design System) と呼んでいます。

ファイル構成

Railsのasset pipelineに乗っています。
基本的にはPC/SPで別になった1つのCSSファイルにまとめられますが、読み込みの高速化対応をしているページでは必要な個別のエントリーポイントを用意し必要なCSSのみ読み込んでいます。

カッコ内は作られた時期と利用法

app/assets/stylesheets/
├── amp (AMP用のCSS)
├── common (v2 同ディレクトリmobile/pcで共通で使われるもの)
├── lp (LP用の独立したCSS群)
├── mobile (SP版で利用される)
  ├── blocks (v2 様々なコンポーネント)
  ├── globals (v2 同ディレクトリ全てのエントリーポイントで読まれるもの)
  ├── _mixin.scss
  ├── _valiables.scss
  ├── application.scss (SP版全体で使われているエントリーポイント)
  ├── companies-show.scss (CompaniesController#showでのみ使われるエントリーポイント)
  ├── その他エントリーポイント
├── pc (PC版で利用される 同ディレクトリmobileとほぼ同じ)
├── tds (v3 FLOCSSベースのCSS エントリーポイントは同ディレクトリmobile/pcにあり、そこから読まれている)
  ├── foundation
  ├── layout
  ├── object
    ├── components
    ├── projects
    └── utilities
  ├── _mixin.scss
  ├── _var_colors.scss
  ├── _var_fonts.scss
  ├── _var_z_index.scss
  ├── base.scss (TDS全てが入っている)
  └── core.scss (objects以外が入っている、高速化のため必要なコンポーネントのみ読み込むエントリーポイントで使う)

コンポーネント名

Atomic Designのレイヤー訳をされているFigma上にあるデザインデータからコンポーネントを作成します。
基本的にFigmaのコンポーネント名がそのままCSSの命名に使われます。
同名のコンポーネントがあるか調べるだけなので既に実装済か確認する手間を減らせます。

Atomic DesignとFLOCSSのレイヤー構造が異なるためエンジニアの判断が必要になりますが、基本的には以下のとおりです。

# Atomic Designレイヤー -> FLOCSSレイヤー
Atoms     -> Component
Molecules -> Component
Organisms -> Component or Project
Templates -> Layout
Pages     -> Project

実装をする場合

FLOCSSに従い書いてください。

コンポーネントやファイルの作り方

新しいCSSを書く場合、app/assets/stylesheets/tds にコンポーネントを作成します。
ページ固有のCSSはFLOCSSに従い tds/objects/projects に作成してください。
既存CSSの改修の場合でも、古いコンポーネントを削除しTDSに追加できないか検討してください。

LPなど独立したページの場合はTDSディレクトリ以外(lp, mobile, pc)で新しいエントリーポイントを作り、そこに閉じ込めることも可能です。
※この場合でも開発者全体の理解のしやすさからCSSの命名規則はTDSから外れないことを推奨しますが、外部パートナーに実装いただいたり等あるため必ずではありません。

Typographyなどのfontやアイコンについて

tds/objects/components/_font.scss にまとまっています。
Figmaの情報を参照すると small medium 等のサイズを確認できるのでそれに合わせたコンポーネント(.c-f-medium)を利用してください。
※特殊な場合を除きProjectで再定義する必要はありません。

アイコンはMaterial Iconを使用しています。
※SVGのアイコンを利用している場合もあります
こちらもFigmaで利用しているアイコンの名前がわかるため参照し
Material Iconのコンポーネント .c-f-icon を利用して実装してください。

Material Iconについては公式ドキュメントを参照してください。

プロパティについて

autoprefixerを利用しているためベンダープレフィックスの記載は不要です。

プロパティの順番に定義はありませんがアルファベット順が多いです。

SP版のCSSの書き方

TDSはPC/SP両方で利用されますが、ビューのレイアウト等も別れていることや、古いCSSは完全に別になっている影響でレスポンシブにはなっていません。
そのためPC/SPを判断するために body タグに is-sp というクラスを付与しています。
TDSコンポーネント内でSP版の場合のCSSを書く場合は以下のようにしてください。

_post.scss
.c-post {
    border: 1px solid #f00;
    .is-sp & {
        border: 0;
    }
}

メディアクエリを利用した場合、その箇所だけSP表示でヘッダーのみPCといったことが起こる可能性があることに注意してください。

Mixin, 変数のスコープに注意する

各コンポーネント内でのみ使うmixinや変数はグローバル汚染/上書きをさせないため、以下の方法を使いスコープを閉じてください。

_post.scss
/* _を付けてファイル内プライベートにする */
@mixin _hoge {}
$_var: 123px;

.c-post {
  /* 名前空間内に入れてスコープを閉じる */
  @mixin moge {}
  $nested_var: 123px;
}

JSで操作する要素

JSで操作する要素はスタイルを当てるクラスと違うことが分かりやすいよう js prefixを付けたクラスまたはIDを用意し操作してください。

<div class="js-hoge-item"></div>

<div id="js-hoge-modal"></div>

<script>$(function() { $('.js-hoge-item').on('click', ...)</script>

エントリーポイントを増やし読み込む場合

高速化やLPなどでCSSのエントリーポイントを増やす場合は
app/assets/config/manifest.js に新たに追加するエントリーポイントのファイルを指定
読み込む必要があるViewファイルで stylesheets_file_name でエントリーポイント名を指定してください。

app/views/posts/show.html.erb
<% provide :stylesheets_file_name, 'pc/posts-show' %>

注意点やよくある間違い

Elementをネストしない、ユニークで短い命名をする

可読性を上げるためクラス名を短くします。

以下の様なDOMがあった場合、

<from class="c-form">
    <div class="c-form__row">
        <label>Name</label>

.c-form__row__label といったElementをネストした命名はしない
.c-form__row-label とするより、 row 配下であることを明示する必要が無いので .c-from__labelとすることを推奨します。
記述量が減らせ、CSSファイル内でのネストも減らせるので見通しやすくもなります。

_form.scss
.c-form {
    /* 2段目にelementが揃うように書くと分かりやすい */
    &__row {}
    &__label {
        /* 3段目は疑似要素やmodifiereくらいが入る */
        &::before {}
        &--requiered {}
    }
    /* 特定Element配下のみで使い、他にも使われる名前になる可能性はその限りでない */
    /* ※それでも固有な名前をつけられる方が良い */
    &__heading {
        &-text {}
    }
    &__footer {
        &-text {}
    }
}

詳細度を上げない

CSS崩壊は詳細度の戦争の歴史です。
詳細度を上げる誘惑に勝つことが平和な世界を手に入れる一番の方法です。

原則としてカスケーディングを禁止していますが、公式ドキュメントにもある通り、ProjectレイヤーでComponentレイヤーのモジュールを変更することを許容しています。
ですが、カスケーディングせずともプロパティの上書きが可能な場合が多いです。
.p-hoge > .c-button .p-hoge > a などが出てきたら改善出来ないか確認してみましょう。

原則として、モジュール間のカスケーディング、他のモジュールを親とするセレクタを用いたカスケーディングは禁止とします。
~~中略~~
例外として、レイヤー間におけるカスケーディング、例えば、次のようなProjectレイヤーがComponentレイヤーのモジュールを変更することは許容します。
ただし、このような例であってもセレクタのカスケーディングをおこなう必要はなく、次のようにProjectレイヤーのElementや、ComponentレイヤーのModifierによって拡張することによって解決することができる場合があります。
カスケーディングと詳細度

Extendを利用しない

CSSプリプロセッサの多くが持つ、セレクタを継承するためのExtendは、原則そのモジュールで完結する継承以外では利用を禁止します。
CSSプリプロセッサのExtend

Extendの弊害はこちらの記事が参考になります。

さいごに

現状の運用で複数名での開発にも耐えられていますが、今後更に増えた場合に新しいメンバーのキャッチアップしやすさも考えていきたいポイントです。
また今後の課題で大きいものは以下が考えられます。

  • 古いCSSの一掃
  • レスポンシブ対応

@ツクリンクエンジニア
ざっと書き出したので抜けがあるかもしれません。
長いので冗長な箇所や説明不要な項目がなどあれば教えてください。

@みなさん
意見や改善点を広く募集しています。
詳細聞きたいなどありましたらお気軽にご連絡ください。

採用も行っています。


※この記事はZennからの転載です。