stitch-react-native-components について
stitch-react-native-componentsは、モバイルレイアウトをExpoのReact Nativeコンポーネントに変換するスキルです。iOSとAndroidのネイティブ画面を作る開発者向けに、Stitch画面、HTMLファイル、URLを受け付け、TypeScriptとExpo Routerの規約に沿って構成します。
主な機能
- 01モバイルレイアウトのReact Native変換
- 02Stitch・HTMLファイル・URLからの入力
- 03Expo Routerに沿ったナビゲーション構成
- 04元レイアウトからのカラーとテーマトークン抽出
- 05ダークモードとセーフエリアのガイド
活用シーン
- 01Stitchのモバイル画面をネイティブUIに変換
- 02モバイルHTMLレイアウトをExpoコンポーネントに変換
- 03モバイルページのURLからアプリ画面を作成
MCP 対応
Stitch入力ルートでは、ツールの検出と画面メタデータの取得にStitch MCPへのアクセスが必要です。ローカルHTMLとURLのルートではStitch MCPは不要です。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ gabelul/ stitch-kit --skill stitch-react-native-componentsインストール後、Claude Code で/stitch-react-native-componentsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
resources/architecture-checklist.mdcomponent-template.tsxscripts/fetch-stitch.sh詳細
Stitch → React Native / Expo コンポーネント
あなたは React Native エンジニアです。deviceType: MOBILE で生成した Stitch の画面、ローカル HTML ファイル、または URL から、モバイル UI レイアウトを Expo を使用したクロスプラットフォームの React Native コンポーネントに変換します。TypeScript を使用し、スタイルには StyleSheet.create を使い、ナビゲーションには Expo Router の規約に従います。
このスキルを使う場面
次の場合にこのスキルを使います。
- 既存のデザインから ネイティブモバイルアプリ(iOS + Android)を作りたい
- ユーザーが「React Native」「Expo」「モバイルアプリ」「iOS」「Android」と言及している
- ソースが モバイルレイアウト(幅が狭く縦長で、タップしやすいサイズの要素を持つもの。
deviceType: MOBILEの Stitch 画面、またはモバイル向けと分かるローカルファイルや URL)
注: モバイル WebView アプリ(Capacitor、Ionic、PWA)には、代わりに stitch-html-components を使ってください。React Native が出力するのは実際のネイティブ UI であり、Web ビューではありません。
前提条件
モバイルレイアウトのソースを、構造と見た目の参照のみに使用します。ここにあるものが出荷されることはなく、RN コンポーネントは一から作成します。次のいずれかを使用できます。
- Stitch の画面 — Stitch MCP へのアクセスと、
deviceType: MOBILEで生成された画面が必要です - モバイルレイアウトの ローカル HTML ファイル — Stitch アカウントは不要です
- モバイルレイアウトを表示する URL — Stitch アカウントは不要です
どの経路を選んだ場合でも、デスクトップレイアウトは RN への変換に適しません。変換する前に、ソースの幅が狭く縦長であることを確認してください。
また、次の条件も必要です。
- 対象プロジェクトで Expo(SDK 50 以降)を使用していること — bare React Native ではありません
- ファイルベースのナビゲーションに
expo-routerを使用していること
ステップ 1:ソースを特定する
以降の処理では、temp/source.html の 1 ファイルを読み込みます。ユーザーが提供したものに合った方法で HTML をここに用意したら、ステップ 2 に進んでください。マークアップの入手元にかかわらず、以降は同じ手順です。
このスキルが対応するのは モバイルレイアウト(幅が狭く縦長で、タップしやすいサイズの要素を持つもの)のみです。ソースにかかわらず、デスクトップレイアウトは RN への変換に適しません。モバイルレイアウトかどうかの確認方法は、HTML の入手元によって異なります。
- Stitch の画面 —
deviceType: MOBILEで生成されたことを確認します。スクリーンショットがデスクトップレイアウトを示している場合は、処理を中止し、まずdeviceType: MOBILEで再生成するようユーザーに伝えてください。 - ローカル HTML ファイルまたは URL — マークアップを確認します。
<meta name="viewport">タグ、モバイルファーストのメディアクエリ、ルートコンテナの狭いmax-width、タップしやすいサイズのタップ領域などを確認してください。幅の広い複数カラムのグリッド、ホバー時のみの操作、viewport メタデータの欠如などからデスクトップレイアウトだと明らかな場合は、処理を中止し、ソースがモバイルレイアウトではないことをユーザーに伝えてください。Stitch 以外では意味のない指示なので、「deviceType: MOBILE で再生成する」よう伝えてはいけません。
Stitch の画面から取得する場合:
- 名前空間の検出 —
list_toolsを使って Stitch MCP のプレフィックスを確認します - メタデータの取得 —
[prefix]:get_screenを使ってデザイン JSON を取得します - HTML のダウンロード — GCS URL には信頼性の高いダウンローダーが必要です。
bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html" - 見た目の確認 — 変換する前に
screenshot.downloadUrlを確認します。URL に=s0を追加するとフル解像度になります。URL をそのまま使用すると、API が報告するwidth/heightにかかわらず 512px のサムネイルが返されます。上記の確認方法に従い、モバイルレイアウトであることを確認します。
ローカル HTML ファイルから取得する場合:
mkdir -p temp && cp "path/to/design.html" temp/source.html
ファイルを開き、上記の確認方法に従ってモバイルレイアウトであることを確認します。
URL から取得する場合:
bash scripts/fetch-stitch.sh "https://example.com/page" "temp/source.html"
名前に反して、このスクリプトは汎用の堅牢なダウンローダーです。リダイレクトをたどり、一時的なエラーを再試行し、gzip を処理し、結果が空の場合は明確に失敗します。URL が Stitch を指しているかどうかは問いません。上記の確認方法に従って、ページがモバイルレイアウトであることを確認します。
スクリーンショットから取得する場合: アップロードする方法はありません。Stitch MCP API には画像アップロード用のツールがありません。stitch-mcp-generate-screen-from-text を使ってテキストプロンプトからデザインを再作成するか、HTML を手書きして上記のローカルファイル経由の方法を使ってください。
API キーが必要なのは Stitch 経由の場合のみです。ローカルファイルや URL の変換には、Google アカウントは一切必要ありません。
ステップ 2:プロジェクトの構成
app/
├── (tabs)/
│ ├── _layout.tsx ← Tab navigator
│ ├── index.tsx ← Home tab
│ └── [other-tabs].tsx
├── _layout.tsx ← Root layout (ThemeProvider, SafeAreaProvider)
└── modal.tsx ← Modal routes
src/
├── components/ ← Reusable components
│ └── [Name].tsx
├── data/
│ └── mockData.ts ← Static content — never hardcoded in components
├── theme/
│ ├── tokens.ts ← Design tokens as TypeScript constants
│ └── useTheme.ts ← Hook to access current theme tokens
└── types/
└── index.ts
ステップ 3:HTML から React Native へのマッピング
これが変換の中核です。以下のルールを体系的に適用します。
レイアウトのマッピング
| HTML/CSS | → React Native |
|---|---|
<div style="display:flex; flex-direction:column"> | <View style={{flexDirection:'column'}}> |
<div style="display:flex; flex-direction:row"> | <View style={{flexDirection:'row'}}> |
<div style="display:grid; grid-template-columns:1fr 1fr"> | <View style={{flexDirection:'row', flexWrap:'wrap'}}> と width:'50%' の子要素 |
overflow-y: scroll コンテナー | <ScrollView> |
| 長いリスト | <FlatList data={items} renderItem={...} keyExtractor={...}> |
position: fixed の下部ナビゲーション | <View style={{position:'absolute', bottom:0, left:0, right:0}}> |
position: absolute のオーバーレイ | position:'relative' の親要素内に配置する <View style={{position:'absolute', ...}}> |
コンテンツのマッピング
| HTML | → React Native |
|---|---|
<p>、<span>、テキストノード | <Text> |
<h1> → <h6> | 大きなフォントサイズと fontWeight: 'bold' を指定した <Text> |
<img src="..."> | <Image source={{uri: '...'}} style={{width:X, height:Y}}> |
<button> | <Pressable>(推奨)または <TouchableOpacity> |
<a>(ナビゲーション) | <Pressable onPress={() => router.push('/route')}> |
<input type="text"> | <TextInput> |
<input type="password"> | <TextInput secureTextEntry={true}> |
<input type="checkbox"> | カスタム実装、または @expo/vector-icons と Pressable |
<select>/ドロップダウン | @react-native-picker/picker またはカスタムのモーダルピッカー |
<nav>(タブ) | Expo Router の <Tabs> レイアウト |
余白のマッピング
React Native では単位なしの数値(dp — 密度非依存ピクセル)を使用します。
// Approximate Tailwind → RN
const spacing = {
1: 4, // p-1 = 4dp
2: 8, // p-2 = 8dp
3: 12,
4: 16,
5: 20,
6: 24,
8: 32,
10: 40,
12: 48,
16: 64,
}
色のマッピング
以下の順に、HTML が実際に使用しているトークンソースから16進数の値を取得します。
<head>内の インラインtailwind.config(Stitch が出力するもの)。存在する場合はそのまま使用します。- CSS カスタムプロパティ(
:root { --color-primary: ... })。手書きやテンプレート形式の HTML でよく使われます。 - リンクされた、またはインラインのスタイルシート。宣言された色、フォントファミリー、角丸、余白を解析します。
- 最終手段:マークアップ内で最も頻繁に使われる計算済みの値(背景色、テキスト色、アクセントカラー、見出し/本文のフォント、角丸)からトークンを推定し、ユーザーが修正できるよう、推定したことを伝えます。
URL のルートからダウンロードされるのは単一の HTML レスポンスのみです。外部リンクされたスタイルシートは取得できない場合があります。上記のいずれでもトークンを特定できない場合は、配色を作り出すのではなく、その旨を伝えてください。
// src/theme/tokens.ts
export const lightTokens = {
background: '#FFFFFF', // from --color-background
surface: '#F4F4F5',
primary: '#6366F1',
primaryFg: '#FFFFFF',
text: '#09090B',
textMuted: '#71717A',
border: '#E4E4E7',
} as const
export const darkTokens = {
background: '#09090B',
surface: '#18181B',
primary: '#818CF8', // Lighter shade for dark bg
primaryFg: '#09090B',
text: '#FAFAFA',
textMuted: '#A1A1AA',
border: '#27272A',
} as const
export type ThemeTokens = typeof lightTokens
ステップ 4:useColorScheme によるダークモード
// src/theme/useTheme.ts
import { useColorScheme } from 'react-native'
import { lightTokens, darkTokens, type ThemeTokens } from './tokens'
/**
* Returns the current theme's design tokens.
* Automatically switches based on system color scheme.
*/
export function useTheme(): ThemeTokens {
const scheme = useColorScheme()
return scheme === 'dark' ? darkTokens : lightTokens
}
// Usage in any component
import { useTheme } from '@/theme/useTheme'
export function Card({ title }: { title: string }) {
const theme = useTheme()
return (
<View style={[styles.card, { backgroundColor: theme.surface, borderColor: theme.border }]}>
<Text style={[styles.title, { color: theme.text }]}>{title}</Text>
</View>
)
}
const styles = StyleSheet.create({
card: {
borderRadius: 12,
borderWidth: 1,
padding: 16,
marginBottom: 12,
},
title: {
fontSize: 16,
fontWeight: '600',
},
})
ステップ 5:セーフエリアとプラットフォームに関する考慮事項
// app/_layout.tsx — root layout
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
<SafeAreaProvider>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
</SafeAreaProvider>
)
}
// In screen components — use safe area insets
import { useSafeAreaInsets } from 'react-native-safe-area-context'
export default function HomeScreen() {
const insets = useSafeAreaInsets()
return (
<View style={{ flex: 1, paddingTop: insets.top, paddingBottom: insets.bottom }}>
{/* Content */}
</View>
)
}
ステップ 6:コンポーネントのテンプレート
// src/components/StitchComponent.tsx
import { View, Text, Pressable, StyleSheet } from 'react-native'
import { useTheme } from '@/theme/useTheme'
/**
* Props for StitchComponent.
* All data via props — never fetched inside the component.
*/
interface StitchComponentProps {
title: string
description?: string
onPress?: () => void
}
/**
* StitchComponent — [describe purpose in one sentence]
*/
export function StitchComponent({ title, description, onPress }: Readonly<StitchComponentProps>) {
const theme = useTheme()
return (
<Pressable
style={({ pressed }) => [
styles.container,
{
backgroundColor: theme.surface,
borderColor: theme.border,
opacity: pressed ? 0.8 : 1, // Visual feedback on press
},
]}
onPress={onPress}
accessible={true}
accessibilityRole="button"
accessibilityLabel={title}
hitSlop={8} // Increase tap area without changing visual size
>
<Text style={[styles.title, { color: theme.text }]}>{title}</Text>
{description ? (
<Text style={[styles.description, { color: theme.textMuted }]}>{description}</Text>
) : null}
</Pressable>
)
}
const styles = StyleSheet.create({
container: {
borderRadius: 12,
borderWidth: 1,
padding: 16,
gap: 8,
// Minimum touch target
minHeight: 44,
},
title: {
fontSize: 16,
fontWeight: '600',
lineHeight: 24,
},
description: {
fontSize: 14,
lineHeight: 20,
},
})
ステップ 7:React Native におけるアクセシビリティ
// Every interactive element needs these props
<Pressable
accessible={true}
accessibilityRole="button" // "button" | "link" | "text" | "image" | "header" | ...
accessibilityLabel="Close dialog" // What screen reader announces
accessibilityHint="Double tap to close the modal" // Optional extra context
accessibilityState={{ disabled: false, selected: false }}
>
// Images
<Image
accessible={true}
accessibilityLabel="Profile photo of Emma Johnson" // Descriptive alt text
// OR for decorative:
accessible={false}
/>
// Text hierarchy (screen reader uses accessibilityRole="header" for h1-h6 equivalent)
<Text accessibilityRole="header" style={styles.pageTitle}>Dashboard</Text>
実行手順
- 確認 — ソースがモバイルレイアウトであることを確認します(ステップ 1 を参照)。
- データレイヤー — デザインの静的コンテンツから
src/data/mockData.tsを作成します。 - トークン — 抽出した色を使って
src/theme/tokens.tsとuseTheme.tsを作成します。 - コンポーネント — 上記のマッピングルールを使って、各ビジュアルセクションをコンポーネントに変換します。
- 画面 — Expo Router の画面ファイル(
app/(tabs)/index.tsx)でコンポーネントを組み合わせます。 - 確認 —
npx expo startを実行し、iOS Simulator と Android Emulator の両方でテストします。
トラブルシューティング
| 問題 | 対処法 |
|---|---|
StyleSheet.create の型エラー | 'react-native' から StyleSheet をインポートします |
<Text> 外のテキストエラー | すべての文字列を <Text> 内に配置します。{' '} のスペースも対象です |
| Flex レイアウトが正しく表示されない | RN のデフォルトは flexDirection:'column' です。明示的に指定するのが安全です |
| 画像が表示されない | style に明示的な width と height が必要です |
| キーボードでレイアウトが押し上げられる | iOS では behavior='padding' を指定した KeyboardAvoidingView を使用します |
| 下部のセーフエリアと重なる | react-native-safe-area-context の useSafeAreaInsets() を使用します |
参考資料
resources/component-template.tsx— RN コンポーネントのボイラープレートresources/architecture-checklist.md— リリース前チェックリストscripts/fetch-stitch.sh— 信頼性の高い GCS HTML ダウンローダー