iPhone Duo向けのAppleの設計指針を読んでいて、フロントエンド側でもかなり気になる話があった。

外側の画面、開いた内側の画面、途中まで折った状態、Split View。1台の端末なのに、アプリが使える領域も縦横比も途中で変わる。

Appleはこれに対して、ポーズごとに別のUIを作るのではなく、利用できる領域に合わせて同じUIを連続的に適応させるよう案内している。ネイティブアプリではSize Classや実際のウィンドウサイズを使い、画面の向きや端末種別への決め打ちを避けるという考え方だ。

この考え方はWebにもそのまま当てはまる。

Duo専用のCSSを書くことより、今あるレスポンシブ設計を device → layout から available space → layout へ寄せる方が先だと思う。

ただ、その話だけでは少し抽象的だ。そもそもDuoでSafariを開いたとき、普通のWebページがどう見えるのかから考えてみたい。

iPhone DuoのSafariはどう表示されるのか

Duoを開いてSafariを全画面で使う場合、左右に独立したWebページが1枚ずつ表示されるわけではない。

基本的には、開いたインナーディスプレイ全体を1つの広い表示領域として使う。

Duoを閉じた状態

┌────────────┐
│            │
│   Safari   │
│            │
│   narrow   │
│            │
└────────────┘

開くと、同じSafariがより広い領域へ適応する。

Duoを開いてSafariを全画面表示

┌──────────────────────────┐
│                          │
│          Safari          │
│                          │
│       wide viewport      │
│                          │
└──────────────────────────┘

普通の縦スクロール記事が、紙の見開きのように左から右へページ送りされるわけでもない。

こうなるわけではない

┌────────────┬────────────┐
│ ページ上部  │ その続き    │
│            │            │
│  1ページ目 │  2ページ目  │
│            │            │
└────────────┴────────────┘

AppleもDuoのレイアウト設計について、記事やフィードのような連続スクロールコンテンツは折り目を理由に片側へ移動させるべきではないとしている。

一方、Split Viewを使えばSafari自体をインナーディスプレイの片側へ置ける。

Split View

┌────────────┬────────────┐
│            │            │
│   Safari   │   別アプリ  │
│            │            │
│   medium   │            │
│            │            │
└────────────┴────────────┘

さらにiPhone Duoでは、同じアプリのウィンドウを複数開ける。Appleは例としてSafariを2ウィンドウ並べ、比較しながらブラウズする使い方も紹介している。

Safariを2ウィンドウ

┌────────────┬────────────┐
│            │            │
│  Safari A  │  Safari B  │
│            │            │
│ 商品ページ │ 比較ページ  │
│            │            │
└────────────┴────────────┘

ここまでを見るだけでも、iPhone Duo = この横幅 と決めるのが難しいことが分かる。

同じ端末、同じSafariでも、閉じる、開く、Split Viewにするという操作だけでWebページへ与えられる場所が変わるからだ。

物理解像度は分かっているが、CSS pxは別の話

Duoのディスプレイ仕様そのものはAppleから公開されている。

画面物理解像度画素密度
インナーディスプレイ1878 × 2670 px430ppi
アウターディスプレイ1398 × 2034 px460ppi

ここで注意したいのが、物理ピクセル数をそのままSafariのCSS pxとして扱えないことだ。

Retinaディスプレイでは以前からそうだが、ディスプレイを構成する物理ピクセルと、Webページが使うCSS pxは同じ単位ではない。devicePixelRatio やOS、ブラウザのviewport処理が間に入る。

Physical display
2670 × 1878 pixels

        │ OS / browser scaling

CSS viewport
window.innerWidth × window.innerHeight

Duoでは、この違いが数字にも表れている。

App Store Connectが指定しているDuoのスクリーンショットサイズは、アウターが 1398 × 2034、インナーが 2007 × 2853 だ。インナーディスプレイの物理解像度 1878 × 2670 とは一致しない。

そのため、物理解像度を2や3で割って「Safariは何CSS pxになる」と決めるのは避けたい。

Web側で本当に知りたいのは次の値だ。

  • window.innerWidth / window.innerHeight
  • window.visualViewport.width / height
  • window.devicePixelRatio
  • screen.width / screen.height
  • safe-area-inset-*
  • Split Viewにしたときのviewport変化
  • 開閉や途中まで折ったときにWebKitがどの情報を公開するか

物理解像度は確定している。一方、Safariから見えるCSS viewportやsafe areaの具体値は、Duo Simulatorまたは実機で確認したい部分になる。

同じ端末の中でavailable spaceが変わる

Duoの特徴をWeb側から単純化すると、同じ端末の中で narrowwide、その途中の幅を行き来することになる。

同じ iPhone Duo

閉じる
┌────────────┐
│   narrow   │
└────────────┘

      ↓ 開く

┌──────────────────────────┐
│           wide           │
└──────────────────────────┘

      ↓ Split View

┌───────────────┐
│    medium     │
└───────────────┘

これを見ると、iPhone Duoならこのレイアウト という端末判定では足りない。

device → layout

ではなく、

available space → layout

で考える方が自然になる。

「スマホならこのUI」が崩れやすくなる

Webでは長いあいだ、画面幅をいくつかの区切りに分けてレイアウトを変えてきた。

たとえば次のような考え方だ。

< 768px
スマホUI

>= 768px
タブレット / PC UI

メディアクエリー自体が悪いわけではない。問題になるのは、この数値を「端末の種類」と結びつけてしまうことだ。

iPhone Duoでは、同じ端末でも閉じているときと開いているときで利用できる領域が違う。Split Viewなら、端末自体は大きくてもWebページへ割り当てられた幅は狭くなる。

さらに、横長だから landscape、縦長だから portrait と決めても、その向きから期待していたほどのスペースがあるとは限らない。

AppleもDuo向けのネイティブアプリについて、画面の向きではなくSize Classと利用可能な領域でレイアウトを決めるよう案内している。内側の画面では、従来の supported interface orientations を前提にしたレイアウト判断が成立しない場面もある。

Webでも見るべきなのは「この端末は何か」より、「いま、このUIにどれだけの場所があるか」になる。

Duo専用レイアウトを先に作らない

Duoが出たからといって、まず次のような分岐を増やすのは避けたい。

if (isIPhoneDuo) {
  // Duo専用レイアウト
}

仮に端末を正しく判別できたとしても、Duoの中だけで利用できる領域が変わる。外画面、内画面、Split Viewをさらに判定し始めると、端末名を起点にした分岐が増えていく。

しかも、この問題はDuoだけではない。折りたたみ端末、タブレットのマルチタスク、デスクトップのリサイズ可能なウィンドウ、ブラウザのサイドバーなどでも、アプリが実際に使える幅は変わる。

通常のWebサイトやWebアプリなら、まずDuoを特別扱いしなくても崩れない状態にしたい。

Duo固有のヒンジ位置まで分かると体験を改善できるUIだけ、その情報を追加で使う。この順番の方が壊れにくい。

ページ全体はviewport、コンポーネントはcontainerを見る

ページの大きな構成を変えるなら、従来どおりメディアクエリーは使える。

ただし、768px = tablet のように端末名へ変換する必要はない。

.page {
  display: grid;
  grid-template-columns: 1fr;
}

@media (min-width: 64rem) {
  .page {
    grid-template-columns: 18rem minmax(0, 1fr);
  }
}

ここで意味があるのは「64rem以上ならタブレット」という分類ではなく、サイドバーと本文を並べても窮屈にならないだけの幅がある、ということだ。

一方、カードや検索パネル、メール一覧のような部品は、viewportより自分が置かれたコンテナの幅を見る方が自然なことが多い。

Container Queriesなら、同じコンポーネントでも置かれた場所の広さに応じてレイアウトを変えられる。

.mail-layout {
  container: mail / inline-size;
}

.mail-content {
  display: grid;
  grid-template-columns: 1fr;
}

@container mail (min-width: 45rem) {
  .mail-content {
    grid-template-columns: minmax(16rem, 30%) minmax(0, 1fr);
  }
}

狭い領域では1カラム、十分な幅があれば一覧と本文を並べる。

これは「スマホ版からタブレット版へ切り替える」というより、同じ情報構造を利用可能な場所へ展開しているだけだ。

AppleがDuoで示している、閉じた状態では1つの情報階層を見せ、開いたら同じ状態を保ったまま複数カラムへ広げる考え方とも近い。

2026年9月時点でCSS Container Queriesは広く利用できる機能になっている。

ブレークポイントはなくならない

available space → layout にすると、ブレークポイントが不要になるわけではない。

45remを超えたら2カラムにする、といった境界は今後も必要になる。

変えたいのは、その値の意味だ。

768pxだからtablet

ではなく、

この幅なら2カラムが成立する

と考える。

実際のコンテンツを見て境界を決めれば、新しい端末が増えても端末一覧を更新し続ける必要がない。

