Skip to content

Instantly share code, notes, and snippets.

@Takazudo
Created September 13, 2026 08:55
Show Gist options
  • Select an option

  • Save Takazudo/4564d7bd6c936f7728cd186afba7b2b5 to your computer and use it in GitHub Desktop.

Select an option

Save Takazudo/4564d7bd6c936f7728cd186afba7b2b5 to your computer and use it in GitHub Desktop.
px-vs-rem
name px-vs-rem
description CSSの寸法に px / rem / em / 単位なし のどれを使うか判断するためのガイド。font-size、スペーシングスケール、padding、border、box-shadow、border-radius、タッチターゲット、行長、メディアクエリ/コンテナクエリのブレークポイントの単位を決めるとき、CSSを書く・レビューするとき、デザイントークンやTailwindの--spacingを設計するとき、html { font-size: 62.5% } や html { font-size: 16px } を見かけた/検討しているときに必ず使用する。「pxでいいのでは」「全部remにする」「IEがないのでremは不要」といった議論、ブラウザのデフォルトフォントサイズ設定とページズームの違い、WCAG 1.4.4/1.4.10 の文字サイズ変更対応について聞かれた場合も対象。

px vs rem

Internet Explorer 亡き後も rem が意味を持つ理由 — ブラウザのデフォルトフォントサイズ設定、それに追従すべき寸法、そして px が正しい単位となる場面。

使い方

  • CSSを書くとき: 各値について下の「判断の問い」を当て、「クイックリファレンス」で単位を決める
  • CSSをレビューするとき: 「AIがよくやるミス」の各項目をチェックリストとして既存コードを走査する(html/body の font-size、0.0625rem のような端数border、px のメディアクエリ、ネストした em の font-size など)
  • 新規プロジェクトでは「プロジェクト開始時のセットアップ」をベースにトークンを定義する

問題

rem を使う古典的な根拠は Internet Explorer だった。IE は px で指定されたテキストを拡大できなかったため、ユーザーが文字サイズを変えるには相対単位しか手段がなかった。IE はもうない。現行ブラウザはすべて pxrem を同じようにページズームする。ここからよくある結論が導かれる —「もう px でいい。1rem より 16px のほうが直感的だ」。

この結論は、いまも存在するひとつの仕組みを見落としている。ブラウザのデフォルトフォントサイズ設定である。Chrome、Edge、Firefox では、サイトごとのズームとは別に、ルートフォントサイズを 20px、24px、32px などに永続的な設定として変更できる。rem のテキストはこの設定に追従し、px のテキストは追従しない。すべて px で組まれたサイトは、24px を望んでいるユーザーにも 16px で表示される — そのユーザーは、サイトを開くたびにズームに手を伸ばすことになる。

逆のミスもよくある。チームが rem を採用した途端、border、shadow、角丸まで含めてあらゆる値が rem になってしまうケースだ。ルートが 24px のとき、0.0625rem の border は 1.5px になり、ブラウザはそれをデバイスピクセル単位に丸める — 1× の画面では 1px、2× の画面では 3px — つまりヘアラインの太さがデバイスごとに変わる。0.25rem 刻みのスペーシングスケールはインターフェース全体を 50% 大きくし、拡大したテキストが本来必要としていた行幅を食いつぶす。これと並んで、「1rem = 10px にするため」の html { font-size: 62.5% } トリックは、サードパーティやスタイル未指定の 1rem テキストをすべて黙って 10px に縮めてしまう。

判断の問い

単位は、ひとつの問いで選ぶ。ユーザーが好みの文字サイズを大きくしたとき、この値も大きくなるべきか?

答え 単位 典型的な値
はい — テキストそのもの、またはテキストのために存在する値 rem font-size、スペーシングスケール、垂直リズム、テキストコントロールの min-height
はい — ただしその要素自身のテキストに対して相対的 em letter-spacing、インラインアイコンのサイズ、font-size の上書きに追従すべきボタンの padding
いいえ — 視覚的なディテール、または幾何学的な制約 px border、outlinebox-shadowborder-radius、ヘアライン、ラスター画像のサイズ
どちらでもない — 比率である 単位なし / % / ch line-height、本文の行長、パーセント幅

ルートフォントサイズには手を触れない — html { font-size: 100% } か、何も書かないか。そうすれば rem は「ユーザーが選んだ基準サイズ」を意味し、これこそが px より rem を選ぶ唯一の理由になる。

クイックリファレンス

