最低限のCSSでフロントエンド開発を乗り切る

# この記事について
CSS書けないしデザインセンスも皆無な自分が個人開発で画面を作る際、どのようにしてそれなりの見た目のものを作ったかについてまとめた記事
読者は↓のような人を想定

- CSSはmarginくらいしか知らない
- デザインなんてやったことない
- Reactの基本的なこと(コンポーネントの作り方など)はわかる

# 戦略
最低限のCSSで見栄えのいい画面を作る戦略の紹介
結論から言うとCSSには出来合いのコンポーネントを使えば書かなくていいものと絶対に逃れられないものの2種類あるので、前者はライブラリを使うことで作業量を削減しようという作戦

## 部品の見た目を決めるCSSと配置を決めるCSS
CSSは大きく分けて2つに分けられる(と思っている)

### 部品の見た目を決めるCSS

例えばこういうおしゃれなボタン
![スクリーンショット 2022-03-17 094059.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/eb9d179a-bd4e-eadf-cb81-1276cde5f0b6.jpeg)

https://material.io/components/buttons#interactive-demo

ボタンの角を丸くしたり影をつけたり色を変えたり...といったことをして実装されている
このようなCSS(borderとかbackground-colorとか)がこれに該当する

### 部品の配置を決めるCSS
Googleのログインフォーム
![スクリーンショット 2022-03-17 095446.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/dd12f467-d035-3222-633a-f707c63db969.jpeg)
ロゴやテキストボックス、ボタンなどの各部品をいい感じに並べている
marginやdisplayなどがこれに該当する

