レイアウト構築の基本を理解しよう ~ 横スクロールが起きない!? Flex脱却編 ~

はじめまして! DXビジネス開発部の澤です。普段は医療系プロダクトのPjMやエンジニアまた技術広報として活動しています。

本記事は、OPTiM TECH BLOG Advent Calendar 2025 Day 1 の記事です!

11月30日に開催されたフロントエンドカンファレンス関西で「レイアウト構築の基本を理解しよう ~ 横スクロールが起きない!? Flex脱却編 ~ 」というタイトルで5分間のLTをさせていただきました。本記事では、発表で時間の都合上触れられなかった背景や詳細な解説も含めてお伝えします。

はじめに

フロントエンド開発で、こんな経験はありませんか?

  • overflow: auto を指定したのに横スクロールできない
  • 長いコンテンツが親要素を突き抜ける
  • モバイルで画面からはみ出してレイアウト崩れ

これらの問題には複数の原因がありますが、今回は私が遭遇した Flexbox特有の問題 を題材に、原因と解決策を考えていきます。

実際に遭遇した事象

OPTiM Support & Growth Portal という製品の開発に携わっているとき、FAQページを実装する機会があり、「A」と記載されたChipと回答内容(Answerコンポーネント)を横並びに表示する実装をしました。

実際のコード(簡略版)が以下です。React(Next) + MUI を利用して実装しています。

export const DetailAnswer = ({ answer }: Props) => {
  return (
    <Stack direction="row" gap={1}>
      <Chip size="small" label="A" />
      <Answer content={answer} />
    </Stack>
  );
};

一見問題なさそうですが、画面幅を狭めると回答本文が親要素を突き抜けてしまいました。

上記のコードが実際にブラウザ上にレンダリングされる場合は以下のようなコードになります。(※ 説明のため簡略化、追加している部分があります。)

<div class="MuiStack-root css-1oxswqj-MuiStack-root"
     style="display: flex; flex-direction: row">
    <div class="MuiChip-root ...">
      <span class="MuiChip-label ...">A</span>
    </div>
    <div class="MuiBox-root ..." ....>
      <!– Answerコンポーネントのコンテンツ -->
    </div>
  </div>
</div>

ここでのポイントは、display: flex; flex-direction: row が style として設定されている点です。MUI の Stack コンポーネントを利用するとFlexboxでレンダリングされることが分かります。

原因は何だったのか?

今回の問題は、MUI の Stack コンポーネントを利用していたことで Flexbox が適応されていたことが原因でした。

Stack は内部的に Flexbox を使って要素を並べる仕組みになっています。Flexbox は便利なレイアウト手法ですが、CSSの仕様上、次のような暗黙的な挙動があります。

Flex の子要素には自動的にmin-width: auto および min-height: auto が適用される

この仕様は、子要素のコンテンツサイズを尊重するために設けられています。つまり、Flexboxでは「子要素はできるだけ縮まない」という前提があり、結果として以下のような現象が起きます。

  • 通常のBlock要素
    • 親要素の幅に合わせて子要素が縮む
  • Flex要素(min-width: auto)
    • 子要素が縮まず、親要素を押し広げる

今回のケースでは、回答本文を表示するAnswerコンポーネントが長文を含んでいたため、Flexboxの暗黙的なmin-width: autoが効いてしまい、親要素を突き抜けるレイアウト崩れが発生しました。

この特性を理解したうえで、最も簡単な解決策は、子要素に min-width: 0 を明示的に指定することです。これにより、Flexboxの暗黙的な制約を解除し、要素が親要素の幅に合わせて縮むようになります。 以下は修正後のコード例です。

export const DetailAnswer = ({ answer }: Props) => {
  return (
    <Stack direction='row' gap={1}>
      <Chip size="small" label="A" />
      <Box sx={{ minWidth: 0, flex: 1 }}>
        <Answer content={answer} />
      </Box>
    </Stack>
  );
};

ここでのポイントは、Box に minWidth: 0 を設定し、さらに flex: 1 を指定することで、回答本文が残りのスペースに収まるようにしている点です。

補足: なぜmin-width: 0が必要なのか?

先で説明している通り、Flex 子要素の min-width はデフォルトで auto です。この auto は「コンテンツサイズに基づいて最小幅を決定する」という意味を持ちます。つまり、テキストが長ければ長いほど、要素は縮まず、親要素を押し広げます。 min-width: 0 すなわち最小幅を 0 に指定することで、「縮むことを許可する」ような状態になり、Flexboxのレイアウトが期待通りに動作します。