シナリオ 手法
本文、見出し、ラベルのフォントサイズ rem
スペーシングスケール、垂直リズム rem(特定要素のサイズに紐づく場合は em)
ボタン/入力欄の padding、インラインアイコン em — コンポーネント自身の font-size に追従
letter-spacing em
line-height 単位なし
border、outline、ヘアライン px
固定形状の box-shadowborder-radius px
タッチターゲットの最小サイズ px(44px)
読みやすい本文カラムの幅 chmax-width: 100%
ページシェルの最大幅、ガター px または % — テキストではなくジオメトリ
ビューポートのブレークポイント rem(または em — メディアクエリ内では同一)
コンテナのブレークポイント コンテナのフォントサイズに相対的な em
ルートフォントサイズ 未変更 / 100%62.5%10px も不可

デフォルトフォントサイズ設定とページズームは別物

ブラウザの「フォントサイズ」設定は、ルートフォントサイズを変える仕組みである。同じ見た目のサイズで書いた2つのテキストのうち、rem 側だけが反応する。

/* ユーザー設定 16 → 24px をシミュレートするには :root の font-size を 150% にする */
.text-px  { font-size: 16px; } /* 設定を無視し、常に 16px */
.text-rem { font-size: 1rem; } /* 設定に追従し、24px になる */

ページズームとデフォルトフォントサイズは独立した設定で、効果も異なる。

ユーザーの操作 16px テキスト 1rem テキスト(ルート未変更) レイアウト
ページズーム 200%(Cmd/Ctrl +) 2 倍 2 倍 CSS px でのビューポートが縮み、ブレークポイントが再評価される
デフォルトフォントサイズ 16 → 24px(Chrome、Edge、Firefox) 変化なし 1.5 倍 ビューポートは変化なし。rem/em ブレークポイントは再評価され、px ブレークポイントはされない
Firefox「文字サイズのみ変更」 ブラウザ依存。通常は両方拡大 拡大 ビューポートは変化なし
最小フォントサイズ(Safari、Firefox) 下限で切り上げ 下限で切り上げ

ページズームは IE 時代の問題を解消している。px テキストはもはや「拡大できないもの」ではない。残っているギャップがデフォルトフォントサイズ設定で、ここが rem の存在意義である。macOS の Safari は汎用のデフォルトフォントサイズを公開しておらず最小サイズしかないため、Safari だけでテストしても px テキストが設定を無視していることは見えない

デフォルトを変更しているユーザーの割合: 一般ユーザーを対象とした唯一の測定値は古く、2018 年の Internet Archive 訪問者のうち約 3% が 16px 以外のルートサイズだった。ロービジョンのユーザーを対象とした調査では、はるかに高い割合(対象により 8〜27%)が報告されている。WCAG 1.4.4(Resize Text)は rem を要求していない — 全ページズームが達成方法として挙げられている — ので、px テキストが自動的に不適合になるわけではない。rem を選ぶ理由は、この設定への対応はプロジェクト開始時ならコストがゼロで、後付けにはコストがかかるという点にある。

どの寸法がテキストに追従すべきか

周囲のスペースを拡大せずにテキストだけ拡大すると窮屈なコンポーネントになり、すべてを拡大すると border の太さがデバイス依存になり、shadow と角丸が大きくなりすぎる。同じカードを3通りで書くと違いが分かる(ルート 24px 時の挙動をコメントに記す)。

/* すべて px: 設定に何も反応しない → テキストが小さいまま */
.card--px {
  padding: 12px;
  border: 1px solid hsl(215, 20%, 80%);
  border-radius: 8px;
  box-shadow: 0 2px 6px hsl(215, 30%, 20%, 0.15);
}
.card--px .card__title  { font-size: 16px; }
.card--px .card__body   { font-size: 13px; }
.card--px .card__button { padding: 6px 12px; border-radius: 6px; font-size: 13px; }

/* すべて rem: ヘアラインと shadow まで拡大 → border が太り、角丸が膨らむ */
.card--rem {
  padding: 0.75rem;
  border: 0.0625rem solid hsl(215, 20%, 80%);
  border-radius: 0.5rem;
  box-shadow: 0 0.125rem 0.375rem hsl(215, 30%, 20%, 0.15);
}
.card--rem .card__title  { font-size: 1rem; }
.card--rem .card__body   { font-size: 0.8125rem; }
.card--rem .card__button { padding: 0.375rem 0.75rem; border-radius: 0.375rem; font-size: 0.8125rem; }

