🤖

CSSだけでMaterial DesignのText Fieldを作成する

2023/12/28に公開

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.htmlreset.cssstyle.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