RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/開発ツール
開発ツール/2026-03-14初級

Rork Max ダークモード・テーマ設計ガイド — アプリの見た目を自在にカスタマイズ

ダークモード・ライトモード対応、カスタムテーマ設計、デザイントークン管理を解説。モダンなアプリUI実装ガイド。

rork-max41themingdark-modedesign-tokensui-design3

Rork Max ダークモード・テーマ設計ガイド

ユーザーの好みはユーザーごとに異なります。ダークモード対応は、もはやオプション機能ではなく、必須機能となっています。夜間使用時の目の疲労軽減、バッテリー省電力、個人の美的好みなど、ダークモード需要は多岐にわたります。

デザイントークンの基礎

カラーパレット管理

テーマ機能を実装する第一歩は、デザイントークン(カラー、フォントサイズなど)を一元管理することです。

// theme/tokens.js
const lightTheme = {
  colors: {
    primary: '#0066CC',
    secondary: '#FF6B6B',
    background: '#FFFFFF',
    surface: '#F5F5F5',
    text: '#222222',
    textSecondary: '#666666',
    border: '#DDDDDD',
    success: '#22C55E',
    warning: '#FBBF24',
    error: '#EF4444',
  },
  spacing: {
    xs: 4,
    sm: 8,
    md: 16,
    lg: 24,
    xl: 32,
  },
  fontSize: {
    sm: 12,
    base: 14,
    lg: 16,
    xl: 20,
    '2xl': 24,
  },
};
 
const darkTheme = {
  colors: {
    primary: '#4A9EFF',
    secondary: '#FF8A9B',
    background: '#1A1A1A',
    surface: '#2D2D2D',
    text: '#FFFFFF',
    textSecondary: '#CCCCCC',
    border: '#404040',
    success: '#4ADE80',
    warning: '#FACC15',
    error: '#F87171',
  },
  spacing: lightTheme.spacing,
  fontSize: lightTheme.fontSize,
};
 
export { lightTheme, darkTheme };

デザイントークンを集約することで、テーマ切り替えが簡潔になります。

React Context でのテーマ管理

グローバルテーマ状態管理

テーマ状態をアプリケーション全体で共有するには、React Context を使用します。

import { createContext, useState, useContext, useEffect } from 'react';
import { useColorScheme } from 'react-native';
import { lightTheme, darkTheme } from './tokens';
 
const ThemeContext = createContext();
 
export function ThemeProvider({ children }) {
  const systemColorScheme = useColorScheme();
  const [isDarkMode, setIsDarkMode] = useState(
    systemColorScheme === 'dark'
  );
 
  // システム設定を監視
  useEffect(() => {
    if (systemColorScheme) {
      setIsDarkMode(systemColorScheme === 'dark');
      console.log(`システムテーマ検出: ${systemColorScheme}`);
      // 期待出力: システムテーマ検出: dark
    }
  }, [systemColorScheme]);
 
  const theme = isDarkMode ? darkTheme : lightTheme;
 
  const toggleTheme = () => {
    setIsDarkMode(!isDarkMode);
    console.log(`テーマ切り替え: ${!isDarkMode ? 'ダーク' : 'ライト'}`);
    // 期待出力: テーマ切り替え: ダーク
  };
 
  const value = {
    isDarkMode,
    theme,
    toggleTheme,
  };
 
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}
 
export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

テーマの実装と使用

コンポーネント内でのテーマ適用

テーマトークンをコンポーネントで使用する方法を示します。

import { useTheme } from './ThemeProvider';
import { View, Text, StyleSheet } from 'react-native';
 
export function ThemedCard({ title, description }) {
  const { theme } = useTheme();
 
  const styles = StyleSheet.create({
    container: {
      backgroundColor: theme.colors.surface,
      borderColor: theme.colors.border,
      borderWidth: 1,
      borderRadius: 8,
      padding: theme.spacing.md,
      marginBottom: theme.spacing.md,
    },
    title: {
      color: theme.colors.text,
      fontSize: theme.fontSize.lg,
      fontWeight: 'bold',
      marginBottom: theme.spacing.sm,
    },
    description: {
      color: theme.colors.textSecondary,
      fontSize: theme.fontSize.base,
      lineHeight: 20,
    },
  });
 
  return (
    <View style={styles.container}>
      <Text style={styles.title}>{title}</Text>
      <Text style={styles.description}>{description}</Text>
    </View>
  );
}

このアプローチにより、テーマ変更時に自動的にコンポーネントが更新されます。

ユーザープリファレンスの永続化

テーマ設定の保存

ユーザーが選択したテーマをローカルストレージに保存し、次回起動時に復元します。

import AsyncStorage from '@react-native-async-storage/async-storage';
import { useEffect } from 'react';
 
const THEME_PREFERENCE_KEY = '@rork_theme_preference';
 
export function usePersistentTheme() {
  const { isDarkMode, toggleTheme } = useTheme();
 
  // 初期化時に保存されたテーマを読み込む
  useEffect(() => {
    loadThemePreference();
  }, []);
 
  // テーマ変更時に保存
  useEffect(() => {
    saveThemePreference(isDarkMode);
  }, [isDarkMode]);
 
  const loadThemePreference = async () => {
    try {
      const savedTheme = await AsyncStorage.getItem(THEME_PREFERENCE_KEY);
      if (savedTheme !== null) {
        const isDark = savedTheme === 'dark';
        if (isDark !== isDarkMode) {
          toggleTheme();
        }
        console.log(`保存されたテーマを読み込み: ${savedTheme}`);
        // 期待出力: 保存されたテーマを読み込み: dark
      }
    } catch (error) {
      console.error('テーマ読み込みエラー:', error);
    }
  };
 
  const saveThemePreference = async (isDark) => {
    try {
      const themeValue = isDark ? 'dark' : 'light';
      await AsyncStorage.setItem(THEME_PREFERENCE_KEY, themeValue);
    } catch (error) {
      console.error('テーマ保存エラー:', error);
    }
  };
}