/* 混在(推奨): テキストとスペーシングは rem、視覚的ディテールは px
   → テキストと余白は大きくなり、border・角丸・shadow はシャープなまま */
.card--mixed {
  padding: 0.75rem;
  border: 1px solid hsl(215, 20%, 80%);
  border-radius: 8px;
  box-shadow: 0 2px 6px hsl(215, 30%, 20%, 0.15);
}
.card--mixed .card__title  { font-size: 1rem; }
.card--mixed .card__body   { font-size: 0.8125rem; }
.card--mixed .card__button { padding: 0.375rem 0.75rem; border-radius: 6px; font-size: 0.8125rem; }

rem のスペーシングスケール — Tailwind v4 のデフォルト

Tailwind CSS v4 は、すべてのスペーシングユーティリティをひとつのトークンから導出する。--spacing: 0.25rem なので、p-4calc(var(--spacing) * 4) = 1rem = デフォルトルートで 16px。テキストサイズも rem で、v4 のブレークポイントは 640px から 40rem に移行した。px が適切な場所では逃げ道が残されている — border は 1px、p-px が存在し、[4px] のような任意の値も許可されている。

:root {
  --spacing: 0.25rem; /* Tailwind v4 */
}

.p-4 {
  padding: calc(var(--spacing) * 4); /* 1rem — ルート16pxで16px、ルート24pxで24px */
}

rem スペーシングスケールのトレードオフは、幅や高さまで駆動してしまうこと。ブラウザのデフォルトが大きくなると第二の部分的なページズームのように振る舞い、gap や padding が行幅をより多く占める。通常これは許容範囲だ — 大きくなったテキストにはより多くの余白が必要 — が、水平方向のページガターや固定幅のクロームは、代わりに pxpx 項を含む clamp() を検討すべき場所である。後からスケールを固定にすると決めた場合も、トークンをひとつ(--spacing: 4px)変えるだけで、マークアップに触れずにすべてのユーティリティが切り替わる。

ブレークポイント: rem と em はブラウザの初期フォントサイズを読む

メディアクエリの中では、remem はブラウザの初期フォントサイズ — つまりユーザー設定 — に対して解決される。スタイルシートが html { font-size } に何を設定していても関係ない。デフォルト 24px で 1280px のウィンドウを使うユーザーは、48rem のブレークポイントに CSS 幅 1152px で到達する。拡大したテキストが実効スペースを減らしたとき、レイアウトは狭い方の形に切り替わる。768px のブレークポイントではこれは起きない。

/* ジオメトリ基準: テキストサイズに関係なく 768 CSS px で発火 */
@media (min-width: 768px) { }

/* テキスト容量基準: デフォルト16pxなら768px、デフォルト24pxなら1152pxで発火 */
@media (min-width: 48rem) { }

em/rem ブレークポイントを避ける古い理由は Safari のズームバグだった。Safari 15 はよく引用されるケースを修正し、Tailwind v4 はそれを根拠に 2024 年にデフォルトを rem に切り替えた。

コンテナクエリは事情が異なる。@container (inline-size > 40em)em はコンテナ自身のフォントサイズに対して解決され、cqi 単位はジオメトリだけを追跡する。そのため cqi だけで組んだフォントサイズはテキストのスケーリングを無視してしまう — clamp() などで rem 項を優位に保つこと。

62.5% トリック

html { font-size: 62.5%; }   /* デフォルト16pxで 1rem = 10px */
body { font-size: 1.6rem; }  /* 16px に戻す */

62.5% は依然として相対値なので、ユーザー設定を壊すことはない — デフォルト 20px ならルートは 12.5px、body テキストは 20px になる。推奨されないのは別の理由で、このトリックはオプトインしていないすべてのものにとっての 1rem を再定義してしまう

html { font-size: 62.5%; }

.my-body-text     { font-size: 1.6rem; } /* 補正済み: 1.6rem × 10px = 16px。正しく見える */
.third-party-text { font-size: 1rem; }   /* 未補正: 1rem × 10px = 10px。読めないサイズに */

サードパーティのウィジェット、ブラウザ拡張の UI、スタイル未指定の 1rem テキストは、すべて 10px で表示される。rem はすでにブラウザのデフォルトサイズに設定されているので、1rem を基準にする限り調整は不要。計算はルートフォントサイズではなくトークンの中に置く。 html { font-size: 10px } も決して設定しない — そこに絶対値を置くと、ユーザー設定を直接上書きしてしまう。

