building-native-ui について
building-native-uiは、Expoアプリ開発者向けのUIガイドスキルです。ネイティブUI、ナビゲーション、スタイル、レスポンシブレイアウトを扱い、Expo Goの活用方法や各種リファレンスも示します。
主な機能
- 01Expo Routerのルートとナビゲーション規約
- 02ネイティブコントロール、フォーム、メディアの資料
- 03アニメーション、アイコン、グラデーションの指針
- 04レスポンシブレイアウトとセーフエリアの推奨事項
- 05Expo Goとカスタムビルドの選択ガイド
活用シーン
- 01Expoアプリのルート構成の設計
- 02iOS UI向けネイティブコントロールと操作の選定
- 03レイアウト、スタイル、ナビゲーション実装の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ midudev/ autoskills --skill building-native-uiインストール後、Claude Code で/building-native-uiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/animations.mdcontrols.mdform-sheet.mdgradients.mdicons.mdmedia.mdroute-structure.mdsearch.mdstorage.mdtabs.mdtoolbar-and-headers.mdvisual-effects.mdwebgpu-three.mdzoom-transitions.md詳細
Expo UI ガイドライン
参考資料
必要に応じて、以下のリソースを参照してください。
references/
animations.md Reanimated: entering, exiting, layout, scroll-driven, gestures
controls.md Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker
form-sheet.md Form sheets in expo-router: configuration, footers and background interaction.
gradients.md CSS gradients via experimental_backgroundImage (New Arch only)
icons.md SF Symbols via expo-image (sf: source), names, animations, weights
media.md Camera, audio, video, and file saving
route-structure.md Route conventions, dynamic routes, groups, folder organization
search.md Search bar with headers, useSearch hook, filtering patterns
storage.md SQLite, AsyncStorage, SecureStore
tabs.md NativeTabs, migration from JS tabs, iOS 26 features
toolbar-and-headers.md Stack headers and toolbar buttons, menus, search (iOS only)
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
zoom-transitions.md Apple Zoom: fluid zoom transitions with Link.AppleZoom (iOS 18+)
アプリの実行
重要:カスタムビルドを作成する前に、必ず最初に 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を介したウィジェット、App Clip、拡張機能) - Expo Go に含まれていないサードパーティ製ネイティブモジュール
app.jsonでは表現できないカスタムネイティブ設定
Expo Go が使える場合
Expo Go は、幅広い機能を標準でサポートしています。
- すべての
expo-*パッケージ(カメラ、位置情報、通知など) - Expo Router によるナビゲーション
- ほとんどの UI ライブラリ(reanimated、gesture handler など)
- プッシュ通知、ディープリンクなど
迷った場合は、まず Expo Go を試してください。 カスタムビルドを作成すると複雑さが増し、反復作業が遅くなるうえ、Xcode/Android Studio のセットアップが必要になります。
コードスタイル
- 文字列の閉じ忘れに注意してください。ネストされたバッククォートはエスケープし、引用符も必ず正しくエスケープしてください。
- ファイルの先頭に必ず import 文を記述してください。
- ファイル名には必ずケバブケースを使用してください(例:
comment-card.tsx)。 - ナビゲーションを移動または再構成する場合は、古いルートファイルを必ず削除してください。
- ファイル名に特殊文字を使用しないでください。
- tsconfig.json でパスエイリアスを設定し、リファクタリング時は相対インポートよりエイリアスを優先してください。
ルート
詳細なルートの規約については、./references/route-structure.md を参照してください。
- ルートは
appディレクトリに配置します。 - コンポーネント、型、ユーティリティを app ディレクトリに同居させないでください。これはアンチパターンです。
- グループルート内でも構いませんので、アプリには必ず「/」に一致するルートを用意してください。
ライブラリの推奨事項
- Picker、WebView、SafeAreaView、AsyncStorage など、React Native から削除されたモジュールは使用しないでください。
- レガシーな expo-permissions は使用しないでください。
expo-avではなくexpo-audioを使用してください。expo-avではなくexpo-videoを使用してください。- SF Symbols には
expo-symbolsや@expo/vector-iconsではなく、source="sf:name"を指定したexpo-imageを使用してください。 - 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を使用してください。
レスポンシブ対応
- レスポンシブ対応のため、ルートコンポーネントは必ずスクロールビューでラップしてください。
<SafeAreaView>の代わりに<ScrollView contentInsetAdjustmentBehavior="automatic" />を使用し、より適切なセーフエリアのインセットを適用してください。- FlatList と SectionList にも
contentInsetAdjustmentBehavior="automatic"を適用してください。 - Dimensions API ではなく Flexbox を使用してください。
- 画面サイズの計測には、
Dimensions.get()よりも必ずuseWindowDimensionsを優先してください。
動作
- より心地よい体験を実現するため、iOS では条件に応じて expo-haptics を使用してください。
- React Native の
<Switch />や@react-native-community/datetimepickerなど、触覚フィードバックが組み込まれたビューを使用してください。 - ルートが Stack に属する場合、最初の子要素にはほとんどの場合、
contentInsetAdjustmentBehavior="automatic"を設定した ScrollView を指定してください。 - ページに
ScrollViewを追加する場合は、ほとんどの場合、ルートコンポーネント内の最初のコンポーネントとして配置してください。 - 検索バーを追加するには、Stack.Screen のオプションで
headerSearchBarOptionsを優先して使用してください。 - コピーできるデータを含むテキストには、
<Text selectable />プロパティを使用してください。 - 1.4M や 38k のように、大きな数値の書式設定を検討してください。
- WebView または Expo DOM コンポーネント内を除き、'img' や 'div' などの組み込み要素は使用しないでください。
スタイル
Apple Human Interface Guidelines に従ってください。
一般的なスタイル設定ルール
- margin や padding のスタイルより、flex gap を優先します
- 可能な場合は、margin より padding を優先します
- スタックヘッダー、タブ、または ScrollView/FlatList の
contentInsetAdjustmentBehavior="automatic"を使い、常にセーフエリアを考慮します - 上部と下部の両方のセーフエリアインセットが考慮されていることを確認します
- スタイルを再利用するほうが効率的な場合を除き、StyleSheet.create ではなくインラインスタイルを使用します
- 状態が変化するときは、開始アニメーションと終了アニメーションを追加します
- カプセル形状を作成する場合を除き、角丸には
{ borderCurve: 'continuous' }を使用します - ページ上にカスタムのテキスト要素を配置せず、必ずナビゲーションスタックのタイトルを使用します
- ScrollView にパディングを設定するときは、ScrollView 自体ではなく
contentContainerStyleの padding と gap を使用します(クリッピングを軽減できます) - CSS と Tailwind はサポートされていません。インラインスタイルを使用します
テキストのスタイル設定
- 重要なデータやエラーメッセージを表示するすべての
<Text/>要素に、selectableプロパティを追加します - カウンターの位置を揃えるには、
{ fontVariant: 'tabular-nums' }を使用します
シャドウ
CSS の boxShadow スタイルプロパティを使用します。React Native の従来の shadow や elevation のスタイルは絶対に使用しないでください。
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
'inset' シャドウがサポートされています。
ナビゲーション
リンク
ルート間のナビゲーションには、'expo-router' の <Link href="/path" /> を使用します。
import { Link } from 'expo-router';
// Basic link
<Link href="/path" />
// Wrapping custom components
<Link href="/path" asChild>
<Pressable>...</Pressable>
</Link>
可能な場合は、iOS の規約に沿って <Link.Preview> を含めます。ナビゲーションを強化するため、コンテキストメニューとプレビューを積極的に追加します。
スタック
- スタックの定義には、必ず
_layout.tsxファイルを使用します - ネイティブのナビゲーションスタックには、'expo-router/stack' の Stack を使用します
ページタイトル
Stack.Screen の options でページタイトルを設定します。
<Stack.Screen options={{ title: "Home" }} />
コンテキストメニュー
Link コンポーネントに長押しのコンテキストメニューを追加します。
import { Link } from "expo-router";
<Link href="/settings" asChild>
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Menu>
<Link.MenuAction
title="Share"
icon="square.and.arrow.up"
onPress={handleSharePress}
/>
<Link.MenuAction
title="Block"
icon="nosign"
destructive
onPress={handleBlockPress}
/>
<Link.Menu title="More" icon="ellipsis">
<Link.MenuAction title="Copy" icon="doc.on.doc" onPress={() => {}} />
<Link.MenuAction
title="Delete"
icon="trash"
destructive
onPress={() => {}}
/>
</Link.Menu>
</Link.Menu>
</Link>;
リンクプレビュー
ナビゲーションを強化するため、リンクプレビューを積極的に使用します。
<Link href="/settings">
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Preview />
</Link>
リンクプレビューはコンテキストメニューと併用できます。
モーダル
画面をモーダルとして表示します。
<Stack.Screen name="modal" options={{ presentation: "modal" }} />
カスタムのモーダルコンポーネントを作成するより、こちらを優先してください。
シート
画面を動的なフォームシートとして表示します。
<Stack.Screen
name="sheet"
options={{
presentation: "formSheet",
sheetGrabberVisible: true,
sheetAllowedDetents: [0.5, 1.0],
contentStyle: { backgroundColor: "transparent" },
}}
/>
contentStyle: { backgroundColor: "transparent" }を使用すると、iOS 26 以降で背景がリキッドグラスになります。
一般的なルート構成
各タブ内にスタックを持つ、標準的なアプリのレイアウトです。
app/
_layout.tsx — <NativeTabs />
(index,search)/
_layout.tsx — <Stack />
index.tsx — Main list
search.tsx — Search view
// app/_layout.tsx
import { NativeTabs, Icon, Label } from "expo-router/unstable-native-tabs";
import { Theme } from "../components/theme";
export default function Layout() {
return (
<Theme>
<NativeTabs>
<NativeTabs.Trigger name="(index)">
<Icon sf="list.dash" />
<Label>Items</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="(search)" role="search" />
</NativeTabs>
</Theme>
);
}
両方のタブから共通画面へ遷移できるよう、共有のグループルートを作成します。
// app/(index,search)/_layout.tsx
import { Stack } from "expo-router/stack";
import { PlatformColor } from "react-native";
export default function Layout({ segment }) {
const screen = segment.match(/\((.*)\)/)?.[1]!;
const titles: Record<string, string> = { index: "Items", search: "Search" };
return (
<Stack
screenOptions={{
headerTransparent: true,
headerShadowVisible: false,
headerLargeTitleShadowVisible: false,
headerLargeStyle: { backgroundColor: "transparent" },
headerTitleStyle: { color: PlatformColor("label") },
headerLargeTitle: true,
headerBlurEffect: "none",
headerBackButtonDisplayMode: "minimal",
}}
>
<Stack.Screen name={screen} options={{ title: titles[screen] }} />
<Stack.Screen name="i/[id]" options={{ headerLargeTitle: false }} />
</Stack>
);
}