0から始めるFLOCSS


こんにちは！Life is Tech! でunityメンターをしております、**たお**と申します！
この記事は[Life is Tech ! Advent Calendar 2023](https://qiita.com/advent-calendar/2023/life-is-tech) 4日目の記事です。


## はじめに
みなさんはweb制作の際のcss設計について考えたことはありますか？
cssは記法が自由であり、タグやクラス名から好きなように設計することができる魅力的なツールです。
しかし、その自由さゆえにcssが重複してしまったり、`!important`を多用してしまったりと破綻に向かっていくケースも多くあります。
<br>

ここで登場するのがFLOCSS(フロックス)であり、その保守性の高さから注目を集めている記法です。
今回はFLOCSSについて、実際にNext.jsを使用した例も交えて紹介していけたらと思います！


# 目次
**[はじめに](#はじめに)**
**[そもそもFLOCSSとは](#そもそもflocssとは)**
**[FLOCSSのメリット](#flocssのメリット)**
**[プロジェクト設計](#実際のプロジェクト設計)**
- [環境構築](#環境構築)
- **[ディレクトリ構成](#ディレクトリ構成)**
- **[ディレクトリの解説](#ディレクトリの解説)**

**[FLOCSSディレクトリの解説](#flocssディレクトリの解説)**
- [Foundation](#foundation)
- [Layout](#layout)
- [Component](#component)
- [Project](#project)
- [Utility](#utility)

**[まとめ](#まとめ)**

## そもそもFLOCSSとは？
Foundation Layout Object CSSの略です。


```
├─ Foundation
├─ Layout
└─ Object
   ├─ Component
   ├─ Project
   └─ Utility
```
cssを役割ごとに分割することで、崩れにくい構成を実現しています。

### 各要素の説明（[公式ドキュメント](https://github.com/hiloki/flocss)より引用）

<dl>
  <dt>・Foundation</dt>
  <dd>プロジェクトにおける基本的なスタイル</dd>
  <br/>
  <dt>・Layout</dt>
  <dd>プロジェクト共通のコンテナーブロックのスタイル</dd>
  <br/>
  <dt>・Object</dt>
  <dd>プロジェクトにおける基本的なスタイルを定義
  <dl>
  <dt>1. Component</dt>
  <dd>再利用可能な小さな単位のモジュール</dd>
  <br/>
  <dt>2. Project</dt>
  <dd>プロジェクト固有のパターンであり、いくつかのComponentと、それに該当しない要素によって構成されるもの</dd>
  <br/>
  <dt>3. Utility</dt>
  <dd>わずかなスタイルの調整のための便利クラスなど</dd>
  <br/>
  </dd>
  </dl>
</dl>

## class命名法
BEM記法をベースにします。
Blocks-Elements-Modifiersの略で、
<dl>
  <dt>・Block</dt>
  <dd>再利用可能な枠組み</dd>
  <br/>
  <dt>・Element</dt>
  <dd>それだけでは独立できないBlock内の要素</dd>
  <br/>
  <dt>・Modifier</dt>
  <dd>色や大きさなどのスタイル</dd>
</dl>

のような意味づけがされています。
`.Block__Element--modifier`の形で記述し、複数語になる場合はケバブケース(区切り文字としてハイフン`-`を使用)をとります。
（例：`class="p-change-password__input p-change-password__input--hidden"`）

ただし、modifierを多く使用するとクラス名が冗長となり、かえってわかりにくくなるので私はmodifierのみのクラスを用いています。
（例：`class="p-change-password__input -hidden"`）


FLOCSSでは、主にBlockの部分の記法を与えます。
特にLayoutにはl-, Componentにはc-, Projectにはp-, Utilityにはu-を先頭につけてそれぞれのクラスの役割を明示します。

:::note info
FLOCSSにおいてはidやタグは用いず、全てのスタイル対象にクラス名を割り振ります。
:::

# FLOCSSのメリット

- #### チーム開発の際の保守性が高い
細かなルールが設けられているため、複数人で同じプロジェクトに取り組んでも破綻しにくいです。


- #### 再利用しやすい
CSSそのものをコンポーネントごとに分割しているので、再利用しやすいです。
- #### クラス名とその機能が結びつきやすい
命名がその機能に即しているので、class名を見た際にそれが何をするブロックなのかが一目でわかります。



# 実際のプロジェクト設計
Next.js(Typescript) での例を考えます。
（もちろん他のフレームワークでも有効です！）

### 環境構築

まだの方はnode.jsをダウンロードしてください。

https://nodejs.org/en


#### Next.jsのプロジェクト作成

```terminal
npx create-next-app　my-app
```
すると以下のように選択肢が提示されますので、お好みで設定してください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632160/68182245-ceb1-612c-a0fa-785cf4c1c5f7.png">

そして
```terminal
cd my-app
npm run dev
```
とすると http://localhost:3000 にアクセスして以下のように表示されていればOKです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632160/fbe5788d-96a9-c382-b8e6-90a81c190a91.png">

#### 絶対パスの導入
```javascript:next.config.js
const path = require('path');
const nextConfig = {
  reactStrictMode: true,
  webpack(config, options) {
    config.resolve.alias['@'] = path.join(__dirname, 'src')
    return config;
  },
}
module.exports = nextConfig
```

これで、`import`などの際に相対パスで記述する手間が省けます。
例：`@/styles/style.scss`

#### sassの導入
```terminal
npm i -D sass
```


### ディレクトリ構成
私が使用しているものになりますが、ディレクトリ構成の一例を以下に示します。

```
src
├─ app
│  ├─ api/
│  ├─ page1
│  │  ├─ layout.tsx
│  │  └─ page.tsx
│  ├─ page2/
│  ├─ ...
│  ├─ not-found.tsx
│  ├─ layout.tsx
│  └─ page.tsx
├─ components
│  ├─ button.tsx
│  └─ ...
├─ assets
│  ├─ img/
│  └─ favicon.ico
├─ const/
├─ models/
├─ lib/
├─ styles
│  ├─ foundation
│  │  ├─ _base.scss
│  │  ├─ _mixin.scss
│  │  ├─ _variables.scss
│  │  └─ _index.scss
│  ├─ layout
│  │  ├─ _main.scss
│  │  └─ _nav.scss
│  ├─ component
│  │  ├─ _button.scss
│  │  └─ ...
│  ├─ project
│  │  ├─ page1
│  │  │  ├─ ...
│  │  │  └─ _index.scss
│  │  ├─ page2/
│  │  └─ ...
│  ├─ utility
│  │  ├─ align
│  │  ├─ ...
│  │  └─ margin
│  └─ style.scss
└─ types/
```

### ディレクトリの解説
#### app
Next.js 13のリリースで、`app/`ディレクトリが実装されました。
以下のように`next.confignjs`に追記することで使用可能です。
```javascript:next.config.js
const nextConfig = {
    ...
    experimental: {
        appDir: true,
    },
}
```

本題ではないので詳細は割愛しますが、各ディレクトリに`page.tsx`と`layout.tsx`などを配置すると、そのままディレクトリ名をルートとして扱い、また共通のレイアウトの定義も簡単に実装することができます。


#### components

プロジェクト全体で再利用できる最小のReactコンポーネントを格納します。
 例：`button.tsx`, `filter.tsx`, `pagination.tsx`

後述しますがFLOCSSのComponentと同じ単位で扱います。

#### assets

画像やfaviconなどの静的な素材を格納します。

#### const

文字データなど、定数を定義するファイルを格納します。

例：`const.tsx`

```typescript:const.tsx
export const API_BASE_URL = 'https://your_base_url'
```

#### models

各ページなどで使用するデータモデルを定義します。
例：`userDataModel.tsx`

#### lib

ライブラリなどを格納します。
例:`mysql.ts`

#### types

型定義ファイルを格納します。
例:`sample.d.ts`

#### styles

今回の本題であり、スタイルシートを格納するディレクトリです。

記法がDartSassに切り替わったため、`@import`は廃止され`@use`と`@forward`を使用します。
各ディレクトリに_index.scssを配置し、そこに`@forward`でディレクトリ内のscssを読み込ませ、`style/`直下の`style.scss`で全てを読み込む方式を取ります。

- 構成

`object/`ディレクトリは使用せず、`component/`、`project/`、`utility/`をそのまま`styles/`直下に作成します。
```
styles
├─ foundation
├─ ...
├─ project/setting
│          ├─ _setitng.scss
│          ├─ _setting-menu.scss
│          ├─ ...
│          └─ _index.scss
└─ style.scss

```
- index.scssで読み込み

```scss:setting/_index.scss
@forward "setting";
@forward "setting-menu";
//...
```

- style.scssでまとめて読み込み

```scss:style.scss
//foundation
@use "foundation/base";
@use "foundation/variable";
@use "foundation/mixin";

//layout
@use "layout/main";
@use "layout/nav";

//object
//component
@use "component/button";
@use "component/filter";
//...

//project
@use "project/signin";
@use "project/setting";
@use "project/notfound";
//...

//utility
@use "utility/margin";
@use "utility/align";
//...

```

## FLOCSSディレクトリの解説

- ### **foundation/**

プロジェクト全体で共通のスタイルを定義します。
variablesやmixinなどもここで定義します。

resetCSSもここで定義する場合もありますが、私はCDNで読み込んでいます。(メンテナンスに柔軟に対応できるため)
```typescript:app/layout.tsx
import '@/styles/style.scss'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/destyle.css@1.0.15/destyle.css" />
      </head>
      <body>{children}</body>
    </html>
  )
}

```

**variables.scss**
色やフォントサイズなどを定義します。
```scss:_variables.scss
//colors

$white:      #ffffff;
$black:      #1e1e1e;

$gray:       #bababa;
$dark-gray:  #636363;
$pale-gray:  #e1e1e1;
$light-gray: #f7f7f7;

$blue:       #004df2;
$light-blue: #437fff;
$pale-blue:  #eaf1ff;

$yellow:     #f2f200;
$red:        #ff437f;
$green:      #7fff43;


//font size

$fs-title:     2.0rem;
$fs-sub_title: 1.8rem;
$fs-text:      1.4rem;
$fs-input:     1.6rem;
$fs-medium:    1.6rem;
$fs-small:     1.2rem;
$fs-smaller:   1.0rem;
$fs-x-small:   0.8rem;

// break points

$breakpoints: (
    pc:  1280px,
    tb:  960px,
    sp:  560px,
    min: 360px,
    ) !default;
```

**mixin.scss**
mixinを定義します。特にメディアクエリの定義にも使用したりします。
```scss:_mixin.scss
@use 'variable' as *;

@mixin contentBox($width: 200px, $height: 200px, $color: var.$white, $scrollable: false) {
    background-color: $color;
    width: $width;
    height: $height;
    border-radius: 10px;
    box-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
    @if $scrollable {
        overflow-y: scroll;
    }
}

@mixin mq($breakpoint: md) {
    @media screen and (max-width: #{map-get($breakpoints, $breakpoint)}) {
        @content;
    }
}
```
**base.scss**
全体のスタイルを定義します。
```scss:_base.scss
@use 'variable' as *;

html {
    font-size: 62.5%;
}

body {
    background-color: $white;
    color: $black;

    font-family: Avenir, Helvetica;
    display: flex;
    place-items: center;
    box-sizing: border-box;
    text-align: center;

    cursor: default;
}

a {
  color: $blue;
  text-decoration: none;
  cursor: pointer;
}

ul, ol, li {
  list-style: none;
}
```


- ### **layout/**

コンテンツエリアはもちろん、ヘッダー、サイドバーなどのプロジェクト全体で使用するレイアウトに係るスタイルを定義します。唯一idを使用してもよいスタイルシートです。

html側でも`.l-***`とクラスを命名します。

**main.scss**
全体のレイアウトを定義します。
```scss:_main.scss
@use '@/styles/foundation' as var;

.l-main {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: row;
}
.l-nav {
    width: 250px;
    position: sticky;
    left: 0;
    z-index: 2;
    box-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
}
    
.l-content {
    position: relative;
    width: calc(100vw - 250px);
}

.l-overlay {
    z-index: 3;
    position: absolute;
    &.-hidden {
    z-index: 3;
    }
}

.l-animation {
    position: absolute;
    top: 50%;
    left: 50%;
    translate: -50% -50%;
    height: 200px;
    width: 200px;
    background: none;
    z-index: 3;
}

```
その他、`_header.scss`や`_footer.scss`など適宜スタイリングします。

- ### **component/**

再利用可能な最小単位のコンポーネントのスタイリングです。
`src/component`ディレクトリ内のReactコンポーネントと1対1対応します。
最小単位なのでもちろんネストしてはいけません。

html側では`.c-***`とクラスを命名します。


**button.tsx**
```typescript:button.tsx
import { MouseEventHandler } from "react"

interface Props {
    addClass?: string,
    onClick?: MouseEventHandler,
    label?: string,
}

export const Button = (props: Props) => {
    return (
        <button
            className={`c-button ${props.addClass}`}
            onClick={props.onClick}
        >
            {props.label}
        </button>
    )
}
```

**button.scss**
```scss:_button.scss
@use '@/styles/foundation' as *;

.c-button {
    height: 30px;
    width: 150px;
    border: 1px solid $gray;
    color: $black;
    background-color: $white;
    border-radius: 5px;
    box-shadow: none;
    font-size: $fs-text;
    text-align: center;
    cursor: pointer;

    &.-large{
        height: 40px;
        width: 200px;
    }
}

```

このようにmodifierなどを用いてスタイルの種類を増やします。
そのコンポーネント以外でも同じように使用できるスタイル(marginやalignなど)を変更したい場合はutilityで調整します。


- ### **project/**

いくつかのコンポーネントの集合で、使い回すこともできる大きいブロックを指します。ページごとにディレクトリを作成し、そのページ固有のブロックを定義していきます。

html側では`.p-***`とクラスを命名します。

**構成**

```
project
├─ page1
├─ ...
├─ setting
│  ├─ _setitng.scss
│  ├─ _setting-menu.scss
│  ├─ ...
│  └─ _index.scss
└─ style.scss

```

**setting/page.tsx**
```typescript:setting.tsx
'use client'
import Image from 'next/image'
import { useRouter } from 'next/navigation'
import { BackButton } from 'src/components/backbutton'

export const Setting = () => {
  const router = useRouter()
  return (
    <div className="p-setting">
      <BackButton addClass="p-setting__back-button" />
      <div className="p-setting__title">設定</div>
      <div className="p-setting__main">
        <div className="p-setting-menu">
          <div
            className="p-setting-menu__item"
            onClick={() => {
              router.push("account")
            }}
          >
            <Image
              className="p-setting-menu__item-img"
              src="asset/img/account.png"
              alt="アカウント"
            />
            <div className="p-setting-menu__item-label">アカウント</div>
            <div className="p-setting-menu__item-explanation">パスワードの変更などができます</div>
          </div>
          <div
            className="p-setting-menu__item"
            onClick={() => {
              router.push("notification")
            }}
          >
            <Image className="p-setting-menu__item-img" src="asset/img/notify.png" alt="通知" />
            <div className="p-setting-menu__item-label">通知</div>
            <div className="p-setting-menu__item-explanation">通知内容や頻度を変更できます</div>
          </div>
        </div>
      </div>
    </div>
  )
}

```

設定画面内に`p-setting`、`p-setting-menu`、`p-setting-content`の3つのprojectが存在しています。
それぞれについて`project/`内にscssを作成してスタイリングしていきます。

**setting.scss**

```scss:_setting.scss
@use "@/styles/foundation" as *;

.p-setting{
    @include contentBox(100%, 100%, $white);
    position: relative;
    padding: 30px 0px;

    &__title{
        font-size: $fs-title;
        font-weight: bold;
        text-align: left;
        padding-left: 30px;
    }

    &__main{
        padding: 0px 30px;
    }

    &__back-button{
        position: absolute;
        top: 30px;
        left:30px;
    }
}
```
他のprojectについても同様にスタイリングしていきます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632160/762cad4d-8e34-8408-ea1c-f0882561e2dd.png" width=500px>

簡単ではありますがこんな感じの見た目になりました。


- ### **utility/**

componentに書いてはいけない、またprojectでも書くべきでないようなmarginなどのスタイルについて`!important`を使用して調整します。

html側では`.u-***`とクラスを命名します。

**margin.scss**
各方向のmargin調整用です。

```scss:_margin.scss
@use "@/styles/foundation" as *;

@each $space in $spaces {
  .u-m#{ $space } {
    margin: #{ $space }px !important;
  }
  .u-mb#{ $space } {
    margin-bottom: #{ $space }px !important;
  }
  //...
}
```

**color.scss**
色の調整用です。
```scss:_color.scss
@use "@/styles/foundation" as *;

.u-bg-blue {
    background-color: $blue !important;
}

.u-blue {
    color: $blue !important;
}

//...
```
<br>

**使用例**

```typescript:setting/page.tsx
<Button addClass="p-signin__submit-button u-mb16" />
```

ButtonはReactコンポーネントですが、button.scssにはmarginなどについて書いてはいけません。そのため`u-mb16` (`margin-bottom: 16px`)を与えることでスタイルを与えられます。



# まとめ

長くなってしまいましたが、FLOCSSについての理解は深まりましたでしょうか？
チーム開発などにおいて、コードの保守性は開発の効率を上げる重要なポイントになります。
ルールが難しかったり命名が大変だったりと慣れるのに時間はかかるかもしれませんが、class名からその機能の推測も簡単になりますし、拡張性も非常に高いので導入するメリットは大いにあると思います。

<br>
今後のweb開発において、綺麗で引き継ぎやすいプロジェクトづくりを目指す際に参考にしていただけると幸いです！