expo-native-ui について
expo-native-ui は、Expo アプリの開発者やコーディングエージェント向けの UI 開発スキルです。レイアウト、スタイル、プラットフォームごとの設計指針、ネイティブ UI コンポーネントの選択方法を扱います。
主な機能
- 01Expo Router のレイアウト指針
- 02プラットフォーム別スタイル規約
- 03ネイティブのセマンティックカラー指針
- 04Expo Go とビルドの推奨事項
- 05UI のリファレンス資料
活用シーン
- 01Expo アプリ画面の構成
- 02iOS と Android の設計規約の適用
- 03Expo Go とカスタムビルドの選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ expo/ skills --skill expo-native-uiインストール後、Claude Code で/expo-native-uiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlreferences/controls.mdgradients.mdicons.mdmedia.mdstorage.mdvisual-effects.mdwebgpu-three.md詳細
Expo Native UI ガイドライン
ルート、リンク、スタック、タブ、モーダル、シート、ヘッダーには、expo-router スキルを使用してください。入退場、ジェスチャー、スプリング、キーボード操作に連動する UI など、モーションには expo-animation スキルを使用してください。
UI コンポーネントを選ぶ前に、必ず最初に
expo-uiを確認してください。@expo/uiは、BottomSheet、Button、Picker、Slider、Menu、Section、Switch、SegmentedControl など、ネイティブに相当するコンポーネントを提供します。iOS では実際の SwiftUI として、Android では Jetpack Compose として描画され、SDK 56 以降ではカスタムビルドなしで Expo Go から利用できます。React Native の組み込み機能やコミュニティライブラリに切り替える前に、適切なコンポーネントを見つけるためexpo-uiスキルを読み込んでください。このスキル(expo-native-ui)では、Expo Router のナビゲーション、レイアウト、スタイリング、視覚効果など、周辺の構造を扱います。
参考資料
必要に応じて、以下のリソースを参照してください。
references/
controls.md Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker
gradients.md CSS gradients via experimental_backgroundImage (New Arch only)
icons.md SF Symbols via expo-symbols SymbolView: names, weights, animations; Material icons on Android
media.md Camera, audio, video, and file saving
storage.md SQLite, AsyncStorage, SecureStore
visual-effects.md Blur (expo-blur) and liquid glass (expo-glass-effect)
webgpu-three.md 3D graphics, games, GPU visualizations with WebGPU and Three.js
アプリの実行
重要:カスタムビルドを作成する前に、必ず Expo Go を試してください。
ほとんどの Expo アプリは、カスタムのネイティブコードなしで Expo Go 上で動作します。npx expo run:ios または npx expo run:android を実行する前に、次の手順を行ってください。
- まず Expo Go を試す:
npx expo startを実行し、Expo Go で QR コードをスキャンします - 機能が動作するか確認する:Expo Go でアプリを十分にテストします
- 必要な場合に限りカスタムビルドを作成する — 詳細は以下を参照してください
カスタムビルドが必要な場合
次の機能を使用する場合に限り、npx expo run:ios/android または eas build が必要です。
- ローカル Expo モジュール(
modules/内のカスタムネイティブコード) - Apple ターゲット(
@bacons/apple-targetsを介したウィジェット、アプリクリップ、拡張機能) - Expo Go に含まれていないサードパーティ製ネイティブモジュール
app.jsonでは表現できないカスタムのネイティブ設定
Expo Go が使える場合
Expo Go は、標準で幅広い機能に対応しています。
- ほとんどの
expo-*パッケージ(カメラ、位置情報、センサー、sqlite など) — ただし、すべてではありません。SDK 53 以降、Android の Expo Go ではリモートプッシュ通知は動作しません。また、一部のパッケージには Expo Go に含まれていないネイティブ機能が必要です(例:WebGPU —references/webgpu-three.mdを参照) - Expo Router のナビゲーションとディープリンク
- ほとんどの UI ライブラリ(reanimated、gesture handler など)
迷った場合は、まず Expo Go を試してください。 カスタムビルドを作成すると、複雑さが増し、反復の速度が落ちるうえ、Xcode/Android Studio のセットアップが必要になります。
コードスタイル
- 文字列の閉じ忘れに注意してください。入れ子になったバッククォートはエスケープし、引用符を正しくエスケープしてください。
- import 文は必ずファイルの先頭に記述してください。
- ファイル名には必ず kebab-case を使用してください。例:
comment-card.tsx - ファイル名に特殊文字を使用しないでください。
- tsconfig.json でパスエイリアスを設定し、リファクタリングでは相対インポートよりエイリアスを優先してください。
ライブラリの選択
- シート、ピッカー、スライダー、トグル、メニュー、またはグループ化されたフォームのセクションには、React Native の組み込み機能やコミュニティライブラリを使う前に、まず
@expo/uiを使用してください(expo-uiスキルを参照してください) — ネイティブの SwiftUI/Compose としてレンダリングされ、SDK 56 以降の Expo Go で動作します。グループ化された設定形式の行(短く、長さが固定されたもの)には、@expo/uiのListとListItemを使用してください。大量または長さが不定のスクロールリスト(フィード、検索結果、カタログ)には、FlatListまたはFlashListを使用してください。@expo/uiのListは仮想化されていません。 - React Native から削除された Picker、WebView、SafeAreaView、AsyncStorage などのモジュールは使用しないでください。
- 非推奨の
expo-permissionsは使用しないでください。 expo-avではなくexpo-audioを使用してください。expo-avではなくexpo-videoを使用してください。- iOS の SF Symbols には
@expo/vector-iconsではなく、expo-symbols(SymbolView)を使用してください —references/icons.mdを参照してください。SF Symbols は Apple 専用です。Android では、すべてのアイコンに Material のソースが必要です(NativeTabsのトリガーではmdプロパティを使用し、画面内のオプションについては icons.md の「Android: Material Icons」を参照してください)。SF Symbols のみのアイコン表現は使用しないでください。 - React Native の
SafeAreaViewではなく、react-native-safe-area-contextを使用してください。 Platform.OSではなく、process.env.EXPO_OSを使用してください。React.useContextではなく、React.useを使用してください。- 組み込み要素の
imgではなく、expo-imageの Image コンポーネントを使用してください。 - 液体ガラスの背景には
expo-glass-effectを使用してください。 - ネイティブのセマンティックカラーには、
PlatformColorの生の値ではなく、expo-routerのColorを使用してください(型安全で、ライト/ダークモードに自動対応します)。 - SDK 56 以降では、
@react-navigation/*から直接インポートしないでください。代わりにexpo-router/react-navigationを使用してください(@react-navigation/native、/core、/elements、/routersをカバーします)。
レスポンシブ対応
- スクロール可能なコンテンツを含む画面は、ScrollView で囲んでください。ルートが FlatList/FlashList の画面に外側の ScrollView を追加してはいけません(リスト自体がスクロールコンテナーです)。また、全画面表示の画面(カメラ、マップ、キャンバス)では、どちらも必要ありません。
- より適切なセーフエリアのインセットを設定するには、
<SafeAreaView>ではなく<ScrollView contentInsetAdjustmentBehavior="automatic" />を使用してください。 contentInsetAdjustmentBehavior="automatic"は、FlatList と SectionList にも適用してください。- Dimensions API ではなく、フレックスボックスを使用してください。
- 画面サイズの測定には、
Dimensions.get()よりも常にuseWindowDimensionsを優先してください。
動作
- iOS では expo-haptics を条件付きで使用し、より心地よい体験を実現します
- React Native の
<Switch />や@react-native-community/datetimepickerなど、触覚フィードバックが組み込まれたビューを使用します - Stack ルートにスクロール可能なコンテンツがある場合は、ルート内の最初のコンポーネントを ScrollView(または FlatList)にし、
contentInsetAdjustmentBehavior="automatic"を設定します - コピーできるデータを含むテキストには、
<Text selectable />プロパティを使用します - 1.4M や 38k のように、大きな数値の書式を検討します
- WebView または Expo DOM コンポーネント内でない限り、'img' や 'div' などの組み込み要素は使用しません
- データを読み込むすべての画面には、読み込み中、エラー、空、コンテンツの4つの状態があります。初回の読み込みが完了する前に空の状態を表示してはいけません。ルールは
expo-data-fetchingスキルに記載されています - スクロール可能なフォームや検索結果では、
keyboardShouldPersistTaps="handled"を使用します。これにより、コントロールが最初のタップを受け取り、処理されないタップでキーボードを閉じられます。処理されないタップでもキーボードを開いたままにする場合に限り、"always"を使用します - フォームの主要なアクションをキーボードの下に置いてはいけません。キーボードの実際のフレームを追跡する UI には、
expo-animationスキルのキーボードレシピ(react-native-keyboard-controller)を読み込みます。Keyboard.addListenerとタイミングアニメーションを組み合わせてはいけません - 有効なコントロールはすべて、表示されている説明どおりのアクションを実行する必要があります。検索では結果が絞り込まれ、保存では編集内容が確定され、設定は動作に反映されます。空のハンドラーや成功アラートだけでは実装とは言えません。プロトタイプを求められた場合は、ローカル状態だけで十分です
- 非同期の保存では、
expo-data-fetchingに従って下書きを保持し、処理中や失敗の状態に対応します。保存に成功する前にフォームを閉じてはいけません
画面の完成前に、データの読み込みや保存時の失敗と復旧も含め、主要なタスクを一通り確認します。キーボード操作と戻る/閉じる動作も確認します。長いタイトル、画像の欠落、検索結果がない場合、大きなシステム文字サイズも試し、必要な操作に引き続きアクセスできることを確認します。実際に確認した項目と、実行できなかった項目を報告します。
スタイル
各プラットフォーム独自のデザイン言語に従います。iOS では Apple Human Interface Guidelines、Android では Material Design 3 を使用します。一方のプラットフォームをもう一方の見た目にしてはいけません。iOS のレイアウトでは FAB やリップルを使わず、Android では手作りの iOS 風 UI(戻るシェブロン、大きなタイトル文字、iOS 風スイッチ)を使いません。
一般的なスタイルルール
- margin や padding のスタイルよりも、flex の gap を優先します
- 可能な場合は margin より padding を優先します
- Stack ヘッダー、タブ、または ScrollView/FlatList の
contentInsetAdjustmentBehavior="automatic"を使い、常にセーフエリアを考慮します - 上下両方のセーフエリアインセットを必ず考慮します
- スタイルを再利用するほうが速い場合を除き、StyleSheet.create ではなくインラインスタイルを使用します
- モーションやアニメーションを扱う場合は、
expo-animationスキルを読み込みます。アニメーションを使うかどうかの判断、タイミング値、中断時のルールはこのスキルに従います - カプセル形状を作成する場合を除き、角丸には
{ borderCurve: 'continuous' }を使用します - ページ上にカスタムのテキスト要素を置かず、必ずナビゲーションスタックのタイトルを使用します
- ScrollView にパディングを設定する場合は、ScrollView 自体ではなく
contentContainerStyleに padding と gap を設定します(クリッピングを軽減します) - CSS と Tailwind はサポートされていません。インラインスタイルを使用します
Colors
ネイティブのセマンティックカラーには、expo-router の Color API を使用します。これは PlatformColor を型安全にラップしたもので、iOS の UIKit カラーを Color.ios.* 経由で、Android の Material 3 カラーを Color.android.material.*(静的)または Color.android.dynamic.*(Android 12 以降でユーザーの壁紙に合わせて調整)経由で公開します。これらのカラーはデバイス上で解決され、ライト/ダークモードやアクセシビリティ設定に自動的に適応するため、ライト/ダーク用の 16 進数カラー表を別々に管理したり、colors.web.ts ファイルを用意したりする必要はありません。
Color はプラットフォーム固有のため、各値を Platform.select でラップし、Web 用の default の 16 進数カラーを指定します。パレットは theme/colors.ts に集約し、すべての場所で colors をインポートします。
// theme/colors.ts
import { Platform } from "react-native";
import { Color } from "expo-router";
export const colors = {
label: Platform.select({
ios: Color.ios.label,
android: Color.android.dynamic.onSurface,
default: "#000000",
})!,
secondaryLabel: Platform.select({
ios: Color.ios.secondaryLabel,
android: Color.android.dynamic.onSurfaceVariant,
default: "#3c3c43",
})!,
separator: Platform.select({
ios: Color.ios.separator,
android: Color.android.dynamic.outlineVariant,
default: "#c6c6c8",
})!,
systemBackground: Platform.select({
ios: Color.ios.systemBackground,
android: Color.android.dynamic.surface,
default: "#ffffff",
})!,
secondarySystemBackground: Platform.select({
ios: Color.ios.secondarySystemBackground,
android: Color.android.dynamic.surfaceVariant,
default: "#f2f2f7",
})!,
systemBlue: Platform.select({
ios: Color.ios.systemBlue,
android: Color.android.dynamic.primary,
default: "#007aff",
})!,
};
import { colors } from "@/theme/colors";
<View style={{ backgroundColor: colors.systemBackground }}>
<Text style={{ color: colors.label }}>Title</Text>
</View>;
- iOS では、システムテーマが変更されると、これらのカラーは自動的に再解決されます。Android では、テーマが切り替わったときに再レンダリングされるよう、これらを表示するコンポーネント内で
useColorScheme()を呼び出します(React Compiler がコンポーネントをメモ化する場合に必要です)。 Color/PlatformColorの値を Reanimated のスタイルに渡さないでください。これらは文字列ではなく、不透明なネイティブカラーオブジェクトです。代わりに静的カラーを使用します。Platform.select({...})!の戻り値はstring | OpaqueColorValueです。React Native のスタイル用プロパティの多くはColorValue(string | OpaqueColorValue)を受け取るため、そのまま使用できます。ただし、一部のサードパーティ製プロパティはstringのみを受け取ります(例:expo-imageのtintColor)。必要に応じてキャストしてください:colors.label as string。
テキストのスタイル設定
- 重要なデータやエラーメッセージを表示するすべての
<Text/>要素にselectableプロパティを追加します。 - カウンターの配置を揃えるには、
{ fontVariant: 'tabular-nums' }を使用します。
シャドウ
CSS の boxShadow スタイルプロパティを使用します。従来の React Native のシャドウスタイルや elevation スタイルは絶対に使用しないでください。
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
inset シャドウもサポートされています。
フィードバックの送信
このスキルにエラー、誤解を招く情報、または古い情報を見つけた場合は、Expo の改善に役立てるため報告してください。
npx --yes @expo/agent-cli@latest feedback --category skills --subject "expo-native-ui" "<actionable feedback>"
具体的で実行可能な報告内容がある場合にのみ送信してください。関連するコンテキストをできるだけ詳しく含めてください。
エージェントが Expo のタスクを繰り返し失敗した場合や、ユーザーが介入しなければならなかった場合は、タスクの概要、期待される結果、問題点を簡潔に報告し、--category evals と、関連する Expo パッケージまたはコマンドを --subject として指定してください。