4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Frontend CSS – パート11】ブラウザから見たIntrinsic Sizing ─ コンテンツとコンテナはどちらがサイズを決めるのか?

4
Posted at

image.png

ご注意
この記事は AI のサポートを受けています

第10回(Subgrid)を読み終えたのに、要素が理由なく膨らむ? width: 300px を指定して DevTools を開いたら、要素の幅は… 300px じゃない? Intrinsic Sizing へようこそ — ブラウザがあなたの指示より、中身のコンテンツを優先してサイズを決める世界です。

この記事は、CSS の基礎に慣れたフロントエンドエンジニア向けです。width / height が期待どおりにならずに悩んでいる方に向けて、extrinsic vs intrinsic → min-content / max-content / fit-content → Flex & Grid → 商品カードコンポーネントの実例、という流れでお話しします。


1. 課題:サイズが思い通りにならないとき

こんな経験、ありませんか?

  • ケース1: ナビゲーションの各項目、文字数がバラバラ。コンテンツにぴったり合わせたい — 余白なし、無駄なし。display: flex + flex: 1 だと長い項目が伸び、短い項目が潰れる。width: auto を試す — 効かない。paddingmarginmax-width を足す — 全部ごちゃごちゃに。
  • ケース2: 商品名を表示するコンポーネント。短い名前(「Tシャツ」)も、長い名前(「2026年冬 高級防水ダウンジャケット」)もある。1行で収めたい — white-space: nowrap + overflow: hidden — 長い名前が… 不自然に切れる。
  • ケース3: 動的コンテンツのツールチップ。文字にぴったり合わせたいが、画面からはみ出したくない。max-width: 300px — 短いコンテンツでも余白だらけ、長いと溢れる。width: fit-content — 理想通り… 古いブラウザでテストしたら、何も起きない。
  • ケース4: トップページの記事カード。画像あり・なしが混在。全部同じ高さにしたい。display: flex + align-items: stretch — 画像なしカードが不揃いに縮む。min-height を足す — コンテンツ多いカードは溢れる。え?

厳しい現実: 多くの人は CSS sizing を「数値を指定する」こととして学びます — width: 300px と書けば 300px になる、と。実際にはブラウザは中身に基づいて独自の判断をします。これを理解しないと、px の数字がいつまでも合わない戦いが続きます。

この記事では Intrinsic Sizing — ブラウザがコンテンツからサイズを計算する仕組み — を分解していきます。理解すれば、width との格闘がかなり減るはずです。


2. 本質:Intrinsic vs Extrinsic — 誰がサイズを決める?

CSS では、要素のサイズを決める方法は大きく2つあります。

2.1. Extrinsic Size — 外から「押し付ける」サイズ

最も馴染みのある方法:ブラウザに「この大きさにしろ」と指示する

.box {
  width: 300px;
  height: 200px;
}

外側からサイズを押し付けています。ブラウザは従うしかない — これが extrinsic sizing:サイズは要素の外側から来る。

欠点? 硬直する。コンテンツが指定サイズより大きい → 溢れる。小さい → 無駄な余白

2.2. Intrinsic Size — 「内側から」決まるサイズ

逆に、intrinsic size は要素が自然に持つサイズ — 完全に中身のコンテンツに基づきます。

空の <div> を想像してください — border を付けると細い線が見えるだけで、高さはありません。Intrinsic size = 0(padding / border を除く)。

テキストを入れる — border が広がって文字を包みます。これが intrinsic size の仕事:コンテンツがサイズを決める

2.3. width: auto は intrinsic sizing そのものではない

ここ、よく誤解されます — 強調しておきます:

width: auto は intrinsic sizing と同義ではありません。

width: auto の挙動は formatting context によって変わります:

  • Block Formatting Context (BFC): width: auto = fill available space(利用可能な空間いっぱいに広がる)。これは extrinsic な挙動です。
<div style="border:1px solid red;">Hello</div>
<!-- コンテンツは "Hello" だけなのに、div は container 幅いっぱいに広がる -->
  • Inline Formatting Context (IFC): inline 要素には width は適用されません。幅は完全に intrinsic — コンテンツ次第。

  • Flexbox / Grid: width: auto はさらに異なる場合があり、通常 flex-basismin-width と組み合わさって基準サイズが決まります。


3. 基礎概念:Intrinsic Size Contribution

具体的なキーワードに入る前に、Grid や Flexbox のような複雑なレイアウトでブラウザが実際にサイズを計算する方法を理解する必要があります。

現代のレイアウトエンジンは、純粋な intrinsic size だけを使うわけではありません — intrinsic size contributions(内在的サイズ寄与)に変換して、レイアウトアルゴリズムに渡します。

