RORK LABEN
DEADLINE — 8月31日から、Google Play の新規アプリと更新はすべて Android 16(API レベル36)を対象にする必要があります。残り7日ですEXTENSION — 条件を満たす場合は Play Console のフォームで11月1日までの延長を申請できますが、申請そのものを締切前に済ませる必要がありますMAX — Rork Max は React Native ではなくネイティブ Swift を生成し、クラウド上の Mac 群でビルドします。iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessage が対象ですCOMPANION — Rork Companion アプリを使えば、有料の Apple Developer アカウントなしに実機の iPhone で動作を確認できます。設計・実装・テストをブラウザだけで回せる構成ですDEVTOOLS — Expo SDK 57 の React Native DevTools でライトモードとダークモードのエミュレーションが可能になりました。端末設定を切り替えずに両方の見え方を確認できますIOS27 — iOS 27 は9月に出ます。ベータ6が8月17日に配布され、RCS Universal Profile 3.0 対応で Android から届いたメッセージへの個別返信ができるようになりましたDEADLINE — 8月31日から、Google Play の新規アプリと更新はすべて Android 16(API レベル36)を対象にする必要があります。残り7日ですEXTENSION — 条件を満たす場合は Play Console のフォームで11月1日までの延長を申請できますが、申請そのものを締切前に済ませる必要がありますMAX — Rork Max は React Native ではなくネイティブ Swift を生成し、クラウド上の Mac 群でビルドします。iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessage が対象ですCOMPANION — Rork Companion アプリを使えば、有料の Apple Developer アカウントなしに実機の iPhone で動作を確認できます。設計・実装・テストをブラウザだけで回せる構成ですDEVTOOLS — Expo SDK 57 の React Native DevTools でライトモードとダークモードのエミュレーションが可能になりました。端末設定を切り替えずに両方の見え方を確認できますIOS27 — iOS 27 は9月に出ます。ベータ6が8月17日に配布され、RCS Universal Profile 3.0 対応で Android から届いたメッセージへの個別返信ができるようになりました
記事一覧/開発ツール
開発ツール/2026-08-24中級

ダークモードの確認を DevTools に寄せて、実機で見るべき場所が4か所に減りました

Expo SDK 57 の React Native DevTools に入ったライト/ダークのエミュレーションで、端末設定の往復をやめました。差し替わるのは JS が報告する値だけで、実機でしか確認できない場所が残ります。その境界を1画面で切り分ける方法をまとめました。

Expo182SDK 57ダークモードReact Native231DevTools

朝の30分を、設定アプリの往復に溶かしていました。

一覧画面を暗くして確認、設定を開いてライトに戻す、詳細画面を暗くして確認、また戻す。壁紙アプリのように画面のほとんどが画像で埋まるアプリだと、背景色の差が出る場所は少ないのに、確認そのものは全画面ぶん必要になります。1画面あたり10秒の切り替えでも、20画面で往復すれば7分近くが移動時間です。個人開発で本数を抱えていると、この7分がアプリの本数ぶん積み上がります。

Expo SDK 57 に上げたあと、React Native DevTools にライトモードとダークモードをエミュレートする切り替えが入っていることに気づきました。端末の設定を触らずにトグル1つで切り替わります。

正直に言えば、最初は「これで全部片づく」と思いました。実際には片づかない場所が残りました。ただ、残った場所がどこかを一度きちんと線引きしたことで、確認の手順がかなり短くなりました。その線引きを書き残しておきます。

エミュレーションが差し替えているのは「JS が報告する値」です

DevTools のトグルが何をしているのかを理解しておくと、あとの判断が速くなります。

React Native には Appearance モジュールがあり、Appearance.setColorScheme() で JS 側が報告する配色を上書きできます。DevTools のエミュレーションは、この上書きを開発ツールから叩けるようにしたものと考えると筋が通ります。

つまり、useColorScheme() の戻り値を見て色を決めているコンポーネントは、すべて切り替わります。

import { useColorScheme, View, Text } from 'react-native';
 
export function Card({ title }: { title: string }) {
  const scheme = useColorScheme(); // 'light' | 'dark' | null
  const isDark = scheme === 'dark';
 
  return (
    <View style={{ backgroundColor: isDark ? '#16181d' : '#ffffff' }}>
      <Text style={{ color: isDark ? '#e8eaed' : '#1f2328' }}>{title}</Text>
    </View>
  );
}