Flexboxの構造的な問題

Flexboxは便利ですが、上記以外にも以下のような課題があります。

  • 不要なdivの増加
    • レイアウトのために余計なHTML要素が必要
  • 非セマンティックな構造
    • 無意識のうちに意味のないコンテナが量産されることも多い
  • 複雑なレスポンシブ対応が必要となる
    • shrink/grow/basisの組み合わせが直感的でない

こうした問題を根本的に解決するには、Gridレイアウトの採用が有効です。

Grid を利用したときの解決方法

今回のケースではFlexboxをやめ、Gridを採用しました。修正後のコードは以下です。

export const DetailAnswer = ({ answer }: Props) => {
  return (
    <Box
      sx={{
        display: 'grid',
        gridTemplateColumns: 'auto minmax(0, 1fr)',
        gap: 1,
        alignItems: 'start',
      }}
    >
      <Chip size="small" label="A" />
      <Answer content={answer} />
    </Box>
  );
};

gridTemplateColumns: 'auto minmax(0, 1fr)'で、Chip は固定幅、Answer コンポーネントは可変幅に設定できます。これで親要素を突き抜ける問題は完全に解消しました。

なぜGridを利用するのか

Gridレイアウトにすることにより、以下のような利点があります。

  • 直感的な幅制御
    • Flexでは、flex-shrink, flex-grow, flex-basis の複雑な組み合わせが必要ですが、gridTemplateColumns: ‘auto 1fr‘ などで直観的に指定できます。
  • 幅制約が自然に適用される
    • Flexと違い、子要素が親の列幅を超えることがありません
  • 不要な中間要素が不要
    • レイアウトのためだけのdivやBoxが削減できます
  • レスポンシブ対応も簡単

FlexboxとGridの適切な使い分け

ここまで読むと「Flexboxは使わないほうがいいの?」と思う方もいるかもしれません。

しかし、Flexboxがダメというわけではありません。むしろ、Flexboxはシンプルなレイアウトに非常に適しています。重要なのは、レイアウト要件に応じてFlexboxとGridを正しく使い分けることです。

Flexboxを使うべき場面

  • 1次元のレイアウト
    • 横並びや縦並びなど、単純な方向に要素を並べたい場合
  • アイテム間の余白調整
    • gapやjustify-contentで簡単にスペースを調整できる
  • 中央寄せやアライメント
    • align-itemsやjustify-contentで柔軟な配置が可能

Flexboxは「並べる」ことに特化しており、シンプルなUIやコンポーネントの内部レイアウトに最適です。

Gridを使うべき場面

  • 2次元のレイアウト
    • 行と列の両方を扱う複雑なレイアウト
  • 固定+可変レイアウトの組み合わせ
    • 例えば「左側は固定幅、右側は残りのスペースを使う」など
  • 複雑なレスポンシブ対応
    • メディアクエリと組み合わせて柔軟にカラム数を変えたい場合など

Gridは「構造を定義する」ことに強く、ページ全体や複雑なコンポーネントのレイアウトに向いています。

今回のケースでは、Chipが固定幅、Answerコンポーネントが可変幅という要件がありました。Flexboxでも対応できますが、min-widthの制御や追加のラッパ要素が必要になるため、Gridのほうが自然で保守性が高いと判断しました。

ポイントは「絶対にFlexboxを使うな」ではなく、「適材適所で選ぶ」ことです。 FlexboxとGridの特性を理解し、要件に応じて選択できるようになることが、フロントエンド開発者にとって重要なスキルだと考えています。

まとめ

Flexboxは便利ですが、暗黙的なmin-width: autoによって横スクロールやレイアウト崩れが起きることがあります。応急処置は可能ですが、構造的な課題を考えるとGridレイアウトの採用が有効となる場合があります。 「とりあえずFlex」から卒業し、要件に応じて適切なレイアウト手法を選びましょう。Flexbox と Grid の特性を理解することが、保守性の高いUIを作る第一歩になると思います。

おわりに

当社では、ReactやVue.jsなどの最新フロントエンド技術を活用し、UI/UXを追求するプロダクト開発に取り組んでいます。フロントエンド分野でも技術的な挑戦を楽しめる仲間を募集しています。興味がある方は、ぜひこちらからご応募ください!

www.optim.co.jp