
はじめに
自治会DXプロジェクトのシニア向けアプリで、72歳の友人の祖母(らくらくフォン使用)にユーザーテストをしてもらった日のことだ。
テストで上がったP1課題のひとつが「画面の文字が小さくて読みにくい場面がある」だった。らくらくフォンはシステムフォントサイズを大きく設定できる機種で、通常の16pxから24px、場合によっては32pxまで拡大できる。
これに対応するため、ログイン画面と登録フローの見出しに clamp() を使ったレスポンシブなフォントサイズを導入することにした。ここまでは順調だった。
問題が起きたのは、実装直後のセルフレビューのときだ。
「あれ、clamp が効いていない」
らくらくフォンのシステムフォントを最大に設定した状態でシミュレートしてみると、見出しが上限を無視して際限なく大きくなった。コードを見直して初めて気づいた。clamp() の3値すべてをremで書いていたのが原因だった。
らくらくフォンとシステムフォント拡大の問題
一般的なWeb開発者がフォントサイズを設計するとき、想定するユーザーはシステムフォントを標準サイズ(おおむね16px)のまま使っていることが多い。レスポンシブ対応として clamp() や em、vw を使っても、それは「画面幅の違い」への対応であって、「ユーザーが設定したフォントサイズの違い」への対応ではない。
しかしシニア向けデバイスの現実は違う。らくらくフォンシリーズは「文字を大きくする」機能を売りのひとつにしている端末だ。設定画面からシステムフォントサイズを変更すると、端末上のアプリも、ブラウザで表示されるWebサイトも、その設定に追従してフォントが大きくなる。
Webブラウザの場合、ユーザーのシステムフォント設定は <html> 要素の基準フォントサイズとして反映される。標準設定では1rem = 16pxだが、端末のフォントサイズを最大に設定すると1rem = 24pxや1rem = 32pxになることがある。
ここで問題が起きる。
CSSの rem 単位は「ルートフォントサイズ(Root EM)」に基づく相対単位だ。1rem = ルートフォントサイズ。ルートフォントサイズが2倍になれば、remで指定したすべての値が2倍になる。
最初の実装 — 全部remで書いたclamp
らくらくフォン対応として、5段階のフォントサイズユーティリティクラスをglobals.cssに追加した。
.text-clamp-sm {
font-size: clamp(0.875rem, 1.0625rem, 1.25rem);
}
.text-clamp-base {
font-size: clamp(1rem, 1.25rem, 1.5rem);
}
.text-clamp-lg {
font-size: clamp(1.125rem, 1.5rem, 1.875rem);
}
.text-clamp-xl {
font-size: clamp(1.25rem, 1.625rem, 2rem);
}
.text-clamp-2xl {
font-size: clamp(1.5rem, 1.875rem, 2.25rem);
}
見た目には整然として正しそうに見える。最小値・推奨値・最大値の3値がすべて同じ単位(rem)で書かれていて、間隔も規則的だ。
しかし、これが「何も制限されなかった」コードだった。
何が起きていたか — remのスケーリングとclampの計算
clamp(min, preferred, max) は、CSSがブラウザで計算されるときにピクセル値に変換してから比較する。
標準設定(1rem = 16px)の場合:
clamp(1.5rem, 1.875rem, 2.25rem)
= clamp(24px, 30px, 36px)
→ 推奨値30pxが最小24pxより大きく、最大36pxより小さいので30pxを採用
これは正しく動く。
ではシステムフォントを最大に設定したユーザーの端末(1rem = 24px)の場合はどうか。
clamp(1.5rem, 1.875rem, 2.25rem)
= clamp(36px, 45px, 54px)
→ 推奨値45pxが最小36pxより大きく、最大54pxより小さいので45pxを採用
clampが「働いている」ことは働いている。だが最大値54pxも「制限としては機能していない」。なぜなら推奨値45pxが最大値54pxを超えていないからだ。
さらにフォントサイズを大きくすると(1rem = 32px):
clamp(1.5rem, 1.875rem, 2.25rem)
= clamp(48px, 60px, 72px)
→ 推奨値60pxが最大値72pxを超えていないので60pxを採用
これでも制限されない。全値が同じ比率で拡大されるので、推奨値が最大値を超えることがない。どんなにルートフォントサイズが大きくなっても、3値の相対関係は変わらない。推奨値は常に「最小と最大の中間」だ。
要するに、3値をすべて同じ単位(rem)で書くと、clamp() は「制限する」機能を完全に失う。
clamp()のmax値が全てremだったため、システムフォント拡大時に上限が全く効かない。pxに変更。
— jichikai-dx 開発日記 2026-03-20
L2 — 単位の選択で何を「固定」し何を「追従」させるか
今回の目的は逆で「らくらくフォンのフォント拡大に対して上限を設けたい」という話ですよね。最大値だけpxにする clamp(rem, rem, px) というパターンは、最小と推奨はシステムフォント設定に追従しつつ、上限だけ固定できます。アクセシビリティの意図と矛盾しません。
解決策 — 最大値だけpxにする
修正はシンプルだった。max値の単位だけremからpxに変える。
.text-clamp-sm {
font-size: clamp(0.875rem, 1.0625rem, 20px);
}
.text-clamp-base {
font-size: clamp(1rem, 1.25rem, 24px);
}
.text-clamp-lg {
font-size: clamp(1.125rem, 1.5rem, 30px);
}
.text-clamp-xl {
font-size: clamp(1.25rem, 1.625rem, 32px);
}
.text-clamp-2xl {
font-size: clamp(1.5rem, 1.875rem, 36px);
}
これで最大値はルートフォントサイズに影響されない固定ピクセル値になる。システムフォントが大きくなっても、見出しは最大36pxより大きくはならない。
標準設定(1rem = 16px)のユーザーには変化はない。最小値と推奨値はremなので、小さい画面や小さいフォント設定には柔軟に対応できる。そして大きいフォント設定のユーザーには「拡大するが上限で止まる」という挙動になる。
replace_all で別の問題を引き起こした
セルフレビューで発見したのはremの問題だけではなかった。
clampクラスを各コンポーネントに適用するとき、エディタの「一括置換(replace_all)」を使って text-2xl font-bold を text-clamp-2xl font-bold に一括変換した。効率的に見えた。しかし実際には、想定外の箇所にまで変換が適用されていた。
登録フローには「招待コードの桁入力ボックス」がある。1桁ずつ入力するタイプのUIで、各ボックスは h-14 w-12 で固定サイズを持っていた。そのボックスの中のテキストも text-2xl で指定されていたため、一括置換で text-clamp-2xl に書き換わってしまった。
固定サイズのボックスにclampを適用すると、システムフォントが小さい場合には文字が小さくなりボックス内に余白ができる。大きい場合には文字がボックスからはみ出す可能性がある。どちらにしても、固定サイズの入力ボックスには「可変フォントサイズ」は不要だ。
replace_allで招待コード桁入力(固定サイズh-14 w-12)にまでclampが誤適用。text-2xlに戻す。
— jichikai-dx 開発日記 2026-03-20
一括置換は便利だが、適用範囲を確認しないと「見た目の問題はないのに仕様が変わっている」ケースを作ってしまう。特にフォントサイズの変更は、視覚的に見えにくい場所(固定サイズのコンテナ内)での影響が見落とされやすい。
この経験から、clampを適用すべき場所とそうでない場所を整理した。
clampを適用すべき場所:
- ページタイトル、セクション見出しなど、画面の主要なテキスト
- ラベルやボタンテキストで、表示領域が柔軟なもの
clampを適用すべきでない場所:
- 固定サイズのコンテナ(入力ボックス、バッジ、アイコンラベルなど)
- 1行に収まることが前提のUI(テーブルのセル、コードブロックなど)
単位の組み合わせが設計の核心
今回の問題を経て、clamp() における単位の選択は「どのスケーリングに追従させるか」の設計問題だと理解した。
CSSの単位はそれぞれ「何に比例するか」が違う。
| 単位 | 基準 |
|---|---|
| px | 絶対値(デバイスピクセル) |
| rem | ルートフォントサイズ(ユーザー設定に追従) |
| em | 親要素のフォントサイズ |
| vw / vh | ビューポートの幅と高さ |
| % | 親要素のサイズ |
clamp の3値を「すべて同じ単位」で書くと、それらはすべて同じ比率でスケーリングする。相対関係が変わらないため、minやmaxは「制限」として機能しない。
clamp を制限として機能させるには、min・preferred・max のうち少なくとも1つを「別のスケーリング軸」に置く必要がある。
よく使われるパターンをまとめると:
/* 画面幅に対してレスポンシブ、上限は固定 */
font-size: clamp(1rem, 4vw, 2rem);
/* システムフォント拡大に追従しつつ、上限は固定(今回のケース) */
font-size: clamp(1rem, 1.5rem, 32px);
/* 最小は固定、それ以上はシステムフォントに追従 */
font-size: clamp(16px, 1.5rem, 2rem);
今回の「らくらくフォン対応」では、推奨値(preferred)はremで「通常のシステムフォント設定に追従する」ようにしつつ、最大値だけpxで「らくらくフォンの最大設定でも破綻しない上限を設ける」というパターンを選んだ。
セルフレビューで気づいた価値
このバグを発見したのは、実装直後に「らくらくフォン設定を最大にしたらどうなるか」という視点でセルフレビューをしたからだ。
通常の開発環境では、ブラウザのフォントサイズは標準設定のままだ。開発者の手元では clamp が「正常に動いているように見える」状態で、テストしても問題が出ない。問題が顕在化するのは、システムフォントを大きく設定しているユーザーの端末でのみだ。
シニア向けアプリ開発では、開発者の手元では再現しない問題が多い。「実際のターゲットユーザーの設定環境でテストする」という視点を持つこと、そして実装後に「想定ユーザーの操作環境でシミュレートする」セルフレビューの習慣が、こうした見落としを防ぐ。
今回のユーザーテストで「文字が小さい」という指摘を受けなければ、らくらくフォン対応自体を後回しにしていたかもしれない。実際のユーザーに触ってもらうことの重要性を、コードのバグという形でも再確認した。
まとめ
clamp() で全値をremで書くと、ルートフォントサイズが変わったときに3値が同じ比率で拡大縮小する。推奨値がminとmaxの間に収まり続けるため、制限が一切機能しない。
対策はシンプルで、max値をpx(絶対単位)にするだけでいい。これにより「ユーザーのシステムフォント設定を尊重しつつ、UIが破綻するサイズにはならない」という制限が機能するようになる。
そして一括置換(replace_all)は強力だが、適用範囲の確認が必須だ。フォントサイズの変更は、固定サイズのコンテナ内など視覚的に気づきにくい場所でも影響が出ることがある。
関連記事:
Zeronova(ゼロノバ)
Product Manager / AI-Native Builder
Web/IT業界19年以上・20以上のWebサービスを担当したPdM。東証プライム上場企業の子会社代表として事業経営を経験。現在はAIを駆使して企画から実装まで完結させる個人開発を実践中。