ダークモード対応のベストプラクティス

アクセシビリティの考慮

ダークモード実装時は、コントラスト比に注意が必要です。

// コントラスト比が十分であることを確認
const theme = {
  colors: {
    // Light mode
    lightText: '#222222',        // 背景#FFFFFF との比: 17:1 (WCAG AAA)
    lightBg: '#FFFFFF',
 
    // Dark mode
    darkText: '#FFFFFF',         // 背景#1A1A1A との比: 16:1 (WCAG AAA)
    darkBg: '#1A1A1A',
  },
};
 
export function AccessibleText({ children, isDark }) {
  const color = isDark ? theme.colors.darkText : theme.colors.lightText;
 
  return (
    <Text style={{ color }}>
      {children}
    </Text>
  );
}

WCAG 2.1 のガイドラインでは、通常テキストのコントラスト比は 4.5:1 以上が推奨されます。

カスタムテーマの作成

ユーザー定義テーマ

上級ユーザーのために、カスタムテーマ機能を提供できます。

import { useState } from 'react';
 
export function useCustomTheme() {
  const [customThemes, setCustomThemes] = useState([]);
 
  const createCustomTheme = (name, colors) => {
    const newTheme = {
      id: Date.now(),
      name,
      colors: {
        primary: colors.primary,
        secondary: colors.secondary,
        background: colors.background,
        surface: colors.surface,
        text: colors.text,
        textSecondary: colors.textSecondary,
      },
    };
 
    setCustomThemes([...customThemes, newTheme]);
    console.log(`カスタムテーマ作成: ${name}`);
    // 期待出力: カスタムテーマ作成: MyCyan
 
    return newTheme;
  };
 
  const deleteCustomTheme = (themeId) => {
    setCustomThemes(customThemes.filter(t => t.id !== themeId));
  };
 
  return {
    customThemes,
    createCustomTheme,
    deleteCustomTheme,
  };
}
 
export function CustomThemeEditor() {
  const [themeName, setThemeName] = useState('');
  const [primaryColor, setPrimaryColor] = useState('#0066CC');
  const { createCustomTheme } = useCustomTheme();
 
  const handleCreate = () => {
    if (themeName) {
      createCustomTheme(themeName, {
        primary: primaryColor,
        secondary: '#FF6B6B',
        background: '#FFFFFF',
        surface: '#F5F5F5',
        text: '#222222',
        textSecondary: '#666666',
      });
      setThemeName('');
    }
  };
 
  return (
    <div style={{ padding: '20px' }}>
      <input
        type="text"
        value={themeName}
        onChange={(e) => setThemeName(e.target.value)}
        placeholder="テーマ名"
      />
      <input
        type="color"
        value={primaryColor}
        onChange={(e) => setPrimaryColor(e.target.value)}
      />
      <button onClick={handleCreate}>テーマを作成</button>
    </div>
  );
}

テーマ情報の継承

ダークモード実装後、カメラ・ギャラリー機能でのUI 統一についてはRork Max カメラ・ギャラリー機能ガイドを参照してください。

また、デザインシステムの統一のため、Figma との連携方法についてはRork Max Figma 連携ガイドもご覧ください。さらに、アニメーション効果をダークモード対応させる方法についてはRork Max アニメーション・ジェスチャーガイドを参照してください。

12年の個人開発で見えてきたこと

リリース直後にチェックしているもの

  • クラッシュレポートの上位エラーを24時間ごとに確認しているか
  • AdMob/StoreKit の収益ダッシュボードに異常検知が出ていないか
  • ユーザーレビューに技術的な指摘が含まれていないか

12年の個人開発から見る Rork の使いどころ

リリース直後にチェックしているもの

  • クラッシュレポートの上位エラーを24時間ごとに確認しているか
  • AdMob/StoreKit の収益ダッシュボードに異常検知が出ていないか
  • ユーザーレビューに技術的な指摘が含まれていないか

まとめ — 次の改善サイクル

シェア

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

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

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

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

関連記事

開発ツール2026-07-13
Detox と Maestro の E2E が全部緑なのに本番でクラッシュが出ていたとき — 自動リトライが握りつぶした不安定テストをフレーク率で炙り出す運用メモ
E2Eが全部緑なのに本番クラッシュが出る。原因は自動リトライがフレークを握りつぶしていたこと。テスト単位でフレーク率を計測し、隔離とリトライ予算で本物の失敗だけを残す運用メモです。
開発ツール2026-07-01
Rork Max のクラウドコンパイル — Mac を持たずにネイティブアプリを仕上げる
Rork Max のクラウドコンパイルで、ローカル Mac なしに iOS/iPadOS のネイティブアプリをビルド・実機テスト・公開する流れを、失敗時の切り分けと実機 Mac を残す判断基準まで含めて実務目線でまとめます。
開発ツール2026-06-17
誕生日を集めずに年齢を確認する — Declared Age Range API を Rork アプリに組み込む
iOS 26 の Declared Age Range API を使い、生年月日を保存せずに年齢層だけを受け取る実装方法。Rork Max のネイティブ Swift と、標準 Rork(Expo)のネイティブモジュール越しの呼び出しを、責務の線引きとともに整理します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →