From stitch-kit
Converts Stitch mobile designs to native iOS SwiftUI views with VStack/HStack/ZStack layout mapping, dark mode color assets, NavigationStack/TabView routing, and Xcode project structure.
npx claudepluginhub gabelul/stitch-kit --plugin stitch-kitThis skill is limited to using the following tools:
You are a Swift/SwiftUI engineer. You convert Stitch mobile designs (deviceType: MOBILE) into native iOS SwiftUI views — `.swift` files that build and run in Xcode. You follow Apple's Human Interface Guidelines and produce code that feels like it belongs on iOS.
Guides iOS app design and SwiftUI implementation following Human Interface Guidelines, with layouts, navigation patterns, accessibility, and adaptive UI for iPhone/iPad.
Designs tasteful iOS 18+ SwiftUI experiences from user goals and context. For building views, screens, or reviewing SwiftUI design quality.
Converts Claude HTML/CSS prototypes (via design URL or .tar.gz) to single self-contained SwiftUI View files in active Xcode workspaces. Generates code, builds, previews, and visually diffs for layout fidelity.
Share bugs, ideas, or general feedback.
You are a Swift/SwiftUI engineer. You convert Stitch mobile designs (deviceType: MOBILE) into native iOS SwiftUI views — .swift files that build and run in Xcode. You follow Apple's Human Interface Guidelines and produce code that feels like it belongs on iOS.
Use this skill when:
deviceType: MOBILENote: This skill targets iOS 16+ with SwiftUI. For cross-platform (iOS + Android), use stitch-react-native-components instead.
deviceType: MOBILElist_tools → find Stitch MCP prefix[prefix]:get_screen → fetch metadatabash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html"screenshot.downloadUrl — confirm mobile layout before convertingOnly convert MOBILE designs. Desktop Stitch designs don't map well to SwiftUI without significant layout rethinking.
MyApp/
├── MyApp.swift ← @main entry point
├── ContentView.swift ← Root view (TabView or NavigationStack)
├── Theme/
│ ├── ThemeTokens.swift ← Design token constants
│ └── Color+App.swift ← Color extension with semantic names
├── Views/
│ ├── [ScreenName]View.swift ← One file per Stitch screen
│ └── Components/
│ └── [Name]View.swift ← Reusable component views
├── Models/
│ └── MockData.swift ← Static preview data
└── Assets.xcassets/
└── Colors/ ← Color assets for light/dark mode
This is the core translation. Apply these rules to every element in the Stitch HTML:
| HTML/CSS pattern | → SwiftUI |
|---|---|
display:flex; flex-direction:column | VStack(alignment: .leading, spacing: 16) |
display:flex; flex-direction:row | HStack(alignment: .center, spacing: 12) |
display:flex; justify-content:space-between | HStack { Spacer() } pattern |
position:absolute overlay | ZStack with layered views |
display:grid (2-column) | LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) |
overflow-y: scroll | ScrollView(.vertical, showsIndicators: false) |
| Repeated list of items | List or ForEach inside ScrollView + LazyVStack |
position:fixed bottom nav | TabView (preferred) or explicit VStack with Spacer() |
SwiftUI uses points (1pt ≈ 1dp on non-retina, 2px on Retina @2x):
// Spacing from Tailwind → SwiftUI points
// p-1(4px)→4 p-2(8px)→8 p-3(12px)→12 p-4(16px)→16
// p-6(24px)→24 p-8(32px)→32 p-12(48px)→48 p-16(64px)→64
// Tailwind rounded- → SwiftUI cornerRadius
// rounded-sm → .cornerRadius(4)
// rounded-md → .cornerRadius(8)
// rounded-lg → .cornerRadius(12)
// rounded-xl → .cornerRadius(16)
// rounded-full → .clipShape(Capsule()) or .cornerRadius(9999)
| HTML | → SwiftUI |
|---|---|
<p>, <span>, text | Text("content") |
<h1> | Text("title").font(.largeTitle).fontWeight(.bold) |
<h2> | Text("title").font(.title2).fontWeight(.semibold) |
<h3> | Text("title").font(.headline) |
<p> body | Text("body").font(.body) |
<small> / caption | Text("caption").font(.caption).foregroundStyle(.secondary) |
<img> | AsyncImage(url: URL(string: "...")) for remote, Image("name") for asset |
<button> primary | Button("Label") { action() }.buttonStyle(.borderedProminent) |
<button> secondary | Button("Label") { action() }.buttonStyle(.bordered) |
<button> ghost/text | Button("Label") { action() }.buttonStyle(.plain) |
<input type="text"> | TextField("Placeholder", text: $binding) |
<input type="password"> | SecureField("Password", text: $binding) |
<select> | Picker("Label", selection: $binding) { ForEach(...) } |
<toggle> / checkbox | Toggle("Label", isOn: $binding) |
| Icon-only button | Button { action() } label: { Image(systemName: "xmark") } |
| Pattern | SwiftUI implementation |
|---|---|
| Bottom tab bar | TabView with tabItem { Label("Home", systemImage: "house") } |
| Stack navigation | NavigationStack { ... NavigationLink(destination: ...) } |
| Modal / sheet | .sheet(isPresented: $showModal) { ModalView() } |
| Full screen modal | .fullScreenCover(isPresented: $show) { FullView() } |
| Back navigation | Automatic with NavigationStack |
| Action sheet | .confirmationDialog("Title", isPresented: $show) { ... } |
// Theme/Color+App.swift
import SwiftUI
extension Color {
// Extract these hex values from the Stitch HTML's tailwind.config
// Backgrounds
static let appBackground = Color("AppBackground") // Asset catalog
static let appSurface = Color("AppSurface")
// Brand
static let appPrimary = Color("AppPrimary")
static let appPrimaryFg = Color("AppPrimaryForeground")
// Text
static let appText = Color("AppText")
static let appTextMuted = Color("AppTextMuted")
// Borders
static let appBorder = Color("AppBorder")
}
Create named Color Sets in Assets.xcassets/Colors/:
For each color (e.g., AppPrimary):
#6366F1 (the light mode value)#818CF8 (lighter shade for dark bg)Alternatively, define programmatically (no asset catalog needed):
// Theme/ThemeTokens.swift
import SwiftUI
struct ThemeTokens {
let background: Color
let surface: Color
let primary: Color
let primaryFg: Color
let text: Color
let textMuted: Color
let border: Color
static let light = ThemeTokens(
background: Color(hex: "#FFFFFF"),
surface: Color(hex: "#F4F4F5"),
primary: Color(hex: "#6366F1"),
primaryFg: Color(hex: "#FFFFFF"),
text: Color(hex: "#09090B"),
textMuted: Color(hex: "#71717A"),
border: Color(hex: "#E4E4E7")
)
static let dark = ThemeTokens(
background: Color(hex: "#09090B"),
surface: Color(hex: "#18181B"),
primary: Color(hex: "#818CF8"), // Lightened for dark bg
primaryFg: Color(hex: "#09090B"),
text: Color(hex: "#FAFAFA"),
textMuted: Color(hex: "#A1A1AA"),
border: Color(hex: "#27272A")
)
}
// Convenience: Color from hex string
extension Color {
init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let r = Double((int & 0xFF0000) >> 16) / 255
let g = Double((int & 0x00FF00) >> 8) / 255
let b = Double(int & 0x0000FF) / 255
self.init(red: r, green: g, blue: b)
}
}
// Anywhere in a view — automatic dark mode
@Environment(\.colorScheme) var colorScheme
var theme: ThemeTokens {
colorScheme == .dark ? .dark : .light
}
// Usage
Text("Hello")
.foregroundStyle(theme.text)
.background(theme.surface)
// Views/Components/StitchComponentView.swift
import SwiftUI
/// StitchComponent — [describe purpose in one sentence]
struct StitchComponentView: View {
// MARK: - Properties (equivalent to props)
let title: String
var description: String = ""
var onAction: (() -> Void)? = nil
// MARK: - Environment
@Environment(\.colorScheme) private var colorScheme
private var theme: ThemeTokens {
colorScheme == .dark ? .dark : .light
}
// MARK: - State
@State private var isPressed = false
// MARK: - Body
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(title)
.font(.headline)
.foregroundStyle(theme.text)
if !description.isEmpty {
Text(description)
.font(.subheadline)
.foregroundStyle(theme.textMuted)
}
if let action = onAction {
Button("Action", action: action)
.buttonStyle(.borderedProminent)
.tint(theme.primary)
}
}
.padding(16)
.frame(maxWidth: .infinity, alignment: .leading)
.background(theme.surface)
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(theme.border, lineWidth: 1)
)
// Minimum touch target — 44pt Apple HIG requirement
.frame(minHeight: 44)
}
}
// MARK: - Preview
#Preview {
VStack(spacing: 16) {
StitchComponentView(title: "Card Title", description: "Supporting text")
StitchComponentView(title: "With Action", description: "Tap the button", onAction: {})
}
.padding()
}
// MyApp.swift
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// ContentView.swift — root with TabView
struct ContentView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
ProfileView()
.tabItem {
Label("Profile", systemImage: "person")
}
}
}
}
SwiftUI handles much of this automatically, but always verify:
// Image accessibility
Image("hero-photo")
.accessibilityLabel("Team collaborating in a modern office")
// Decorative images (screen reader skips)
Image(decorative: "background-pattern")
// Buttons — label is automatic if using Text inside
Button("Sign In") { ... } // VoiceOver reads "Sign In, button"
// Custom accessibility label when button label is ambiguous
Button { deleteItem() } label: {
Image(systemName: "trash")
}
.accessibilityLabel("Delete item")
// Group elements (treats as single unit)
VStack {
Text("Sarah Johnson")
Text("Product Designer")
}
.accessibilityElement(children: .combine)
// Dynamic type support — always use semantic fonts
Text("Headline")
.font(.headline) // ✅ Scales with user's text size
// NOT .font(.system(size: 17, weight: .semibold)) // ❌ Fixed size
SwiftUI has excellent built-in animations — use them for the micro-interactions:
// Button press spring
Button(action: primaryAction) {
Text("Get Started")
.padding(.horizontal, 24)
.padding(.vertical, 14)
.background(theme.primary)
.foregroundStyle(theme.primaryFg)
.clipShape(Capsule())
.scaleEffect(isPressed ? 0.96 : 1.0)
.animation(.spring(response: 0.2, dampingFraction: 0.6), value: isPressed)
}
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged { _ in isPressed = true }
.onEnded { _ in isPressed = false }
)
// Card appear transition
VStack { /* card content */ }
.transition(.move(edge: .bottom).combined(with: .opacity))
// Respect reduced motion
@Environment(\.accessibilityReduceMotion) var reduceMotion
var animation: Animation {
reduceMotion ? .none : .spring(response: 0.3, dampingFraction: 0.7)
}
deviceType: MOBILEModels/MockData.swift from static content in the designTheme/ThemeTokens.swift with extracted hex values, and Color+App.swiftTabView (tab bar) or NavigationStack (stack)| Issue | Fix |
|---|---|
| View overflows screen | Add .frame(maxWidth: .infinity) + parent ScrollView |
| Text truncates unexpectedly | Add .lineLimit(nil) or .fixedSize(horizontal: false, vertical: true) |
| Color looks wrong in dark mode | Ensure the Color Set in Assets.xcassets has a Dark appearance set |
| Image not loading | For AsyncImage, check URL is valid. For local images, file must be in Assets.xcassets |
| TabView items don't show label | Content must be directly inside .tabItem { } — no wrapping views |
| Sheet not dismissible | Add @Environment(\.dismiss) var dismiss and call dismiss() in the sheet |
| Preview crashes | Check #Preview has valid mock data — never optional-unwrap without fallback |
resources/component-template.swift — Boilerplate SwiftUI viewresources/layout-mapping.md — Full HTML/CSS → SwiftUI referenceresources/architecture-checklist.md — Pre-ship checklistscripts/fetch-stitch.sh — Reliable GCS HTML downloader