contribution は要素の最終サイズではなく、計算プロセスに寄与する値です。ブラウザはこれをもとに、各要素とレイアウト全体の実際のサイズを決定します。

3.1. Intrinsic Size Contribution の種類

用語 意味
min-content contribution レイアウトアルゴリズムに寄与できる最小値。通常、コンテンツを収める最小サイズ(padding、border 含む)に相当。
max-content contribution レイアウトアルゴリズムに寄与できる最大値。通常、コンテンツを1行で収めるのに必要なサイズ(改行なし)に相当。

Grid での例:

.grid {
  display: grid;
  grid-template-columns: min-content 1fr max-content;
  /*                   ↑                ↑          ↑
         min-content   1fr        max-content
         contribution            contribution */
}

この例では:

  • 列1:列内の全要素の min-content contribution を取り、最大値を選ぶ → 列幅。
  • 列2:残りのスペースを占有。
  • 列3:列内の全要素の max-content contribution を取り、最大値を選ぶ → 列幅。

なぜ知る必要がある? Grid や Flexbox では、要素が自身の intrinsic size より大きくなることがあります — 隣の要素に合わせて引き伸ばされるから。contribution がレイアウト全体のサイズ決定に関わっています。


4. 3つの内在的キーワード:min-content、max-content、fit-content

CSS は intrinsic sizing を直接扱う3つのキーワードを提供しています:

4.1. min-content — 最大限に縮める

min-content は、コンテンツを収められる最小サイズです。

ブラウザは要素を可能な限り縮小し、それ以上縮められない部分だけを残します。

  • テキスト: min-content = 段落内の最長単語の幅。単語の途中で改行しない(word-break: break-all がある場合を除く)ため、その単語を収める幅が必要。
  • 画像(replaced element): ブラウザは画像の intrinsic dimensionsaspect ratio を使用。min-content ≠ 画像の元サイズ — レイアウトコンテキストにも依存。
  • Flex item: min-content = アイテムが縮小できる最小サイズ(min-width / min-height の影響あり)。
.box {
  width: min-content;
  /* 最長単語にぴったり合う幅 */
}

視覚的な例:

<p class="min-content">
  これはとても長い単語super_long_unbreakable_wordを含む段落です
</p>

width: min-content では、要素の幅 = super_long_unbreakable_word の幅 — 他の単語は可能な限り改行されます。

4.2. max-content — 最大限に伸ばす

max-content は、コンテンツを1行に収めるのに必要な最大サイズ — 改行なし。

ブラウザは要素を引き伸ばし、全コンテンツを1行に収めます。

.box {
  width: max-content;
  /* 1行分のコンテンツ幅いっぱい */
}

4.3. fit-content — バランス(誤解しないで)

fit-content は上記2つを組み合わせたもの。CSS Sizing Spec の公式:

fit-content = min(max-content, max(min-content, available-space))

各部分の意味:

  1. max(min-content, available-space):2つの値の大きい方を取る — 要素はそれより狭くなれない。
  2. min(max-content, ...):要素は max-content を超えない。

まとめると:

  • 通常、要素は min-content より小さくならずmax-content より大きくならない
  • 可能なら available-space に合わせるが、上記2値でクランプされる。

fit-content は要素がコンテナに必ず収まることを保証しません。available-spacemin-content より小さい場合、要素は少なくとも min-content のサイズ — コンテナから溢れる可能性があります。

ブラウザ対応: fit-content は Chrome 46+、Firefox 94+、Safari 11+ でサポート(Can I Use)。古いブラウザ向けの安全なフォールバック:width: auto + max-width


5. 直接比較:min-content、max-content、fit-content

同じコンテンツ、3つの box、3つの値 — 同じコンテナ内でどう違うか見てみましょう。

<div class="container">
  <div class="box box-min">Intrinsic sizing の各値を試すための、かなり長いテキストの段落です</div>
  <div class="box box-max">Intrinsic sizing の各値を試すための、かなり長いテキストの段落です</div>
  <div class="box box-fit">Intrinsic sizing の各値を試すための、かなり長いテキストの段落です</div>
</div>
.container {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: 400px;
  border: 2px dashed #ccc;
  padding: 1rem;
}

.box {
  padding: 0.5rem;
  background: #f0f0f0;
  border: 1px solid #999;
}

.box-min {
  width: min-content;
  /* 最長単語にぴったり */
}

.box-max {
  width: max-content;
  /* 1行分 — コンテナから溢れる可能性 */
}

.box-fit {
  width: fit-content;
  /* コンテナに合わせるが、min より小さく max より大きくはならない */
}

