mcpField
T
theming

theming

github.com/code-with-betoデザイン・UI144194 回インストール15 週間前に更新
リポジトリ

theming について

theming は、Expo Router アプリに共通のセマンティックカラーシステムを導入するスキルです。プラットフォーム別の色解決やライト・ダーク切り替えに対応し、ナビゲーションテーマの統合方法も案内します。

主な機能

  1. 01共通のセマンティックカラートークン
  2. 02iOS・Android のネイティブカラー解決
  3. 03ライト・ダークモードへの自動対応
  4. 04カラー・ブランド用フック
  5. 05ナビゲーションテーマ統合

活用シーン

  1. 01Expo Router アプリの色の統一
  2. 02ナビゲーション表示とアプリカラーの連動
  3. 03ライト・ダーク表示への対応
  4. 04ネイティブカラーまたは固定値の利用

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/code-with-beto/skills --skill theming

インストール後、Claude Code で/themingを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
colors.ts
config.ts
ThemeContext.tsx

詳細

Expo 統合テーマ設定

iOS と Android の両方で同時に使える単一のカラーパレットを設定します。アプリ全体に Color.ios.* や Color.android.dynamic.*(またはハードコードされた 16 進数カラー)を散りばめる代わりに、ユーザーはセマンティックトークン(background、text、link など)を使います。これらはプラットフォームごとに適切なネイティブカラーに解決され、ダーク/ライトに自動で切り替わり、単一の useColors() フックから読み取れます。

これは Platano テンプレートのパターンを、あらゆる Expo Router アプリに導入できるようパッケージ化したものです。

インストールされるもの

3 つのファイル(assets/ に含まれています。プロジェクトに合わせて調整してください):

  • config.ts — 唯一の信頼できる情報源です。セマンティックシステムトークンを "native" または 16 進数カラーに設定し、ブランドカラーも定義します。日々編集するのはこのファイルだけです。
  • colors.ts — トークンをプラットフォームごとの実際のカラーに解決し、getSystemColor、useSystemColors、ブランドヘルパー、getNavigationTheme を公開します。
  • ThemeContext.tsx — React の use を使って構築された ColorsProvider と、useColors()/useTheme()/useBrand() フックです。

さらに、プロバイダーを接続するためにルートの _layout.tsx を少し編集します。

これを機能させる 2 つの考え方

ユーザーには次の点を説明してください。これがこの仕組みの要点であり、間違えやすい 2 つのポイントです。

  1. テーマ変更時に再レンダリングする。 ネイティブカラーと Material You カラーはプラットフォームのカラーオブジェクトであり、React が差分を検出できる文字列ではありません。テーマが変わったことを知らせるのは useColorScheme() です。そのため、リゾルバーは値を参照しない場合でも、引数としてカラースキームを受け取ります。これによりダーク/ライトが明示的な依存関係となり、ユーザーが切り替えたときに値が再計算されます。ColorsProvider はルート付近で useColorScheme() を一度読み取るため、切り替えによってすべてのコンシューマーが再レンダリングされます。

  2. 2 つの ThemeProvider は役割が異なる。 ColorsProvider(このスキル)は、コンポーネントに useColors() を提供します。ナビゲーションの ThemeProvider はナビゲーションの UI(ヘッダー、タブバー、デフォルトの背景)にテーマを適用します。どちらもルートに配置します。ナビゲーション側は getNavigationTheme(dark) で接続し、UI をトークンに合わせてください。混同したり、どちらかを省いたりしないでください。Expo SDK 56 以降では、ナビゲーションのテーマ関連要素(ThemeProvider、DarkTheme、DefaultTheme、Theme)を expo-router/react-navigation からインポートします。それ以前の SDK では、代わりに @react-navigation/native からインポートします(ステップ 1 を参照)。

ステップ 1:プロジェクト、SDK、ネイティブの Color API を確認する