このコードは DevTools のトグルで完全に追従します。NativeWind の dark: 修飾子も、内部で同じ配色ソースを読んでいるので同様です。

逆に言えば、OS の配色設定をネイティブ側で直接読んでいるものは追従しません。JS が「今はダークです」と申告しても、UIKit や Android のリソース解決は元の端末設定のまま動き続けます。

ここが境界です。手元で最初に混乱したのも、この2つが同じ画面に同居していたからでした。

出所を1画面に並べて、自分のプロジェクトで境界を確かめる

仕組みの説明を信じるより、自分のプロジェクトとバージョンで一度確かめたほうが確実です。配色の申告先を3つ並べて表示するだけの診断画面を作りました。

import { useEffect, useState } from 'react';
import { Appearance, useColorScheme, View, Text, Platform } from 'react-native';
import { WebView } from 'react-native-webview';
 
const PROBE_HTML = `
<html><body style="margin:0;font:16px -apple-system,system-ui">
  <div id="out" style="padding:12px"></div>
  <script>
    const dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
    document.getElementById('out').textContent = 'WebView: ' + (dark ? 'dark' : 'light');
    document.body.style.background = dark ? '#16181d' : '#ffffff';
    document.body.style.color = dark ? '#e8eaed' : '#1f2328';
  </script>
</body></html>`;
 
export function ColorSchemeProbe() {
  const hookScheme = useColorScheme();
  const [snapshot, setSnapshot] = useState(Appearance.getColorScheme());
 
  useEffect(() => {
    const sub = Appearance.addChangeListener(({ colorScheme }) => {
      setSnapshot(colorScheme);
    });
    return () => sub.remove();
  }, []);
 
  return (
    <View style={{ gap: 8, padding: 16 }}>
      <Text>useColorScheme(): {String(hookScheme)}</Text>
      <Text>Appearance.getColorScheme(): {String(snapshot)}</Text>
      <Text>Platform: {Platform.OS}</Text>
      <WebView source={{ html: PROBE_HTML }} style={{ height: 64 }} />
    </View>
  );
}

この画面を開いたまま DevTools のトグルを操作します。上の2行が切り替わり、WebView の行が動かなければ、境界は想定どおりです。もし WebView まで追従したなら、そのバージョンでは私の前提が古いということですので、以降の4か所は改めて確認してください。

確認は1分で終わりますし、SDK を上げるたびに走らせる価値があります。バージョン間の挙動差を人づてに信じるより、手元の1画面のほうが速くて確実です。

エミュレーションでは動かない4か所

診断画面で境界を確認したうえで、実機の設定を切り替えないと見えない場所を整理しました。

場所配色の決まり方確認の手段
スプラッシュ画面 アプリ起動前に OS がネイティブリソースから描画 端末設定を切り替えてコールドスタート
ネイティブアラート・キーボード UIKit / Android のトレイトに従う 端末設定を切り替えて該当画面を開く
WebView 内の prefers-color-scheme WebView 自身が OS 設定を参照 端末設定の切り替え、または明示的な注入
Android のシステムバー背景 styles.xml と edge-to-edge の設定 端末設定を切り替えて実機で目視

このうち WebView は明示的に渡してしまうのが実務的でした。OS の設定に任せると、アプリ本体だけがダークで WebView だけが白い、という不揃いが起きます。利用規約やヘルプを WebView で出しているアプリでは目立ちます。

const scheme = useColorScheme() ?? 'light';
 
<WebView
  source={{ uri: 'https://example.com/help' }}
  injectedJavaScriptBeforeContentLoaded={`
    document.documentElement.dataset.scheme = ${JSON.stringify(scheme)};
    true;
  `}
/>

受け側の CSS を prefers-color-scheme ではなく [data-scheme="dark"] で書いておけば、アプリ側の配色ソース1つに統一できます。DevTools のトグルでも追従するようになるので、確認漏れの箇所そのものが1つ減ります。

ネイティブのキーボードは、少しだけ手当ができます。TextInputkeyboardAppearance を配色ソースから渡しておけば、iOS のキーボードだけは JS 側の判断に寄せられます。

