コメント欄に長文を書いたら、狭い入力欄の中でスクロールすることになった——そんな経験はないでしょうか。<textarea> や <input> は、中身がどれだけ増えても大きさが変わらないのが既定の動作です。これを「中身に合わせて伸ばす」ために、これまでは JavaScript を書くのが定番でした。
CSS の field-sizing プロパティを使うと、この自動リサイズが field-sizing: content; の 1 行で実現できます。この記事では基本の使い方から、伸びすぎを防ぐ max-height との組み合わせ、input や select での挙動の違い、そして対応していないブラウザーへの備え方までを解説します。
目次
これまでは JavaScript で高さを計算していた
まず、field-sizing が無かったころのやり方を見ておきます。textarea を中身に合わせて伸ばすには、入力のたびに高さをいったん auto に戻し、中身の高さを表す scrollHeight を読んで、その値を高さとして設定し直す、という手順が必要でした。
const textarea = document.querySelector('.comment');
textarea.addEventListener('input', () => {
// いったん高さをリセットしないと、縮むときに追従しない
textarea.style.height = 'auto';
// 中身の高さ(scrollHeight)をそのまま高さにする
textarea.style.height = `${textarea.scrollHeight}px`;
});
動きはしますが、面倒な点がいくつもあります。初期表示のときにも一度実行しておかないと最初の内容に合いませんし、フォームをリセットしたときや、JavaScript から値を書き換えたときは input イベントが発生しないので手動で呼び直す必要があります。入力欄が増えるたびにイベントを登録する手間もかかります。
field-sizing は、この一連の処理をブラウザー側の仕事にしてしまうプロパティです。
field-sizing の基本の使い方
指定できる値は 2 つだけです。
| 値 | 意味 |
|---|---|
fixed | 既定値。要素に固定のサイズを設定する。中身が増えても大きさは変わらない |
content | 中身に合わせて要素のサイズを調整できるようにする |
そもそも <input> や <textarea>、<select> といったフォームコントロールは、既定の推奨サイズを持っています。サイズを何も指定しなくてもそれなりの幅で表示されるのは、この推奨サイズが働いているからです。field-sizing: content は、その推奨サイズを「中身に合わせる」という基準に置き換えるプロパティ、と考えると分かりやすいです。
/* これだけで中身に合わせて伸縮するようになる */
textarea {
field-sizing: content;
}
実際の動きを見てみましょう。下のデモでは、上 2 つの入力欄に field-sizing: content を指定し、一番下の textarea は既定の fixed のままにしてあります。文字を入力したり改行したりして比べてみてください。
<form class="demo">
<label class="field">
<span class="field__label">ニックネーム(field-sizing: content)</span>
<input type="text" class="auto" placeholder="入力すると横に伸びます">
</label>
<label class="field">
<span class="field__label">ひとこと(field-sizing: content)</span>
<textarea class="auto" placeholder="改行すると縦に伸びます"></textarea>
</label>
<label class="field">
<span class="field__label">ひとこと(既定の fixed)</span>
<textarea placeholder="こちらは高さが変わりません"></textarea>
</label>
</form>
/* これだけで入力欄が中身に合わせて伸縮する */
.auto {
field-sizing: content;
}
/* 空のときに細くなりすぎないよう下限を決めておく */
.auto[type="text"] {
min-width: 10em;
}
/* ここから下は見た目を整えるだけの指定 */
.demo {
display: grid;
gap: 16px;
align-items: start;
padding: 24px;
font-family: system-ui, sans-serif;
}
.field {
display: grid;
gap: 6px;
justify-items: start;
}
.field__label {
font-size: 13px;
color: #555;
}
input,
textarea {
padding: 8px 10px;
border: 1px solid #bbb;
border-radius: 6px;
font: inherit;
line-height: 1.6;
}
input のほうは横に伸び、textarea のほうは改行に合わせて縦に伸びます。JavaScript は 1 行も書いていません。
デモの CSS では、テキスト入力に min-width: 10em を添えています。これは field-sizing: content を指定したテキスト系の input が、最小幅を指定していないとテキストカーソルと同じ幅まで縮むためです。プレースホルダーがあるときはそのテキストが収まる大きさになりますが、プレースホルダーを使わない入力欄では下限を決めておくのが安全です。
伸びすぎを防ぐには min / max を組み合わせる
field-sizing: content を指定した要素は、中身が増え続ければどこまでも伸びていきます。長文を貼り付けられたときにページ全体が崩れないよう、実務では上限と下限をセットで指定します。ここで使うのが min-width / max-width / min-height / max-height です。
大事なのは、width や height で固定のサイズを指定しないことです。固定サイズを指定してしまうと、そちらが優先されて中身に合わせた伸縮が起きなくなります。MDN でも、field-sizing: content と固定サイズを併用しないよう案内されています。あくまで下限と上限だけを決めて、その範囲内でブラウザーに伸縮させる、という考え方です。
下のデモは、コメント入力欄によくある形です。最低 3 行ぶんの高さを確保しつつ、max-height: 10em を超えたらそれ以上は伸びずにスクロールへ切り替わります。文章を足したり消したりして、上限に達したときの挙動を確かめてみてください。
<div class="comment-box">
<label class="comment-box__label" for="body">コメント</label>
<textarea id="body" class="comment-box__input" placeholder="コメントを入力してください">field-sizing: content と max-height を組み合わせると、
入力した行数だけ高さが伸びて、
上限に達したあとはスクロールに切り替わります。
この行まで入力してみてください。</textarea>
<button type="button" class="comment-box__submit">送信する</button>
</div>
.comment-box__input {
/* 中身に合わせて伸縮させる */
field-sizing: content;
/* 空のときに潰れないよう最低 3 行ぶんを確保 */
min-height: 4.8em;
/* 伸びすぎ防止。ここを超えたらスクロールに切り替わる */
max-height: 10em;
/* 横幅は伸ばさず固定して、縦だけ伸ばす */
width: 100%;
/* 手動リサイズのつまみは不要になるので消す */
resize: none;
}
/* ここから下は見た目を整えるだけの指定 */
.comment-box {
display: grid;
gap: 8px;
justify-items: start;
max-width: 420px;
padding: 24px;
font-family: system-ui, sans-serif;
}
.comment-box__label {
font-size: 13px;
color: #555;
}
.comment-box__input {
box-sizing: border-box;
padding: 10px 12px;
border: 1px solid #bbb;
border-radius: 6px;
font: inherit;
line-height: 1.6;
}
.comment-box__submit {
padding: 8px 20px;
border: none;
border-radius: 6px;
background: #1a73e8;
color: #fff;
font: inherit;
cursor: pointer;
}
高さが上限に達したあとは、textarea がもともと持っているスクロールの挙動に戻るだけなので、特別な指定は要りません。横方向も考え方は同じで、input なら min-width と max-width で挟みます。
size 属性や rows・cols 属性は効かなくなる
HTML 側でサイズを決める size 属性(input)や rows / cols 属性(textarea)は、既定の推奨サイズを決めるための属性です。field-sizing: content はその推奨サイズの決め方を上書きするので、これらの属性は効果を持たなくなります。
「rows="5" を書いているのに 1 行分の高さで表示される」という場合は、この仕様が原因です。高さの調整は rows ではなく min-height で行ってください。
.comment {
field-sizing: content;
/* rows 属性の代わりに min-height で下限を決める
(line-height が 1.6 なら 3 行ぶんは 4.8em) */
min-height: 4.8em;
max-height: 12em;
}
maxlength を付けていれば、そこで伸びが止まる
入力欄に maxlength 属性で文字数の上限を付けている場合、その上限まで入力された時点で文字が増えなくなるため、サイズの拡大もそこで止まります。もともと短い入力を想定している欄なら、max-width を書かなくても実質的な上限が決まる、というわけです。
input と select ではどう変わるか
field-sizing は textarea 専用ではありません。要素の種類ごとに「中身」として扱われるものが違うので、代表的なものを押さえておきましょう。
テキスト系の input とプレースホルダー
text / search / url / tel / email / password / number 型の input は、入力された値に合わせて幅が変わります。空のときは、placeholder 属性があればそのテキストが表示できる大きさになり、無ければ(最小幅を指定していない限り)テキストカーソルと同じ幅まで縮みます。
つまりプレースホルダーは、field-sizing: content と組み合わせたときには「初期サイズを決めるもの」としても働きます。タグの検索欄のように入力前後で幅が変わってよい場所なら、プレースホルダーだけで十分見栄えが整います。
file 型はファイル名に合わせて変わる
file 型の input にも効きます。ファイルが選ばれるとコントロールにファイル名が表示されるので、その名前の長さに合わせて幅が変化します。長いファイル名でボタンの外にはみ出す、といったことが起きないよう、こちらも max-width を添えておくと安心です。
select は選択中の選択肢に合わせて縮む
通常のドロップダウン形式の <select> は、既定では一番長い選択肢に合わせた幅で表示され、短い選択肢を選んでいるときは右側が余ります。field-sizing: content を指定すると、いま表示されている選択肢の幅に合わせて変化するようになります。
一方、multiple 属性や size 属性を付けたリストボックス形式の <select> では意味が変わり、スクロールせずにすべての選択肢を表示できるサイズになります。選択肢の数が多いときは縦に長くなるので、使いどころは選んでください。
/* 選んだ項目の長さに合わせて幅が縮むドロップダウン */
select {
field-sizing: content;
min-width: 6em; /* 短い選択肢でも小さくなりすぎないように */
max-width: 20em;
}
対応していないブラウザーがあるときの書き方
field-sizing は比較的新しいプロパティで、Chromium 系のブラウザーで先行して実装されました。執筆時点では対応していない環境も残っているため、対応していなくても困らない書き方にしておくのが基本になります。
幸い、CSS は解釈できないプロパティを無視するだけなので、field-sizing を足したこと自体で表示が壊れることはありません。非対応の環境では単に「伸び縮みしない、いつもどおりの入力欄」になります。これはプログレッシブエンハンスメント(対応環境でだけ体験がよくなる作り方)として素直に成立するので、多くの場面ではこれで十分です。
ただし、rows 属性が効かなくなる点との兼ね合いには注意が必要です。rows を消して min-height に置き換えると、非対応の環境でも min-height が効くので問題ありませんが、初期の高さを厳密に分けたい場合は @supports で切り分けます。
/* 非対応の環境向け。固定の高さでスクロールさせる */
.comment {
height: 8em;
}
/* 対応している環境だけ、自動リサイズに切り替える */
@supports (field-sizing: content) {
.comment {
field-sizing: content;
height: auto; /* 固定の高さを打ち消す */
min-height: 4.8em;
max-height: 12em;
}
}
@supports (field-sizing: content) は「このプロパティと値の組み合わせを解釈できるか」をブラウザーに尋ねる書き方です。解釈できる環境でだけ中のスタイルが適用されるので、固定の高さを height: auto で打ち消してから自動リサイズの設定を書きます。
なお、JavaScript から判定したいときは CSS.supports('field-sizing', 'content') が使えます。非対応のときだけ前述の scrollHeight による自動リサイズを読み込む、といった切り替えに使えます。
まとめ
field-sizing: content は、input や textarea、select の大きさを中身に合わせて伸縮させる CSS プロパティです。既定値は fixed で、こちらは中身が増えても大きさが変わりません。これまで scrollHeight を使った JavaScript で実装していた textarea の自動リサイズが、CSS の 1 行で置き換えられます。
実際に使うときは、width や height で固定サイズを指定せず、min-height と max-height(横方向なら min-width と max-width)で下限と上限だけを決めるのがコツです。size や rows / cols 属性は効かなくなるので、サイズの指定は CSS 側に寄せてください。対応していない環境では単に伸縮しないだけなので、そのまま足してしまうか、初期の高さを分けたいときだけ @supports (field-sizing: content) で切り分けるとよいでしょう。