このスキルは Expo SDK 56 以降の Expo Router アプリを対象とします。SDK 56 では、テンプレートが使う Color API(iOS のシステムカラーと Android の Material You)と expo-router/react-navigation による再エクスポートの両方が導入されました。"native" トークンを使う前に、SDK と Color API を確認してください。

# Find the project's app dir and root layout (root-level or under src/)
ls app/_layout.tsx src/app/_layout.tsx 2>/dev/null

# Check the installed Expo SDK major version
node -e "console.log('expo', require('expo/package.json').version)" 2>/dev/null

# Confirm expo-router exports the Color API
node -e "console.log('Color export:', !!require('expo-router').Color)" 2>/dev/null
grep -rl "android.dynamic\|ios.systemBackground" node_modules/expo-router/build 2>/dev/null | head -1

確認できた内容に応じて分岐します。

  • SDK 56 以降(対象): テンプレートをそのまま使用します。ナビゲーションのテーマ関連要素は expo-router/react-navigation からインポートします。
  • SDK 56 未満: 2 つの調整が必要です。
    1. colors.ts と _layout.tsx の両方で、ThemeProvider、DarkTheme、DefaultTheme、type Theme を expo-router/react-navigation ではなく @react-navigation/native からインポートします。(以前の Expo Router はこれらを再エクスポートしていないため、React Navigation から直接インポートします。)
    2. Color API が存在しない可能性があります。SDK 56 へのアップグレードを勧めるか、16 進数カラーのフォールバックを使います。ファイルはそのままにして、config.ts の system トークンを "native" ではなく 16 進数カラーに設定します(Material You は使えませんが、それ以外はすべて動作します)。getBrandColors は引き続き動作し、Color がない場合は isAndroidDynamic が false に解決されます。

また、tsconfig.json からプロジェクトのインポートエイリアスを検出します(一般的には @/* → src/* またはプロジェクトのルートです)。インポートを書く際は、そのエイリアスに合わせてください。エイリアスがない場合は、相対パスを使います。

ステップ 2:ファイルの配置場所を決める

3 つのファイルを 1 つのフォルダーにまとめて配置し、相対インポート (./config、./colors)がそのまま機能するようにします。プロジェクトの構成に合わせてください。

  • アプリが src/app/ にある場合 → src/theme/ を作成します。
  • アプリがルートの app/ にある場合 → ルートに theme/ を作成します。

既存のテーマ/カラーのモジュールや ThemeProvider がないか、先に確認してください。すでにアプリにテーマ設定がある場合は、上書きしないでください。既存の内容を確認し、重複する部分をユーザーに説明してから、既存の構成に統合するか、これらのファイルを名前で用途が明確なフォルダーに配置し、ユーザー自身で移行できるようにします。決して確認せずに上書きしないでください。

ステップ 3:ファイルを作成する

同梱の 3 つのテンプレートを選んだフォルダーにコピーし、次の点を調整します。

  • インポートエイリアス/相対パスをプロジェクトに合わせます。
  • config.ts の system トークンと brand カラーをユーザーのブランドに合わせます(ブランドのプライマリカラー/アクセントカラーを尋ねるか、適切なデフォルト値を使い、変更箇所を伝えます)。

各アセットを読み込み、プロジェクト内に書き込んでください(シンボリックリンクは使わないでください。これらはユーザー自身のファイルになります)。

  • assets/config.ts → <theme>/config.ts
  • assets/colors.ts → <theme>/colors.ts
  • assets/ThemeContext.tsx → <theme>/ThemeContext.tsx

コメントは残してください。再レンダーの仕組みやブランド/動的な動作について説明しており、学習者が自身のコードベースで使うために必要な内容です。

ステップ 4:ルートレイアウトを接続する

既存の _layout.tsx を最小限の変更で編集し、既存のプロバイダー、画面、オプションをすべて維持してください。目標とする構成では、ツリーを ColorsProvider で囲み、カラースキームを読み取る内部コンポーネントからナビゲーションにテーマを適用します。

// SDK 56+: from "expo-router/react-navigation". Older SDKs: "@react-navigation/native".
import { ThemeProvider } from "expo-router/react-navigation";
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import { useColorScheme } from "react-native";

import { getBrandColors, getNavigationTheme } from "@/theme/colors";
import { ColorsProvider } from "@/theme/ThemeContext";

export default function RootLayout() {
  return (
    <ColorsProvider>
      {/* keep any existing providers (QueryClient, RevenueCat, gestures...) here */}
      <RootLayoutInner />
    </ColorsProvider>
  );
}