結果:

  • .box-min:非常に狭く、テキストが多く改行 — 最長単語分の幅のみ。
  • .box-max:非常に広く、テキスト1行 — コンテナから溢れる可能性。
  • .box-fit:コンテナ(400px)にぴったり。available-space = 400px、max-content > 400px なので fit-content = 400px。

image.png


6. さまざまなコンテキストでの Intrinsic Sizing

6.1. Flexbox の場合

flex: 0 1 auto(デフォルト)では、flex item はコンテンツに基づく intrinsic size を持ちます。

.container {
  display: flex;
}
.item {
  flex: 0 1 auto; /* デフォルト */
}

各 item はコンテンツに応じた幅 — 長い item ほど広い。

ただし flex: 1 1 0%flex: 1)の場合:

  • flex-basis: 0 が初期サイズ決定での intrinsic size の役割を弱める
  • Intrinsic sizing は min-width: automin-height: auto 等を通じて最終段階で依然関与。

無視したい? min-width: 0 を追加:

.item {
  flex: 1 1 0%;
  min-width: 0; /* min-width: auto を無効化 — intrinsic size が縮小を妨げなくなる */
}

6.2. Grid の場合

Grid には intrinsic sizing 用の特殊な単位があります:min-contentmax-contentautofit-content()

.grid {
  display: grid;
  grid-template-columns: min-content 1fr max-content;
  /* 列1: 最小コンテンツ(min-content contribution)
     列2: 残りを占有
     列3: 最大コンテンツ(max-content contribution) */
}

Grid では、min-content 列の幅は列内の全 cell の min-content contribution の最大値で決まります — 個々の cell ごとではありません。

Grid の fit-content() vs width: fit-content

width: fit-content Grid の fit-content(300px)
コンテキスト プロパティ値(widthheightinline-size 等) Grid トラックの sizing 関数(grid-template-columns 等)
意味 min(max-content, max(min-content, available-space)) 同じ intrinsic ロジックだが、引数で上限を固定(例:300px)
コンテンツに合わせて縮むツールチップ grid-template-columns: fit-content(200px) 1fr — 列1は最大 200px

6.3. Block layout(通常のレイアウト)の場合

Block layout には「どんでん返し」があります:

  • デフォルト幅width: auto)→ fill available space(extrinsic)。
  • デフォルト高さheight: auto)→ コンテンツベース(intrinsic)。

だから空の <div> は高さ 0(padding / border 除く)なのに、幅は常に container いっぱいなのです。


7. 実践例:スマートな商品カードコンポーネント

コンテンツに応じてサイズを自動調整する商品カード — intrinsic sizing を適切な場所で使います。

要件:

  • 各カードの幅はコンテンツにぴったり — 余白なし、不足なし。
  • 商品名は可能な限り1行
  • 長すぎる名前 → ... で省略。
  • カードはコンテナから溢れない
  • 同じ行のカードは同じ高さ
ProductCard.tsx — React コンポーネント
ProductCard.tsx
import React from 'react';
import './ProductCard.css';

interface Product {
  id: string;
  name: string;
  price: number;
  image?: string;
  badge?: string;
}

interface ProductCardProps {
  product: Product;
  variant?: 'compact' | 'default' | 'detailed';
}

export const ProductCard: React.FC<ProductCardProps> = ({
  product,
  variant = 'default'
}) => {
  return (
    <div className={`product-card product-card--${variant}`}>
      {product.image && (
        <div className="product-card__image-wrapper">
          <img
            src={product.image}
            alt={product.name}
            className="product-card__image"
          />
        </div>
      )}
      <div className="product-card__content">
        {product.badge && (
          <span className="product-card__badge">{product.badge}</span>
        )}
        <h3 className="product-card__title">{product.name}</h3>
        <p className="product-card__price">{product.price.toLocaleString()}</p>
      </div>
    </div>
  );
};

interface ProductListProps {
  products: Product[];
}

