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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/stitch-kit:stitch-swiftui-componentsThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
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.
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 downloadernpx claudepluginhub gabelul/stitch-kit --plugin stitch-kitGuides iOS app design and SwiftUI implementation following Human Interface Guidelines, with layouts, navigation patterns, accessibility, and adaptive UI for iPhone/iPad.
Translates between Figma designs and SwiftUI code in both directions: design-to-code and code-to-design. Routes to direction-specific reference docs.
Provides best practices and examples for SwiftUI views, components, navigation hierarchies, custom modifiers, responsive layouts with stacks/grids, and state management (@State/@Binding). Use for creating/refactoring iOS UI.