function RootLayoutInner() {
  const dark = useColorScheme() === "dark";
  const { primary } = getBrandColors(dark);

  return (
    <>
      <ThemeProvider value={getNavigationTheme(dark, primary)}>
        {/* keep the project's existing <Stack> / screens exactly as they were */}
        <Stack />
      </ThemeProvider>
      <StatusBar style={dark ? "light" : "dark"} />
    </>
  );
}

統合ルール:

  • ColorsProvider は、useColors() を呼び出すものより上位に配置する必要があります。RootLayout の最上部か、その近くに置くのが最も安全です。
  • ファイルがすでに RootLayout と内部コンポーネントに分かれている場合は、新しい内部コンポーネントを作らず、そこに ThemeProvider と useColorScheme() を追加します。
  • expo-status-bar がインストールされておらず、プロジェクトでステータスバーをすでに管理していない場合は、StatusBar の行を省略してかまいません。

ステップ 5:検証する

  • 利用できる場合は型チェックを実行します:npx tsc --noEmit(3 つのファイルに新たなエラーがないことを確認します。最もよくある問題は、インポートエイリアスの誤りです)。
  • アプリを実行してから、OS の外観設定(またはシミュレーターのダーク/ライト設定)を切り替え、カラーとナビゲーションの表示がリアルタイムで更新されることをユーザーに確認してもらいます。Android の実機では、壁紙を変更すると Material You のパレットが変わります。

ステップ 6:使い方を示す

ユーザーに短い使用例を示します(すぐに動作を確認できるよう、必要であれば画面の 1 つにテーマを適用した例を追加することも提案してください)。

import { View, Text } from "react-native";
import { useColors, useBrand } from "@/theme/ThemeContext";

export function Example() {
  const { background, text, secondaryText, separator } = useColors();
  const { primary } = useBrand();

  return (
    <View style={{ flex: 1, backgroundColor: background, padding: 16 }}>
      <Text style={{ color: text, fontSize: 20, fontWeight: "600" }}>
        Unified theming
      </Text>
      <Text style={{ color: secondaryText }}>
        Same tokens on iOS and Android.
      </Text>
      <View style={{ height: 1, backgroundColor: separator, marginVertical: 12 }} />
      <Text style={{ color: primary }}>Brand accent</Text>
    </View>
  );
}

React の外部(トーストや単発のスタイルなど)では、フックを使わないアクセサーを使用します。

import { getSystemColor } from "@/theme/colors";
const card = getSystemColor("secondaryBackground") as string;

カスタマイズ

  • すべての箇所のカラーを変更する: config.ts を編集します。トークンに 16 進数カラーを設定すると固定され、"native" に戻すと OS に従います。
  • Android でブランドカラーに Material You を使用しない: brand.useAndroidDynamic: false に設定すると、すべてのプラットフォームでブランドカラーの 16 進数値が適用されます。
  • トークンを追加する: config.ts の system に追加し、colors.ts の getNativeDefault に iOS/Android/デフォルトの各エントリーを追加したうえで、resolveSystemColors にも 1 行追加します。抜けているものがあれば TypeScript が知らせてくれます。

注意事項

  • これはカラーとブランドのみを設定します。余白や角丸のプリセット、永続化機能は意図的に追加していません。ユーザーから求められない限り、範囲を広げないでください。
  • これらのファイルはユーザー自身のコードになります。ブラックボックスとして扱わず、コメントを読み、調整するよう促してください。このスキルが提供するレッスンの目的は、ユーザーが学べるようにすることです。

コメント