const scheme = useColorScheme() ?? 'light';
 
<TextInput
  placeholder="検索"
  keyboardAppearance={scheme === 'dark' ? 'dark' : 'light'}
/>

検索欄のあるアプリでは、ここが揃っていないと入力のたびに違和感が出ます。逆に Alert.alert() のようなネイティブのダイアログには同種の指定がありませんので、そこは実機の切り替えで見るしかありません。見る対象を「直せるもの」と「見るしかないもの」に分けておくと、確認の時間配分が決めやすくなります。

スプラッシュ画面については、そもそも配色を分けない選択も現実的です。ブランドカラー1色で塗ってしまえば、ライトとダークの差を確認する対象から外れます。私は壁紙アプリでこの方針を取っていて、起動直後のちらつきに悩む時間がなくなりました。

Android のシステムバーは、SDK 57 に edge-to-edge まわりの修正が入っているため、上げた直後に一度は実機で見ておくことをおすすめします。ネイティブ設定を触っている場合は、Expo SDK 57 に上げる前に、prebuild で消えるネイティブ変更を洗い出すのほうを先に済ませておくと、原因の切り分けが楽になります。

リリース前の確認手順を書き直しました

線引きが済んだあと、App Store と Google Play へ提出する前に必ず通す手順として、こう固定しました。

DevTools でまとめて見る(全画面・切り替えは1回)

アプリを起動したまま、トグルをダークにして全画面を通します。この段階で見ているのは、JS 側の配色ソースを読んでいる部分だけです。文字色のコントラスト不足、影の見え方、区切り線が背景に溶ける箇所。私自身の感覚では、ここで見つかる不具合が全体の8割ほどでした。

端末設定を切り替えて実機で見る(4か所のみ)

そのうえで端末をダークにし、コールドスタート、アラートを1つ、WebView を含む画面を1つ、Android ならシステムバーを含む画面を1つ。4か所だけを見ます。全画面を往復していた頃に比べて、確認そのものが数分で終わるようになりました。

切り替え直後の再描画を1回だけ確認

エミュレーションでも実機でも、切り替えた瞬間に再描画が走ります。ここで白画面が挟まる、状態が飛ぶ、といった症状が出るなら別の問題です。テーマ切り替えでプロセスが再起動する構成にしている場合は、テーマ切替の白画面を recreate() なしで直すで扱った内容が近いはずです。

手順が短くなると、確認の頻度が上がります

この記事で書いたことは、機能そのものの目新しさより、確認の摩擦が下がったという話に近いかもしれません。

ただ、7分かかる確認は「今回は前回から変えていないから飛ばそう」と思いますが、1分の確認は毎回走らせられます。頻度が上がったぶん、ダークモード起因の不具合が本番へ抜ける回数は確実に減りました。

次の一歩として、まずは診断画面を1つプロジェクトに置いてみてください。自分のバージョンでの境界が5分でわかりますし、そのあとの確認手順を自分の手で短くできます。

配色と同じく「実機でしか本当のところがわからない」領域として、VoiceOver と Dynamic Type の扱いをRork で VoiceOver と Dynamic Type を本番品質に整える実装メモにまとめてあります。あわせて手を入れると、設定まわりの確認をひとまとめにできます。

お読みいただきありがとうございました。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-08-23
Android 16 の予測型「戻る」を、8月31日までに直すか後回しにするか
targetSdk 36 では予測型「戻る」が既定で有効になり、onBackPressed が呼ばれなくなります。期限までに必要な作業と、切り離してよい作業を分ける判断と、opt-out を config plugin に置くまでを書きました。
開発ツール2026-08-22
一括置換は全ファイル成功しました。壊れたのは、消さなかった行のほうです
生成コードに一括置換をかけると、一致した行ではなく隣の行が壊れます。運用中のプロジェクトで実際に起きた破損と、置換の不変条件を検査する20行のガードスクリプトをまとめました。
開発ツール2026-08-17
Expo UI の drop-in 置き換えで、依存が本当に1本減る条件
Expo UI が SDK 56 で安定版になり、コミュニティ製パッケージ8件に drop-in の置き換えが用意されました。import を1行入れ替えても依存が減らない場合があります。減る条件を依存グラフから機械的に判定する手順をまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →