CSSだけでMaterial DesignのText Fieldを作成する
Introduction
皆さんはMaterial DesignのText Field(長いので以下Material Text Fieldと呼びます。)を知っていますか。ご存じない方は,Material Designの公式サイトにデモがあるので触ってみてください(これは古いバージョンですが,Material3だとデモがないみたいなので)。Googleのログインフォームとかに出てくるとても見慣れたUIだと思います。
Placeholderがちょこちょこ動いてうるさいとか,Placeholderの文字が小さくなって読みづらいとか,毛嫌いする人も少なくないUIではありますが,これがあるだけでページがちょっとリッチに見えるので私はこのText Fieldが大好きです。
ReactならMUI,VueならVuetify,AngularならAngular Material,Web ComponentsならMaterial Design Web Componentsによってそれぞれ具体化されており,モダンなフロントエンド環境なら気軽に使うことができます。
とはいえ,どのライブラリもJavaScriptを利用してUIが実装されているため,JavaScriptが無効化されている状況では動かなかったりします(多分)。SSRが台頭してきて,raw CSSへの回帰が始まる今,CSSだけを使ったUIの実装には価値があるはず!
ということで,CSSだけを使ってMaterial Text Fieldの実装を行ってみたいと思います。:has
や:not
といった最新のCSSを利用することでMaterial Text Fieldは案外簡単に実装することができます。
もちろん,CSSだけなので上記のライブラリで用意されているような複雑なロジックではなく,見た目の実装に注力します。
結果だけ見たい方はCode Sandboxにサンドボックスを置いておくので使ってみてください。以下では,このコードの基づいて説明を行います。
解説
主要なファイルはindex.html
,reset.css
,style.css
です。index.html
はMaterial Text Fieldを生成するHTMLで,reset.css
はそのままリセットCSSです。The New CSS Resetを利用しています。
style.css
がスタイルのメインファイルです。このファイルに書かれたコードの解説を行います。
まず,一番外側の親要素である.textfield
についてです。
.textfield {
position: relative;
height: 58px;
width: 100%;
}
この要素を起点に位置調整を行うためにposition: relative;
を設定し,高さと横幅はいい感じにセットします。
次に,.input
と.outline
についてです。.input
は実際に入力を受け付けるinput
要素で,.outline
はText Fieldの外枠を描くためのクラスです。
.textfield .input {
width: 100%;
height: 100%;
padding-left: 12px;
padding-right: 12px;
padding-top: 16px;
}
.textfield .outline {
position: absolute;
inset: 0;
pointer-events: none;
transition: 0.3s;
}
.textfield:not(:has(.input:focus)) .outline {
border: gray solid 1px;
}
.textfield:has(.input:focus) .outline {
border: blue solid 1px;
}
.input
は縦横を.textfield
に合わせて,いい感じにpadding
を付与します。.outline
も同じように縦横を揃えます。.outline
にはわざわざposition: absolute;
をセットしていますが,現時点ではこれは好みの問題です。width: 100%; height: 100%;
でも問題はありません。
そして,.input
がフォーカスされていないとき,すなわち,.textfield
がフォーカスされた.input
要素を持たないとき外枠をgray
にし,フォーカスされているときは外枠をblue
にします。
最後に.placeholder
についてです。これが今回のさわりです。
/* .placeholder共通のスタイル */
.textfield .placeholder {
position: absolute;
pointer-events: none;
left: 12px;
transform-origin: 0;
transition: 0.3s;
}
/* フォーカスされていないとき,あるいは,文字が入力されていないとき */
.textfield:not(:has(.input:focus)) .placeholder,
.textfield:has(.input:placeholder-shown) .placeholder {
top: 50%;
transform: translateY(-50%);
color: gray;
}
/* フォーカスされている,あるいは,文字が入力されているとき */
.textfield:has(.input:focus) .placeholder,
.textfield:not(:has(.input:placeholder-shown)) .placeholder {
top: 2px;
transform: scale(0.8);
}
/* フォーカスされているとき */
.textfield:has(.input:focus) .placeholder {
color: blue;
}
大体のことはコメントを読めばわかると思いますが,一点だけ説明します。Material Text Fieldでは,input
に入力があるときにもplaceholder
を上に移動させますが,これは:placeholder-shown
を利用することで表現できます。:placeholder-shown
で入力がないとき,:not(:placeholder-shown)
で入力があるときと指定できます。便利ですね。
Closing
以上でMaterial Text Fieldの見た目の実装ができるはずです。Material Text Fieldは状態が自分の中で閉じており,クリック位置の取得などもないのでCSSだけでの実装が可能です。これがSelect Menusのように,状態が複数のコンポーネントにまたがり始めると,CSSだけでの実装は不可能になります(余談ですが,最近はSelectlistなんかも実験的に実装されており,Select MenusをCSSだけで作れる日も来るかも?などと思っています)。
:has
や:not
といったモダンなCSSを使うことで複雑なUIもある程度はCSSだけで実装することが可能です。よかったら皆さんも試してみてください。限られて技術の中で頭を捻るのは意外と面白いです。
Discussion