MUI (Material UI) の TextField のスタイルを整える ―― 例のﾁｮｺﾁｮｺ動くラベルを止める

Material UI を使った開発で困ることと言えば、TextField の見た目ですよね？

![Material UI 公式サイトの TextField のスクリーンショット](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/9376b9eb-21fe-42e1-cf5a-5fef88c5af18.png)　　
([Material UI 公式サイト](https://mui.com/material-ui/react-text-field/) のスクリーンショット)


Material Design のガイドラインには準拠しているようですが、左上に縮こまったラベルはサイズが小さすぎて、日本語環境においては不便極まりなく、一般的にもあまりウケがよくないデザインだと思います。

**動かない、読めるサイズのラベル・普通の枠線付きの入力エリア**、これらが普通に縦に並んでいる入力フィールドが欲しいですよね？

Material UI のテーマ機能を使えば、TextField の各パーツの見た目をカスタマイズして、ラベルが動かない普通の見た目にできます。

これで MUI Base や Joy UI をプロジェクトに導入するまでの間をしのぎましょう。

:::note info

実は、現在 Alpha 版の [MUI Base (@mui/base) を使えば](https://mui.com/base/react-form-control/)、スタイル抜きの入力フォームを使って見た目を自由自在にカスタマイズすることが出来ます。

また、現在開発が進められている [Material UI の姉妹ライブラリである Joy UI (@mui/joy)](https://mui.com/joy-ui/getting-started/overview/) には、[ズバリ、マトモな見た目の TextField](https://mui.com/joy-ui/react-text-field/) が含まれています。

![Joy UI の TextField のデモンストレーションのスクリーンショット](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/f6b61752-aa5e-86b8-c70c-7053c92d4523.png)
([Joy UI 公式](https://mui.com/joy-ui/react-text-field/) のスクリーンショット)

:::

## 使用ライブラリとバージョン

| ライブラリ | バージョン | 
|:- | -: |
| **@mui/material** | **5.10.2** |
| @emotion/react    |  11.10.0   |
| @emotion/styled   |  11.10.0   |


## カスタマイズの方法

今回は MUI のテーマ機能を使って、TextField のスタイルを直接上書きしてしまいます。

▼ この記事の3番の方法になります。

https://mui.com/material-ui/customization/how-to-customize/

他にも、TextField をラップしたコンポーネントを自作してそちらを使う方法もありますが、forwardRef, className, sx, controlled/uncontrolled の管理などを怠ると、各利用箇所(例：横幅を調整する、refから操作する)で問題が出て、対処が後手に回ることになると思うので、この方法を取りました。 

（あと、フォーカスすると青くなったり、エラーだと赤くなったりする挙動は継承したい、要はラクしたいというのもあります。）

（同じ理由で、'@mui/material/styles' から利用可能な `styled()` も良い選択肢だと思います。）

## まずは結論から

![まともなスタイルが当てられたフィールド](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/54d2610a-da39-739d-6818-c809f1260c4c.png)


まずは、

```tsx
<TextField
  name="taskName"
  label="タスク名"
  sx={{ display: "flex", maxWidth: 360 }}
  helperText="ああああ"
  margin="normal"
/>
```
と記述したときに、上図のようなスタイルが得られるテーマの記述を挙げて、その後ろに解説を続けます。

:::note warn
デフォルトで `display: inline-flex` で、そこは上書きしていないので、フィールドを縦に並べたい場合には `flex` に変えましょう。
:::

---

:::note warn
`createTheme` は、同名の関数が他のパッケージにもあるので、 "@mui/material" からインポートするように要注意！！
:::

```tsx
import { createTheme } from "@mui/material";

const theme = createTheme({
  components: {
    // TextField 関連のコンポーネントのスタイルを調整する
    MuiInputLabel: {
      styleOverrides: {
        formControl: {
          // 移動をクリック時に動かないように固定
          position: "static",
          transform: "none",
          transition: "none",
          // クリックを可能に
          pointerEvents: "auto",
          cursor: "pointer",
          // 幅いっぱいを解除
          display: "inline",
          alignSelf: "start",
          // タイポグラフィを指定
          fontWeight: "bold",
          fontSize: "0.75rem",
          // テーマの Composition を使えば以下のようにも書ける
          // base.typography.subtitle2
        },
      },
    },
    MuiOutlinedInput: {
      styleOverrides: {
        root: {
          // デフォルトだと、
          // ラベルをはみ出させるための小さなmarginがある
          marginTop: 0,
        },
        input: {
          paddingTop: "10px",
          paddingBottom: "8px",
          height: "auto",
        },
        notchedOutline: {
          // デフォルトだと、 position が absolute、
          // ラベルをはみ出させるため上に少しの余白がある
          top: 0,
          "legend" : {
            // 内包された legend 要素によって、四角の左側の切り欠きが実現されているので、
            // 表示されないように。
            // (SCSS と同様にネスト記述が可能です。)
            display: "none",
          },
        },
      },
    },
    MuiFormHelperText: {
      styleOverrides: {
        root: {
          // フォーム下部のテキスト、エラーメッセージ
          // お好みで左余白を無くしています。
          marginLeft: 0,
        },
      },
    },
  },
});
```

コメントアウトされている、`typography.subtitle2` という形の指定には「テーマの Composition」という手法を用います。 [公式ドキュメント](https://mui.com/material-ui/customization/theming/#createtheme-options-args-theme) の "**Theme composition: using theme options to define other options**" の項目に詳しく書かれています。

## テーマ機能によるコンポーネントのカスタマイズ

https://mui.com/material-ui/customization/theme-components/

テーマ機能を使ったコンポーネントのカスタマイズは、
`components > Mui(コンポーネント名) > styleOverrides > (CSS ルール) > {スタイルの記述}`
のような階層構造で記述します。


```tsx
const theme = createTheme({
  components: {             // コンポーネントたちのスタイルを上書きする
    MuiOutlinedInput: {     // OutlinedInput コンポーネント
      defaultProps: {       // Prop のデフォルト値の上書き
        type: "text"
      },
      styleOverrides: {     // CSS によるスタイルの上書き
        root: {             // CSSルール "root" が対象
          marginTop: 0,
        },
        input: {            // CSSルール "input" が対象
```
### InputLabel, OutlinedInput, FormHelperText って何？

> 「なんで TextField じゃないの？ InputLabel, OutlinedInput, FormHelperText って何？」

とお思いと思います。

まず、 `TextField` インプットは、それ自体に何らかのスタイルが当てられているコンポーネントではありません。 
次の複数の組み合わされたコンポーネントによって構成された **Compound Component (複合的コンポーネント)** です。

- FormControl
  - InputLabel
  - OutlinedInput (`variant="outlined"` の場合)
  - FormHelperText

ドキュメントの下記の章に、それらのコンポーネントについて軽く説明されています。

https://mui.com/material-ui/react-text-field/#components



### CSS ルールについて

ところで、それぞれのコンポーネントについて、`root`, `notchedInput` のような「CSSルール」に対してスタイルが当てられているのですが、この「CSSルール」とは何なのでしょうか？

MUI のドキュメントには、Component のドキュメントと、APIのドキュメントとがあり、 Component のドキュメントの末尾には **API** という章が立てられて、複数のコンポーネントのAPIへのリンクが書かれています。

例： [`TextField` コンポーネントの "API" 章](https://mui.com/material-ui/react-text-field/#api)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/c88e913b-00e4-b169-4968-962533becd55.png)

そして、それぞれのAPIページにある "CSS" の章を参照してみましょう。
例： [OutlinedInput の "CSS" の章](https://mui.com/material-ui/api/outlined-input/#css)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/3e4f9934-f4b4-7a6b-1106-23e26636cb5b.png)

"Rule name" のカラムに書かれているのが、今回の「テーマによるカスタマイズ」に使われている CSSルール名になります。
(それぞれの説明はDescription に書かれている英文を見れば何となく分かりますよね？)

### DevTools を見ながら実際のスタイルを編集する

**最後は作業です。根性です。**

DevTools を使って実際の要素に当てられたスタイルを確認しつつ、テーマの CSS を上書きしていきましょう。
スタイルの確認には実際の class 名が必要ですが、これには CSS の表の Global class のカラムの文字列が相当します。