プロジェクト開始時のセットアップ

/* ルートには触れない。省略するか、意図を明示する */
html {
  font-size: 100%;
}

:root {
  /* テキストとテキスト駆動のリズム — 設定に追従する */
  --font-body: 1rem;
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-4: 1rem;
  --space-8: 2rem;

  /* 視覚的ディテール — 設定に追従しない */
  --border-width: 1px;
  --focus-width: 2px;
  --radius-sm: 6px;
  --shadow-sm: 0 2px 6px hsl(215, 30%, 20%, 0.15);
}

body {
  font-size: var(--font-body);
  line-height: 1.5;          /* 単位なしの比率 */
}

h1 {
  font-size: clamp(2rem, 1.5rem + 2vw, 3rem); /* 下限と推奨値の両方に rem 項を持たせる */
  letter-spacing: -0.02em;   /* 自身のサイズに相対 */
}

.button {
  min-block-size: 44px;      /* タッチターゲット: 物理的な制約 */
  padding: 0.625em 1em;      /* ボタン自身の font-size に追従 */
  border: var(--border-width) solid currentColor;
  border-radius: var(--radius-sm);
}

.button > svg {
  inline-size: 1em;          /* インラインアイコン: テキストに追従 */
  block-size: 1em;
}

.prose {
  max-inline-size: 65ch;     /* 行長: ch */
}

.page-shell {
  inline-size: min(100% - 32px, 1200px); /* ページのジオメトリ: px */
  margin-inline: auto;
}

@media (min-width: 48rem) { }

テストはプロジェクトの最後だけでなく、セットアップの最後にも行う。Chrome か Firefox でブラウザのデフォルトフォントサイズを 24px に設定し、テキストとスペーシングが大きくなり border は変わらないことを確認したうえで、WCAG 1.4.4 と 1.4.10 のためにページズーム 200% と 400% も確認する。

AIがよくやるミス

  • 「IE は消えた」を「px は rem と同等」と解釈する — ページズームは同等だが、ブラウザのデフォルトフォントサイズ設定は依然として別個の永続的な設定で、px はそれを無視する
  • すべての値を rem に変換する — ルート 24px では border が小数値になり画面ごとに異なるデバイスピクセル幅に丸められ、shadow と角丸が膨らみ、rem のスペーシングスケールは拡大したテキストが必要としていた行幅を食いつぶす
  • 切りのいい数字のために html { font-size: 62.5% } を設定する — サードパーティやスタイル未指定の 1rem テキストが 10px に落ちる。計算はトークンに置く
  • 「リセット」として html { font-size: 16px }body { font-size: 16px } を設定する — 絶対値のルートはユーザー設定を完全に上書きする。絶対値の body サイズは、その内側すべてへの継承を断ち切る
  • rem テキストと並べて px ブレークポイントを使う — 拡大したテキストが狭いレイアウトを決してトリガーしない。メディアクエリでは remem を使う
  • html { font-size }rem ブレークポイントを変えると期待する — メディアクエリの単位はブラウザの初期フォントサイズに対して解決され、html への author スタイルは参照されない
  • WCAG が rem を要求していると主張する — 1.4.4 はページズームを達成方法として挙げている。rem を選ぶ理由はユーザー設定であって、適合性ではない
  • ネストしたコンポーネントのフォントサイズに em を使う — サイズが祖先を通じて複利的に増減する。font-size には rem を使い、em はそのフォントサイズに追従すべき寸法のために取っておく

使い分け

rem

フォントサイズ、スペーシングスケール、垂直リズム、そしてテキストのために存在するあらゆる寸法 — 入力欄の min-height、本文を収めるダイアログの max-width など。

em

要素自身の font-size の上書きに追従すべき値: ボタンの padding、インラインアイコンのサイズ、letter-spacing、見出しレベルでサイズが決まる prose コンポーネント内の margin。

px

border、outline、shadow、固定形状の角丸、タッチターゲットの最小サイズ、ページシェルのジオメトリ、そしてテキスト拡大時に太く・大きくなると不自然に見えるあらゆるもの。px のフォントサイズが正当化されるのは固定コンポジションの内側 — キャンバスの注釈、スライドのステージなど、ステージ全体と比例して拡縮するタイポグラフィ — に限られる。

使ってはいけないもの

html { font-size: 62.5% }html { font-size: 10px }、リセットとしての body への pxfont-size。ルートはブラウザのデフォルトのままにして、rem が本来の意味を持つようにする。

参考リンク

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment