building-ui について
building-uiは、Expo RouterとReact Nativeで画面を構築するためのスキルガイドです。ルーティング、ネイティブコントロール、スタイル、アプリの実行方法を資料と実装指針でまとめ、iOSの慣習に沿った開発を支援します。
主な機能
- 01Expo Routerのルート構成規約
- 02ネイティブコントロールとナビゲーションの指針
- 03アニメーション、メディア、ストレージの資料
- 04スタイルとレスポンシブレイアウトの規則
- 05Expo Goとカスタムビルドの指針
活用シーン
- 01Expoアプリのルート構成の計画
- 02画面に使うExpo・React Nativeライブラリの選定
- 03画面とセーフエリアに対応するレイアウトの実装
- 04カスタムネイティブビルドの要否の判断
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ gigs-slc/ react-native-skills --skill building-uiインストール後、Claude Code で/building-uiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/animations.mdcontrols.mdform-sheet.mdgradients.mdicons.mdmedia.mdroute-structure.mdsearch.mdstorage.mdtabs.mdvisual-effects.mdwebgpu-three.md詳細
Expo UI ガイドライン
参考資料
必要に応じて、以下のリソースを参照してください。
- ./references/route-structure.md -- ルートファイルの規則、動的ルート、クエリパラメーター、グループ、フォルダー構成
- ./references/tabs.md -- NativeTabs を使用したネイティブタブバー、JS タブからの移行、iOS 26 の機能
- ./references/icons.md -- expo-symbols を使用した SF Symbols、一般的なアイコン名、アニメーション、ウェイト
- ./references/controls.md -- ネイティブ iOS コントロール:Switch、Slider、SegmentedControl、DateTimePicker、Picker
- ./references/visual-effects.md -- expo-blur を使用したぼかし効果と、expo-glass-effect を使用したリキッドグラス
- ./references/animations.md -- Reanimated アニメーション:入場、退場、レイアウト、スクロール駆動、ジェスチャー
- ./references/search.md -- ヘッダーとの検索バー連携、useSearch フック、フィルタリングのパターン
- ./references/gradients.md -- experimental_backgroundImage を使用した CSS グラデーション(新アーキテクチャのみ)
- ./references/media.md -- Expo Router のメディア処理(カメラ、オーディオ、ビデオ、ファイル保存を含む)
- ./references/storage.md -- SQLite、AsyncStorage、SecureStore を含むデータストレージのパターン
- ./references/webgpu-three.md -- WebGPU と Three.js を使用した 3D グラフィックス、ゲーム、GPU を活用したビジュアライゼーション
アプリの実行
重要:カスタムビルドを作成する前に、必ず最初に 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-*パッケージ(カメラ、位置情報、通知など) - 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を使用してください。@expo/vector-iconsではなくexpo-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を使用してください。
レスポンシブ対応
- レスポンシブ対応のため、ルートコンポーネントは必ずスクロールビューでラップしてください
- よりスマートにセーフエリアのインセットを調整するには、
<SafeAreaView>ではなく<ScrollView contentInsetAdjustmentBehavior="automatic" />を使用してください contentInsetAdjustmentBehavior="automatic"は FlatList と SectionList にも適用してください- Dimensions API ではなく Flexbox を使用してください
- 画面サイズの測定には、
Dimensions.get()よりも必ずuseWindowDimensionsを優先してください
動作
- より心地よい体験を実現するため、iOS では条件に応じて expo-haptics を使用してください
- React Native の
<Switch />や@react-native-community/datetimepickerなど、触覚フィードバックを内蔵したビューを使用してください - ルートが Stack に属する場合、最初の子要素にはほとんどの場合、
contentInsetAdjustmentBehavior="automatic"を設定した ScrollView を使用してください - 検索バーを追加するには、Stack.Screen のオプションで
headerSearchBarOptionsを優先してください - コピーできるデータを含むテキストには、
<Text selectable />プロパティを使用してください - 1.4M や 38k のように、大きな数値の書式設定を検討してください
- WebView または Expo DOM コンポーネント内でない限り、'img' や 'div' などの組み込み要素は決して使用しないでください
スタイリング
Apple Human Interface Guidelines に従ってください。
スタイリングの一般ルール
- margin や padding のスタイルよりも、Flex の gap を優先してください
- 可能な場合は margin よりも padding を優先してください
- Stack のヘッダー、タブ、または ScrollView/FlatList の
contentInsetAdjustmentBehavior="automatic"を使用して、必ずセーフエリアを考慮してください - 上部と下部の両方のセーフエリアのインセットを考慮してください
- スタイルを再利用するほうが効率的な場合を除き、StyleSheet.create ではなくインラインスタイルを使用してください
- 状態変化には、入場時と退場時のアニメーションを追加してください
- カプセル形状を作成する場合を除き、角丸には
{ borderCurve: 'continuous' }を使用してください - ページ上に独自のテキスト要素を置くのではなく、必ずナビゲーションスタックのタイトルを使用してください
- ScrollView にパディングを設定する場合は、ScrollView 自体ではなく
contentContainerStyleにパディングと 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' シャドウがサポートされています。
ナビゲーション
Link
ルート間のナビゲーションには、'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> を含めてください。ナビゲーションをより使いやすくするため、コンテキストメニューとプレビューを頻繁に追加してください。
Stack
- Stack の定義には必ず
_layout.tsxファイルを使用してください - ネイティブのナビゲーションスタックには、'expo-router/stack' の Stack を使用してください
ページタイトル
Stack.Screen のオプションでページタイトルを設定してください。
<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>
);
}