## ライブラリを使ってCSSの記述量を減らそう
上記のCSSのうち`部品の見た目を決めるCSS`に関しては[これらのライブラリ](#コンポーネントライブラリ)を用いることで記述する必要がなくなる
`部品の配置を決めるCSS`に関してはどうあがいても逃れられないので記述する必要はあるが、アーキテクチャを工夫したりライブラリを利用することでCSS初心者でも比較的書きやすくしたり見通しをよくしてメンテナンスしやすくしたりはできる

※(詳細は後述)Material-UIやBootstrapなどのライブラリには部品の配置を決めるためのコンポーネントが用意されているのでさらにCSSの記述量を減らすことが可能

# ライブラリ/フレームワーク紹介
今回は[React](https://ja.reactjs.org/)をベースにし、以下の2種類のデザイン用ライブラリを組み合わせて実装する

- おしゃれにスタイリングされたコンポーネントを提供するライブラリ(コンポーネントライブラリ)
  - `部品の見た目を決めるCSS`はこれに一任する
- ReactのコンポーネントのCSSを書くためのライブラリ(CSSフレームワーク)
  - `部品の配置を決めるCSS`はこれを使って記述する

それぞれいくつか候補をあげていくので適当に見繕ってインストールしてほしい
ちなみに自分が選択したのは[Material-UI](#material-ui)とMaterial-UIのsx props

## コンポーネントライブラリ
ざっと触った感じ機能に大きな差があるわけではないので見た目の好みで決めていいと思う
特にこだわりがなければコミュニティが大きく更新も頻繁なMaterial-UIかBootstrapがよさそう
### [Material-UI](https://mui.com/)
[マテリアルデザイン](https://material.io/design)に沿って実装されたコンポーネントを提供
![スクリーンショット 2022-03-16 213048.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/4752c479-5ca7-8c79-84d3-7f42e21d076d.jpeg)
歴史が長くドキュメントが豊富
### [Bootstrap](https://react-bootstrap.github.io/)
フラットデザインのコンポーネントを提供
![スクリーンショット 2022-03-16 213758.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/cee004a7-1841-5b71-1652-7cb7fc617527.jpeg)
こちらも古株でドキュメントには困らない
### [Semantic-UI](https://react.semantic-ui.com/)
Bootstrapと似た感じのコンポーネント
![スクリーンショット 2022-03-16 214342.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/e010b52c-9225-6074-e384-7f6223a9f8af.jpeg)
上2つと比べるとマイナーでドキュメントも少ない

## CSSフレームワーク
### [styled-components](https://styled-components.com/)
Reactのコンポーネント単位でのスタイリング機能を提供するフレームワーク

```typescript
const Button = styled.button`
  background: ${props => props.primary ? "palevioletred" : "white"};
  color: ${props => props.primary ? "white" : "palevioletred"};
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

render(
  <div>
    <Button>Normal</Button>
    <Button primary>Primary</Button>
  </div>
);
```

https://styled-components.com/docs/basics#adapting-based-on-props

CSSを文字列で表現しているためテンプレートリテラルで変数を埋め込むことも可能
コンポーネント単位なのでどうしても↓2つに比べて記述量が多くなってしまうのが玉にキズ
ただしこちらの記事のようにDOM、スタイリング、依存性注入とコンポーネントを細かく分けて実装する方法だとかなり便利

https://qiita.com/Takepepe/items/41e3e7a2f612d7eb094a

### [Tailwind CSS](https://tailwindcss.com/)
`UtilityFirst`(汎用的なCSSのクラスを作りそれらを組み合わせてスタイリングする手法)なCSSフレームワーク

https://tailwindcss.com/docs/box-shadow

`shadow-sm` `shadow-lg`のようにスタイルの大きさをenum的に表現してくれているのがgood
ちょっと凝ったデザインにしようものならクラス名が肥大化してしまうのが難点

### [MUI.sx props](https://mui.com/system/basics/#the-sx-prop)
単体のフレームワークではなくMaterial-UIのコンポーネントの[sx](https://mui.com/system/styles/api/#makestyles-styles-options-hook)というpropsに直接CSSを渡す方法
CSSをjavascriptのObjectで表現するので何かと融通が利く

若干話は逸れるがMaterial-UIなどには[こちら](#stack)のように部品の配置を決めるためのコンポーネントが用意されているため、それらを活用すればsx propsで記述するCSSの量もグッと減らせる

コード例

```typescript
import * as React from 'react';
import Box from '@mui/material/Box';

export default function BoxSx() {
  return (
    <Box
      sx={{
        width: 300,
        height: 300,
        backgroundColor: 'primary.dark',
        '&:hover': {
          backgroundColor: 'primary.main',
          opacity: [0.9, 0.8, 0.7],
        },
      }}
    />
  );
}
```

https://mui.com/components/box/#the-sx-prop

# 実装方針
ReactのプロジェクトにMaterial-UIをインストールしてコンポーネントを実装する
コンポーネントをどういった粒度で分割していくかについては[Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/)に従って決定する

## Atomic Design
UI設計の方法論の1つ
詳細についてはわかりやすい記事がたくさんあるので各自参照していただきたい

ここではAtomic Designのざっくりとした解説とCSSのコーティング規約(っぽいもの)を各階層ごとに書き連ねていく

### Atoms
Atomic Designの最小単位となる階層
![スクリーンショット 2022-03-19 203944.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/90f0296e-c98b-d5c4-76a0-8e092feafc65.jpeg)
↑のLABEL、INPUT、BUTTONのようにこれ以上細かく分解できないような粒度のコンポーネントをこの階層に実装する

CSSは自身の見た目を決めるもののみ記述するべきで、自身の配置や大きさについては関心を持たないようにするべき

具体的には↓

```css
// OK
.btn {
  text-align: center;
  background-color: blue;
  padding: 8px;
  border-width: 2px;
}

// NG
.btn {
  margin-top: 8px;
  width: 200px;
}
```

### Molecules
`Atoms`を組み合わせて実装されるコンポーネント
![スクリーンショット 2022-03-19 205039.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/e6114147-5e03-3cee-e2ce-d30d2fff423a.jpeg)
これも自身の見た目に関するCSSのみ記述可能

### Organisms
`Atoms`や`Molecules`を組み合わせて実装されるコンポーネント
![スクリーンショット 2022-03-19 205516.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/27a148c2-b037-9b3a-44a2-024ac3c87bc3.jpeg)
これも自身の見た目(ロゴと検索フォームを横並びにするなど、自身の子コンポーネントの配置が主)に関するCSSのみ記述可能

### Templates
`Organisms`を組み合わせてページ全体を構成するコンポーネント
![スクリーンショット 2022-03-19 205730.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/a59dc1dc-a0f7-63b5-c752-b3f039b1a7dd.jpeg)
ただし実際に`Templates`コンポーネント内で`Organisms`コンポーネントを呼び出すわけではなく、ヘッダーはここで動画があそこで...という風にレイアウトを決めているだけ

CSSは↑にある通り`Organisms`コンポーネントをどのように配置するか、サイズはどれくらいかなどを記述していく

### Pages
`Templates`コンポーネントに対して必要な`Organisms`コンポーネントを渡すことでページを表現するためのコンポーネント
![スクリーンショット 2022-03-19 210133.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/f6ddb5b8-5e83-4e1c-61e3-6496a77795db.jpeg)
CSSはこのコンポーネントでは書かない

# 実践
以下のコマンドでプロジェクト作成

```bash
npx create-react-app {プロジェクト名} --template typescript
cd {プロジェクト名}
npm install @mui/material @emotion/react @emotion/styled
```
## ディレクトリ構成

```
src/
  ├ components/ 
  │        ├ atoms/
  │        ├ molecules/
  │        ├ organisms/
  │        ├ templates/
  │        └ pages/
  └ styles/theme.ts
```

`components`以下にAtomicDesignに従ってコンポーネントを実装していく
また、グローバルなスタイル定義は`styles/theme.ts`に書く

## [ThemeProvider](https://mui.com/material-ui/customization/theming/)
Material-UIのコンポーネントのCSSをグローバルに管理できるプロバイダ
以下のようにテーマオブジェクトを作成して`<ThemeProvider />`に渡してやることで、全子コンポーネントのCSSを変更できる

```tsx
const theme = createTheme({
  status: {
    danger: orange[500],
  },
});

<ThemeProvider theme={theme}>
  ...
</ThemeProvider>
```

以下ではThemeProviderでカスタムできる項目のうちよく使いそうなものだけ抜粋して紹介

### [Color](https://mui.com/customization/color/)
コンポーネントの色を変更できる

```ts
const theme = createTheme({
  palette: {
    primary: {
      light: '#0066ff',
      main: '#0044ff',
      dark: '#0022ff',
    },
    secondary: {
      main: '#ff4400',
      // light, darkを省略した場合mainの値に応じて自動で決定される
    },
  },
});
```

カラーコードを自分で作るのが面倒な場合は↓を使うと便利

- [Material-UIのカラーパレット](https://mui.com/customization/color/#color-palette)
- [メインの色から補色を自動で算出してくれるツール](https://material.io/design/color/the-color-system.html#tools-for-picking-colors)

### [Spacing](https://mui.com/customization/spacing/)
marginやpaddingなどの大きさをカスタマイズできる

## コンポーネント実装
今回はよくあるログインページを実装してみる
### Atoms
基本的にMaterial-UIのコンポーネントを少しカスタマイズするだけ

```src/components/atoms/Button.tsx
import * as mui from "@mui/material";

type ButtonProps = {
  label: string;
  type?: "submit" | "button";
  color?: "primary" | "secondary";
  onClick?: () => void;
};

export function Button(props: ButtonProps) {
  return (
    <mui.Button
      variant="contained"
      color={props.color}
      onClick={() => props.onClick && props.onClick()}
      type={props.type}
      fullWidth={true}
    >
      {props.label}
    </mui.Button>
  );
}
```

その他[Typography](https://mui.com/components/typography/)、[TextField](https://mui.com/components/text-fields/)のコンポーネントも作っておく

### Molecules
今回は対象となるコンポーネントがないので省略

### Organisms

※今回の主題はデザインなので関数などは適当

この階層ではAtomsのコンポーネントを呼び出し、それらの配置を決めるコンポーネントを実装する
以下はログインフォームのコンポーネント

```src/components/organisms/LoginForm.tsx
import { Paper, Stack } from "@mui/material";
import { Button } from "../atoms/Button";
import { Textbox } from "../atoms/Textbox";
import { Typography } from "../atoms/Typography";

export function LoginForm() {
  return (
    <Paper
      sx={{
        padding: 4,
      }}
    >
      <Stack spacing={4}>
        <Typography value="Login" variant="h3" />
        <Textbox
          value="test@example.com"
          onChange={() => null}
          label="Email"
          type="email"
        />
        <Textbox
          value="password"
          onChange={() => null}
          label="Password"
          type="password"
        />
        <Button label="LOGIN" color="primary" />
      </Stack>
    </Paper>
  );
}
```

新たに登場した`<Paper />`、`<Stack />`について簡単に補足

#### [Paper](https://mui.com/components/paper/)
画用紙っぽく浮き出た感じに装飾された`<div>`
![スクリーンショット 2022-03-23 213535.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/6f24476f-37d3-c854-e24a-161986957fdc.jpeg)

#### [Stack](https://mui.com/components/stack/)
コンポーネントを1次元に並べるコンポーネント
`direction`をcolumn(デフォルト)にすると垂直方向に、rowにすると水平方向に子コンポーネントを1列に並べる
![スクリーンショット 2022-03-23 214056.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/25f1af28-c144-2ee6-2483-a0d19209a264.jpeg)
他にも2次元に並べるための`<Grid>`や中央揃えするための`<Container>`などが用意されている

ついでにヘッダーのコンポーネントも実装する

```src/components/organisms/Header.tsx
import { AppBar } from "@mui/material";
import { Typography } from "../atoms/Typography";

export function Header() {
  return (
    <AppBar position="relative">
      <Typography value="My App" variant="h1" />
    </AppBar>
  );
}
```

### Templates
Organismsのコンポーネントの配置を決めるためのコンポーネント

```src/components/templates/AuthenticationTemplate.tsx
import { Box, Container } from "@mui/material";

type AuthenticationTemplateProps = {
  header: JSX.Element;
  form: JSX.Element;
};

export function AuthenticationTemplate(props: AuthenticationTemplateProps) {
  return (
    <Box
      sx={{
        width: "100vw",
        height: "100vh",
      }}
    >
      <Box>{props.header}</Box>
      <Container
        sx={{
          marginTop: 10,
        }}
      >
        {props.form}
      </Container>
    </Box>
  );
}
```

### Pages
Templatesに対してOrganismsのコンポーネントを流し込むためのコンポーネント
(今回は省略したものの)各コンポーネントで使用するコールバック関数や引数などもこのコンポーネントで注入する形になる

```src/components/pages/LoginPage.tsx
import { Header } from "../organisms/Header";
import { LoginForm } from "../organisms/LoginForm";
import { AuthenticationTemplate } from "../templates/AuthenticationTemplate";

export function LoginPage() {
  return <AuthenticationTemplate header={<Header />} form={<LoginForm />} />;
}
```

実装完了したら`npm start`で実行

![スクリーンショット 2022-03-23 215104.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2533663/e40d250e-2eaa-bf12-de66-ee7f4fb71fd5.jpeg)