export const ProductList: React.FC<ProductListProps> = ({ products }) => {
  return (
    <div className="product-list">
      {products.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
};
ProductCard.css — スタイル
ProductCard.css
/* ============================================
   Container: intrinsic sizing を活用
   ============================================ */
.product-list {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  padding: 1rem;
  max-width: 1200px;
  margin: 0 auto;
  border: 2px dashed #e5e7eb;
}

/* ============================================
   Product Card: fit-content + max-width で安全に
   ============================================ */
.product-card {
  width: fit-content;
  max-width: min(100%, 320px);

  display: flex;
  flex-direction: column;
  background: white;
  border-radius: 12px;
  padding: 1rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  transition: all 0.2s;

  flex: 0 1 auto;
  box-sizing: border-box;
}

/*
  vertical-rl / vertical-lr 等の writing mode 対応では
  width の代わりに inline-size: fit-content; も使えます。
*/

.product-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

.product-card__image-wrapper {
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: 8px;
  background: #f3f4f6;
}

.product-card__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.product-card__badge {
  display: inline-block;
  background: #ef4444;
  color: white;
  font-size: 0.7rem;
  font-weight: 600;
  padding: 0.2rem 0.6rem;
  border-radius: 9999px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.product-card__title {
  width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;

  font-size: 1rem;
  font-weight: 600;
  margin: 0.5rem 0 0.25rem 0;
  color: #1f2937;
  max-width: 100%;
  box-sizing: border-box;
}

.product-card__price {
  font-size: 1.1rem;
  font-weight: 700;
  color: #10b981;
  margin: 0;
}

.product-card--compact {
  padding: 0.5rem;
  gap: 0.25rem;
}

.product-card--compact .product-card__title {
  font-size: 0.8rem;
}

.product-card--compact .product-card__price {
  font-size: 0.9rem;
}

.product-card--detailed {
  min-width: 200px;
}

.product-card--detailed .product-card__title {
  white-space: normal;
  text-overflow: unset;
  overflow: visible;
}

ポイント:

  • width: fit-content + max-width: min(100%, 320px):コンテンツに合わせつつコンテナから溢れない、最大 320px。
  • flex: 0 1 auto:intrinsic size を尊重しつつ、必要に応じて伸縮。
  • 高さの統一: .product-listdisplay: flex + align-items: stretch(デフォルト)— 同じ行のカードは、その行で最も高い item に合わせて伸びます。width: fit-content は幅のみに影響します。
  • Badge は inline-block — コンテンツに合わせて縮み、min-content は不要。
  • Title の長い名前は ellipsis — 妥協策ですが、production UI では許容範囲。

8. チェックリスト:サイズで悩む前に

サイズの問題に直面したら、!important を足す前にこのリストを確認:

  • この要素の intrinsic size はいくつ?

    • 画像:ファイルの intrinsic dimensions + aspect ratio。
    • テキスト:font size、長さ、改行の可否。
    • 空コンテンツ:contribution = 0 だが、padding / border は表示サイズを作る。
  • extrinsic と intrinsic、どちらを使っている?

    • width: 300px → Extrinsic。
    • width: auto → formatting context 次第(block: fill available、inline: コンテンツベース)。
    • width: fit-content → 制限付き intrinsic。
  • min-content — どこまで縮むか把握している?

    • テキスト:最長単語の幅、段落全体ではない。
    • 画像:intrinsic dimensions + aspect ratio から計算。
  • max-content — 非常に広い要素に備えている?

    • viewport より広くなる可能性 — ship 前に確認!
  • fit-contentmax-width を付けた?

    • fit-content 単体だと長い単語で予想外に広がる。
    • production UI:常に max-width を併用。
  • Flexbox が intrinsic を上書きしていない?

    • flex: 1 1 0% → 初期サイズで intrinsic を上書き。
    • flex: 0 1 auto → intrinsic を尊重(デフォルト)。
  • Grid で列に min-content / max-content を使った?

    • grid-template-columns: min-content 1fr → 列1はコンテンツベース、列2は残り。
    • 列サイズ = cell の contribution の最大値。
  • intrinsic と extrinsic を意図せず混ぜていない?

    • width: 300px + padding: 20pxbox-sizing: border-box なし → 合計 340px。
    • width: fit-contentmax-width なし → コンテナから溢れる可能性。

9. まとめ

Intrinsic sizing は「あれば便利」ではなく — ブラウザのデフォルトの動き方です。理解すれば:

  1. 要素サイズの当て推量が減る。
  2. CSS が少なくて済む — 残りはブラウザが処理。
  3. コンポーネントが柔軟に — コンテンツに自動調整。
  4. 溢れを防げる — 各要素の限界がわかる。

クイックリファレンス:

  • Intrinsic size = コンテンツに基づくサイズ。
  • Extrinsic size = 外側から押し付けられたサイズ。
  • width: auto ≠ intrinsic sizing — formatting context 次第。
  • min-content = 最大限に縮小。
  • max-content = 最大限に伸長、1行。
  • fit-content = バランス、production では max-width を併用。
  • Flexbox: flex: 0 1 auto は intrinsic を尊重;flex: 1 1 0% は完全ではない — min-width: auto 経由で intrinsic が影響。
  • Grid: min-content / max-content で cell の contribution に基づく柔軟な列。

マントラ:

「コンテンツがサイズを決める。例外は私が指定するとき — そのとき、自分が何を指定しているか理解していること。」


インタラクティブデモ


参考資料


👉 次回

【Frontend CSS – パート12】ブラウザから見た Overflow:要素がはみ出したとき何が起こるのか?

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?