コンテンツにスキップ

04. Spec KitでStitchのデザインをUIコンポーネントに実装する

画面骨格ができたら、次はアプリの「顔」となるUIコンポーネントを実装します。

リデザイン編(Phase03)では、Google Stitchを使って配色、余白、カードの角丸、影(シャドウ)、そして操作時のインタラクション仕様を DESIGN.md にまとめました。

この章では、その設計書をSpec Kit(AIエージェント)に正確に読み込ませ、チープなデフォルトUIを脱却した、洗練されたReact Nativeコンポーネント群へと変換していくプロセスを詳しく解説します。


  1. デザインシステムをテーマ定数(theme.ts)として定義する
  2. Stitchのデザインから抽出したコアUIパーツの仕様
  3. React Native特有のスタイリング制約とプラットフォーム差の吸収
  4. Spec Kitの2周目:UIコンポーネントを仕様駆動で作る
  5. 生成されたUIコンポーネントの実装例(HabitCardと触覚フィードバック)
  6. 実機でのビジュアル確認とマイクロインタラクションの調整
  7. まとめと次のステップ

1. デザインシステムをテーマ定数(theme.ts)として定義する

Section titled “1. デザインシステムをテーマ定数(theme.ts)として定義する”

AIエージェントが各コンポーネントで勝手なカラーコード(#123456など)を直書きするのを防ぐため、まずは DESIGN.md のトークンをまとめた constants/theme.ts を作らせます。

constants/theme.ts
export const Colors = {
primary: '#10B981', // エメラルドグリーン(アクション・完了状態)
primaryLight: '#D1FAE5', // 完了カードの背景アクセント
secondary: '#6366F1', // インディゴブルー(統計情報・ハイライト)
background: '#F9FAFB', // 画面背景(オフホワイト)
surface: '#FFFFFF', // カード背景(純白)
textPrimary: '#111827', // メインテキスト
textSecondary: '#6B7280',// サブテキスト
border: '#E5E7EB', // 境界線
};
export const Spacing = {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
};
export const BorderRadius = {
sm: 8,
md: 12,
lg: 16,
full: 9999,
};
export const Typography = {
h1: { fontSize: 24, fontWeight: '700' as const, color: Colors.textPrimary },
h2: { fontSize: 18, fontWeight: '600' as const, color: Colors.textPrimary },
body: { fontSize: 14, fontWeight: '400' as const, color: Colors.textPrimary },
caption: { fontSize: 12, fontWeight: '400' as const, color: Colors.textSecondary },
};

ここで定義した値は、リデザイン編で作成した DESIGN.md のカラーシステムとタイポグラフィをそのまま写したものです。AIが勝手に別の色を使い始めたら、DESIGN.mdconstants/theme.ts の両方を読み直させてください。

このようにテーマを一箇所に集約することで、後からのダークモード対応やブランドカラーの変更も容易になります。


2. Stitchのデザインから抽出したコアUIパーツの仕様

Section titled “2. Stitchのデザインから抽出したコアUIパーツの仕様”

サンプルアプリ(HabitFlow)のホーム画面で必要な共通パーツは以下の3点です。

  1. HabitCard(習慣カード):
    • 習慣名、継続日数バッジ(🔥 5日連続など)、完了チェックボタン
    • タップ時にチェック状態が切り替わり、カード全体の見た目が変化する(完了時は緑のチェックと淡い背景)
  2. ProgressBar(達成進捗バー):
    • 今日の達成率(例: 「3/5 完了 60%」)をグラフィカルに表示
  3. FloatingAddButton(習慣追加ボタン):
    • 画面右下に浮かぶ丸いFAB(Floating Action Button)または固定の追加ボタン

3. React Native特有のスタイリング制約とプラットフォーム差の吸収

Section titled “3. React Native特有のスタイリング制約とプラットフォーム差の吸収”

WebのCSSと異なり、React NativeのStyleSheetには注意すべき特徴があります。

  • デフォルトが flex-direction: column: Webの row と逆なので、横並びにする際は明示的に flexDirection: 'row' を指定します。
  • 影(シャドウ)のOS差:
    • iOS: shadowColor, shadowOffset, shadowOpacity, shadowRadius で指定。
    • Android: elevation(数値)で指定。
  • プラットフォーム分岐の活用: Platform.select({ ios: { ... }, android: { ... } }) を使ってAIにOSごとの最適値を吐かせます。

4. Spec Kitの2周目:UIコンポーネントを仕様駆動で作る

Section titled “4. Spec Kitの2周目:UIコンポーネントを仕様駆動で作る”

ここからが Spec Kit サイクルの2周目です。前の記事と同じ順序(仕様 → 計画 → タスク → 実装)を、今度はUIコンポーネントに対して回します。

手順1:/speckit.specify で何を作るかを書く

Section titled “手順1:/speckit.specify で何を作るかを書く”
/speckit.specify
ホーム画面で使う共通UIコンポーネントを用意する。
ルートの `DESIGN.md` および `constants/theme.ts` を読み込んでください。
以下の共通UIコンポーネントを `components/` ディレクトリ配下に作成してください。
### 作成するコンポーネント
1. `components/HabitCard.tsx`
- props: `title: string`, `streakDays: number`, `isCompleted: boolean`, `onToggle: () => void`
- チェックボタン(TouchableOpacity)を押すと `onToggle` を呼び出す。
- 完了状態のときは、カードの背景色やテキストスタイルを `DESIGN.md` の完了時スタイルに切り替える。
- タップ時に `expo-haptics` を使って軽い振動(ImpactFeedbackStyle.Light)を発生させること。
2. `components/ProgressBar.tsx`
- props: `total: number`, `completed: number`
- アニメーションまたはスムーズに進捗バーを描画し、パーセンテージを表示する。
3. `components/FloatingAddButton.tsx`
- 画面右下に浮かぶ丸いFAB。`onPress` を受け取り、わずかなドロップシャドウで浮遊感を出す。
4. `components/AddHabitModal.tsx`
- 習慣の新規作成用のモーダルシート。TextInputと登録・キャンセルボタンを備える。
### 規約
- スタイリングには React Native の `StyleSheet.create` を用い、色は `constants/theme.ts` の定数のみを使用すること。
- iOSとAndroidの両方で美しいドロップシャドウ(elevationとshadowプロパティ)を付与すること。
### この機能でやらないこと
- 習慣データの保存・読み込み(次の記事で実装する)
- 画面への組み込み(コンポーネント単体で完結させる)

手順2:/speckit.plan で技術的な方針を決める

Section titled “手順2:/speckit.plan で技術的な方針を決める”
/speckit.plan
- 触覚フィードバックには `expo-haptics` を使う。未導入なら
`npx expo install expo-haptics` をタスクに含めること。
- 色・余白・角丸は必ず `constants/theme.ts` の定数を参照し、
カラーコードをコンポーネント内に直接書かないこと。
- OS差のあるシャドウは `Platform.select` で iOS / Android を分岐させる。
- 各コンポーネントは props だけで見た目が決まる純粋な表示部品とし、
AsyncStorage やグローバルな状態に依存しないこと。

手順3:タスクに割って実装する

Section titled “手順3:タスクに割って実装する”
/speckit.tasks
/speckit.implement

specs/002-.../tasks.md にコンポーネントごとの作業が並び、上から順に実装されていきます。


5. 生成されたUIコンポーネントの実装例(HabitCardと触覚フィードバック)

Section titled “5. 生成されたUIコンポーネントの実装例(HabitCardと触覚フィードバック)”

AIが生成した components/HabitCard.tsx のコード例です。タップした瞬間に心地よい振動が手元に伝わります。

import React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Platform } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import * as Haptics from 'expo-haptics';
import { Colors, Spacing, BorderRadius } from '../constants/theme';
interface HabitCardProps {
title: string;
streakDays: number;
isCompleted: boolean;
onToggle: () => void;
}
export const HabitCard: React.FC<HabitCardProps> = ({
title,
streakDays,
isCompleted,
onToggle,
}) => {
const handlePress = () => {
// 触覚フィードバックを発火
if (Platform.OS !== 'web') {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}
onToggle();
};
return (
<View style={[styles.card, isCompleted && styles.cardCompleted]}>
<TouchableOpacity
style={[styles.checkbox, isCompleted && styles.checkboxCompleted]}
onPress={handlePress}
activeOpacity={0.7}
accessibilityRole="checkbox"
accessibilityLabel={title}
accessibilityState={{ checked: isCompleted }}
hitSlop={10}
>
{isCompleted && <Ionicons name="checkmark" size={18} color="#FFFFFF" />}
</TouchableOpacity>
<View style={styles.content}>
<Text style={[styles.title, isCompleted && styles.titleCompleted]}>
{title}
</Text>
<Text style={styles.streak}>🔥 {streakDays}日連続</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
card: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: Colors.surface,
padding: Spacing.md,
borderRadius: BorderRadius.md,
marginBottom: Spacing.sm,
borderWidth: 1,
borderColor: Colors.border,
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.05,
shadowRadius: 4,
},
android: {
elevation: 2,
},
}),
},
cardCompleted: {
backgroundColor: Colors.primaryLight,
borderColor: 'transparent',
},
checkbox: {
width: 28,
height: 28,
borderRadius: BorderRadius.sm,
borderWidth: 2,
borderColor: Colors.primary,
alignItems: 'center',
justifyContent: 'center',
marginRight: Spacing.md,
},
checkboxCompleted: {
backgroundColor: Colors.primary,
borderColor: Colors.primary,
},
content: {
flex: 1,
},
title: {
fontSize: 16,
fontWeight: '600',
color: Colors.textPrimary,
},
titleCompleted: {
color: Colors.textSecondary,
textDecorationLine: 'line-through',
},
streak: {
fontSize: 12,
color: Colors.textSecondary,
marginTop: 2,
},
});

6. 実機でのビジュアル確認とマイクロインタラクションの調整

Section titled “6. 実機でのビジュアル確認とマイクロインタラクションの調整”

完成したコンポーネントを app/(tabs)/index.tsx に並べ、スマホ実機で確認します。

実機で見て「少し余白が狭い」「チェックした時のアニメーションが欲しい」と感じた場合は、AIに直感的にフィードバックします。

「実機で確認したところ、カードの上下パディングが少し窮屈です。Spacing.mdからSpacing.lg相当に変更し、チェックボタンをタップした際にカード全体がふわっとフェードインするような心地よいトランジションを追加してください。」

このように、「実機で触る → 感覚的なフィードバックをAIに伝える → コードが即座にブラッシュアップされる」 というサイクルこそが、バイブコーディングの真骨頂です。


Stitchで設計したビジュアルが、手元のスマートフォンの上で生き生きと動くReact Nativeコンポーネントになりました。

次の記事では、ローカルストレージ(AsyncStorage)によるデータの保存ロジックを結びつけ、アプリを完全に動くMVPとして完成させ、GitHubに保存する「05. 最初の動く版を作ってGitHubに保存する」に進みましょう。