デザインシステム側でも mobiletabletdesktop という名前だけで設計意図を持たせるより、compactwide やコンポーネント固有の状態として扱った方が、何を基準に切り替えているのか分かりやすい。

JavaScriptで必要なのも端末判定ではなくサイズ変化

見た目だけならCSSへ任せたいが、利用できる領域に応じてJavaScript側の挙動も変えたいことはある。

その場合も、初期表示時の window.innerWidth だけを読んで状態を固定する実装は避けたい。

const isMobile = window.innerWidth < 768;

これでは、表示後にウィンドウが変わる前提をコードへ持ち込めない。

viewport単位の条件なら matchMedia()、特定コンポーネントのサイズを見るなら ResizeObserver が使える。

const element = document.querySelector<HTMLElement>(".workspace");

if (element) {
  const observer = new ResizeObserver(([entry]) => {
    const isWide = entry.contentRect.width >= 720;

    element.dataset.layout = isWide ? "wide" : "compact";
  });

  observer.observe(element);
}

ResizeObserver は要素そのもののサイズ変化を監視できる。CSSだけでは切り替えられない処理がある場合でも、端末判定へ戻らずに済む。

ただし、単に2カラムへ変えるだけならJavaScriptで data-layout を付ける必要はない。レイアウトはCSSに任せ、JavaScriptは挙動まで変える必要があるときだけ使いたい。

Safe Areaを左右対称だと思わない

Duoでは画面の形だけでなく、カメラやシステムUIによって利用できる領域も変わる。

AppleのDuo向けガイドでも、Safe Areaやレイアウトマージンが非対称になる場合があるため、各辺を個別に扱うよう案内している。

Webでもedge-to-edgeなUIを作っているなら、safe-area-inset-* を使える。

.app-shell {
  padding-top: max(1rem, env(safe-area-inset-top));
  padding-right: max(1rem, env(safe-area-inset-right));
  padding-bottom: max(1rem, env(safe-area-inset-bottom));
  padding-left: max(1rem, env(safe-area-inset-left));
}

ここでも「iPhoneだから左はこの値」のように決めるのではなく、ブラウザから与えられた現在の環境値を使う。

safe-area-inset-toprightbottomleft は、それぞれ独立した値として定義されている。

ヒンジを意識したいUIにはViewport Segmentsがある

通常のレスポンシブ対応だけでは足りないUIもある。

たとえば、左右にツールを配置する編集画面、地図と詳細を並べるアプリ、メールの一覧と本文を左右へ分けるUIでは、中央のヒンジをまたいで重要なボタンを置きたくない。

Webには、こうした複数領域を扱うViewport Segments APIがある。

CSSでは horizontal-viewport-segmentsvertical-viewport-segments を使い、env(viewport-segment-*) から各領域の寸法や位置を取得できる。

@media (horizontal-viewport-segments: 2) {
  .workspace {
    display: grid;
    grid-template-columns:
      env(viewport-segment-width 0 0)
      env(viewport-segment-width 1 0);
    column-gap: calc(
      env(viewport-segment-left 1 0) -
      env(viewport-segment-right 0 0)
    );
  }
}

この仕組みを使えれば、「幅が広いから2カラム」からさらに一歩進み、物理的に分かれた利用可能領域へカラムを合わせられる。

ただし、2026年9月時点でViewport Segments APIや関連メディア特性はMDNでも Limited availabilityExperimental とされている。

そのため、これをDuo対応の土台にはしない方がいい。SafariでDuoのfold情報がWebへどう公開されるかも、実際のWebKit環境で確認したい部分として残る。

まず通常のレスポンシブUIとして成立させ、その上でAPIを利用できるブラウザではヒンジを避ける。Progressive Enhancementとして扱うのが現実的だと思う。

外画面と内画面で別のDOMを持たせない

Duoの設計で興味深いのは、Appleが外画面と内画面を別アプリのように切り替えないことを重視している点だ。

メールなら、閉じた状態で一覧または本文を表示し、開いたら一覧と本文を並べる。選択中のメールやナビゲーション状態はそのまま引き継ぐ。

Webでも同じで、幅によって完全に別のUIツリーへ差し替える実装は慎重にしたい。

return isMobile
  ? <MobileMailApp />
  : <DesktopMailApp />;

この形でも作れるが、2つのコンポーネントで状態や操作を揃え続ける必要がある。リサイズ時に片方を破棄してもう片方を作るなら、スクロール位置、選択状態、入力途中の内容なども引き継がなければならない。

