Material UI を使った開発で困ることと言えば、TextField の見た目ですよね?
(Material UI 公式サイト のスクリーンショット)
Material Design のガイドラインには準拠しているようですが、左上に縮こまったラベルはサイズが小さすぎて、日本語環境においては不便極まりなく、一般的にもあまりウケがよくないデザインだと思います。
動かない、読めるサイズのラベル・普通の枠線付きの入力エリア、これらが普通に縦に並んでいる入力フィールドが欲しいですよね?
Material UI のテーマ機能を使えば、TextField の各パーツの見た目をカスタマイズして、ラベルが動かない普通の見た目にできます。
これで MUI Base や Joy UI をプロジェクトに導入するまでの間をしのぎましょう。
実は、現在 Alpha 版の MUI Base (@mui/base) を使えば、スタイル抜きの入力フォームを使って見た目を自由自在にカスタマイズすることが出来ます。
また、現在開発が進められている Material UI の姉妹ライブラリである Joy UI (@mui/joy) には、ズバリ、マトモな見た目の TextField が含まれています。
(Joy UI 公式 のスクリーンショット)
使用ライブラリとバージョン
ライブラリ | バージョン |
---|---|
@mui/material | 5.10.2 |
@emotion/react | 11.10.0 |
@emotion/styled | 11.10.0 |
カスタマイズの方法
今回は MUI のテーマ機能を使って、TextField のスタイルを直接上書きしてしまいます。
▼ この記事の3番の方法になります。
他にも、TextField をラップしたコンポーネントを自作してそちらを使う方法もありますが、forwardRef, className, sx, controlled/uncontrolled の管理などを怠ると、各利用箇所(例:横幅を調整する、refから操作する)で問題が出て、対処が後手に回ることになると思うので、この方法を取りました。
(あと、フォーカスすると青くなったり、エラーだと赤くなったりする挙動は継承したい、要はラクしたいというのもあります。)
(同じ理由で、'@mui/material/styles' から利用可能な styled()
も良い選択肢だと思います。)
まずは結論から
まずは、
<TextField
name="taskName"
label="タスク名"
sx={{ display: "flex", maxWidth: 360 }}
helperText="ああああ"
margin="normal"
/>
と記述したときに、上図のようなスタイルが得られるテーマの記述を挙げて、その後ろに解説を続けます。
デフォルトで display: inline-flex
で、そこは上書きしていないので、フィールドを縦に並べたい場合には flex
に変えましょう。
createTheme
は、同名の関数が他のパッケージにもあるので、 "@mui/material" からインポートするように要注意!!
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」という手法を用います。 公式ドキュメント の "Theme composition: using theme options to define other options" の項目に詳しく書かれています。
テーマ機能によるコンポーネントのカスタマイズ
テーマ機能を使ったコンポーネントのカスタマイズは、
components > Mui(コンポーネント名) > styleOverrides > (CSS ルール) > {スタイルの記述}
のような階層構造で記述します。
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
ドキュメントの下記の章に、それらのコンポーネントについて軽く説明されています。
CSS ルールについて
ところで、それぞれのコンポーネントについて、root
, notchedInput
のような「CSSルール」に対してスタイルが当てられているのですが、この「CSSルール」とは何なのでしょうか?
MUI のドキュメントには、Component のドキュメントと、APIのドキュメントとがあり、 Component のドキュメントの末尾には API という章が立てられて、複数のコンポーネントのAPIへのリンクが書かれています。
そして、それぞれのAPIページにある "CSS" の章を参照してみましょう。
例: OutlinedInput の "CSS" の章
"Rule name" のカラムに書かれているのが、今回の「テーマによるカスタマイズ」に使われている CSSルール名になります。
(それぞれの説明はDescription に書かれている英文を見れば何となく分かりますよね?)
DevTools を見ながら実際のスタイルを編集する
最後は作業です。根性です。
DevTools を使って実際の要素に当てられたスタイルを確認しつつ、テーマの CSS を上書きしていきましょう。
スタイルの確認には実際の class 名が必要ですが、これには CSS の表の Global class のカラムの文字列が相当します。