情報構造が同じなら、同じDOMやコンポーネントツリーを保ったまま配置を変えた方が単純なことが多い。

compact
一覧 → 詳細

wide
一覧 | 詳細

変わるのは情報そのものではなく、表示できる量と配置だ。

DuoでAppleが言う「ひとつの体験を、利用可能な場所へ適応させる」という考え方は、Webでもこの部分に効いてくる。

iPhone Duo向けのWeb表示をどう検証するか

Duo実機がなくても、レスポンシブ設計の大部分は今から確認できる。

ただし、何をシミュレーションできるのかは分けて考えた方がいい。

SafariのResponsive Design Modeで幅の変化を試す

SafariのResponsive Design Modeでは、viewportの幅と高さを任意に変更でき、Pixel Ratioも指定できる。

Apple自身も、ここで表示されるプリセットは実機の正確なレイアウトや挙動を再現するものではなく、実機に近い確認にはSimulatorを使うよう案内している。

つまりResponsive Design Modeは、Duoそのものを再現するツールではない。

一方で、この記事で扱っている available space → layout を試す用途には十分使える。

Responsive Design Mode

narrow
┌──────────┐
│          │
└──────────┘

medium
┌────────────────┐
│                │
└────────────────┘

wide
┌──────────────────────────┐
│                          │
└──────────────────────────┘

幅を連続的に変えて、カラムが切り替わる境界や途中の幅で崩れないかを見る。

Android Foldable Emulatorでも大部分は試せる

折りたたみによって狭い画面と広い画面を行き来すること自体は、Duoだけの仕組みではない。

Android EmulatorにはPixel FoldなどのFoldableプロファイルがあり、Fold / Unfoldを切り替えられる。Resizable Emulatorでもphone、foldable、tabletを切り替えてレイアウトを確認できる。

Android Foldable Emulator

Fold
┌──────────┐
│  Chrome  │
└──────────┘

      ↓ Unfold

┌──────────────────────────┐
│          Chrome          │
└──────────────────────────┘

ここで確認できるのは、開閉でviewportが変わったときにレイアウトや状態が追従するかという部分だ。

Duoの物理解像度やSafari固有のsafe areaまで再現するものではないが、Container Queries、ブレークポイント、状態保持、Fold / Unfold時のリサイズ対応などは先に検証できる。

Duo SimulatorではSafari固有部分を確認する

AppleはXcode 27.1のDevice HubからiPhone Duo Simulatorを起動し、開く、閉じる、回転する、途中まで折るといったポーズを画面上から変更できるとしている。

ただし、2026年9月14日時点ではApple DeveloperのiPhone Duoページで Xcode 27.1 betaは「Coming later this month」 と案内されている。

そのため、現時点ではResponsive Design ModeやAndroid Foldable Emulatorで先にレスポンシブ部分を確認し、Xcode 27.1 betaが公開されたらDuo SimulatorのSafariで最終確認する流れになる。

Duo Simulatorで確認したいのは、主にApple固有の差分だ。

  • 外画面と内画面それぞれの window.innerWidth
  • devicePixelRatio
  • visualViewport のサイズ
  • Safariのツールバーを含むviewport変化
  • 各辺のsafe area
  • Split View時の実際のCSS viewport
  • Half-open時にWebKitから取得できる情報
  • Viewport Segments関連機能の実際の対応状況

Android Foldableで確認した内容を全部やり直すというより、最後にSafari/WebKitとの差分を埋める作業になると思う。

Safariで実測するためのページを用意しておく

Simulatorが公開されたら、目視だけでなく実際の値も記録したい。

たとえば、次のようなページを1つ用意しておけば、外画面、内画面、Split Viewを切り替えながらviewportとsafe areaを確認できる。

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta
    name="viewport"
    content="width=device-width, initial-scale=1, viewport-fit=cover"
  >
  <title>Viewport metrics</title>
  <style>
    #safe-area-probe {
      position: fixed;
      visibility: hidden;
      pointer-events: none;
      padding-top: env(safe-area-inset-top, 0px);
      padding-right: env(safe-area-inset-right, 0px);
      padding-bottom: env(safe-area-inset-bottom, 0px);
      padding-left: env(safe-area-inset-left, 0px);
    }
  </style>
</head>
<body>
  <pre id="metrics"></pre>
  <div id="safe-area-probe" aria-hidden="true"></div>

  <script type="module">
    const metrics = document.querySelector("#metrics");
    const probe = document.querySelector("#safe-area-probe");

    const update = () => {
      const safeArea = getComputedStyle(probe);

      const data = {
        innerWidth: window.innerWidth,
        innerHeight: window.innerHeight,
        screenWidth: window.screen.width,
        screenHeight: window.screen.height,
        devicePixelRatio: window.devicePixelRatio,
        visualViewport: window.visualViewport
          ? {
              width: window.visualViewport.width,
              height: window.visualViewport.height,
              scale: window.visualViewport.scale,
            }
          : null,
        safeArea: {
          top: safeArea.paddingTop,
          right: safeArea.paddingRight,
          bottom: safeArea.paddingBottom,
          left: safeArea.paddingLeft,
        },
      };

      metrics.textContent = JSON.stringify(data, null, 2);
    };

    update();
    window.addEventListener("resize", update);
    window.visualViewport?.addEventListener("resize", update);
  </script>
</body>
</html>

screen.width よりも、実際にレイアウトへ使える window.innerWidthvisualViewport.width の変化を中心に見る。

たとえば結果を次の順で記録しておけば、DuoのWeb表示がかなり見えやすくなる。

Outer display

Inner display / Full screen

Inner display / Split View

Rotate

Half-open

ここで初めて「DuoのSafariでは全画面時に何CSS pxになるのか」を実測値として書ける。

まず既存コードの「画面の決め打ち」を探したい

私ならDuo向けの作業を始めるとき、専用CSSを書く前に既存コードを確認する。

特に見たいのは次のような実装だ。

  • User-Agentからスマホ・タブレットを判定してレイアウトを切り替えている
  • 初期表示時の window.innerWidth だけでUIモードを固定している
  • portrait / landscape をページ構造の条件として強く使っている
  • 左右のsafe areaが同じだと仮定している
  • 768pxや1024pxを端末カテゴリそのものとして扱っている
  • モバイル版とデスクトップ版で別の状態管理を持っている

ここを直せば、Duo以外にも効く。

タブレットのSplit View、ブラウザウィンドウのリサイズ、AndroidのFoldable、将来の別の折りたたみ端末でも同じ設計を使えるからだ。

テストする幅も「代表的な端末サイズ」だけでは足りない

実装を available space 基準に変えても、テストがiPhoneとデスクトップの2種類だけなら見落としやすい。

Duoを意識するなら、少なくとも次の状態は確認したい。

狭い縦長
狭い横長
広い横長
途中の幅
Split View相当
リサイズ中
Fold / Unfold直後

重要なのは、特定モデルの解像度だけを再現することではない。

幅を少しずつ変えたとき、ある地点だけで操作不能にならないか。カラムが増減しても状態が残るか。極端な縦横比で固定ボタンが本文へ重ならないかを見る。

ヒンジ位置を使った最適化まで行う場合は、対応した環境でも追加確認する。通常のレスポンシブ表示が先に成立していれば、fold情報を取得できないブラウザでもページ自体は壊れない。

Duo対応というより、レスポンシブ設計の前提が変わる

iPhone Duoだけを見ると、外画面、内画面、ヒンジ、Reserved Region、縦型コントロールなど、新しく考えることが一気に増えたように見える。

ネイティブアプリでは実際にDuo固有の仕組みも増えている。AppleはiOS 27.1でReserved Regionを扱うAPIを追加し、標準コンテナもDuoの形状へ適応するようにしている。

一方、Webフロントエンドで最初にやることはそれほど特殊ではない。

Duoを検出するのではなく、端末への決め打ちを減らす。

CSS GridやFlexboxで可変にし、ページ全体はviewport、部品はContainer Queriesで利用可能な幅を見る。JavaScriptでサイズが必要ならResizeObserverを使う。画面端ではsafe areaを個別に扱う。fold情報が取れる環境では追加最適化する。

Responsive layout

available spaceに追従

safe areaを尊重

対応環境だけfold / hingeを追加最適化

しかも、この大部分はDuo実機を待たなくても確認できる。

SafariのResponsive Design Modeで幅を連続的に変え、Android Foldable EmulatorでFold / Unfold時の状態変化を見る。Duo Simulatorが使えるようになったら、SafariのCSS viewportやsafe area、WebKit固有の挙動だけを実測する。

私にはiPhone Duoが、「新しい端末向けCSSを追加してください」というより、レスポンシブを本当に端末非依存で作れているかを試す端末に見える。

mobile → tablet → desktop という分類そのものがなくなるわけではない。ただ、レイアウトを決める根拠は端末名ではなく、いま使える場所へ少しずつ移していった方がよさそうだ。