
ui-design-rules-ios
by pranavpai
A repo to try out different claude code features (hooks, skills, subagents, mcps, etc)
SKILL.md
name: ui-design-rules-ios description: Expert knowledge for interpreting Figma design tokens and generating iOS-native SwiftUI code. Includes token mapping rules, design system patterns, and SwiftUI best practices for creating production-ready iOS applications. version: 1.0.0 tags:
- ios
- swiftui
- design-tokens
- figma
- ui-design
UI Design Rules for iOS
This skill provides comprehensive guidance for converting Figma designs into production-ready SwiftUI code with proper design system architecture.
Core Principles
1. Design Tokens Over Hardcoded Values
Always extract reusable design tokens for:
- Colors (semantic naming: primary, secondary, surface, content)
- Typography (size, weight, line height, letter spacing)
- Spacing (consistent scale: 4, 8, 16, 24, 32, 48, 64)
- Effects (shadows, blurs, borders)
- Corner radius (small, medium, large)
Never hardcode magic numbers or hex values directly in views.
2. Semantic Naming Convention
Use purpose-based names rather than appearance-based:
✅ Good: Color.brandPrimary, Color.contentSecondary, Color.statusError
❌ Bad: Color.blue, Color.gray, Color.red
✅ Good: .titleLarge, .bodyMedium, .labelSmall
❌ Bad: .font24Bold, .font16Regular, .font12Medium
3. Dark Mode by Default
- All colors MUST reference Asset Catalog entries
- Asset Catalog contains both light and dark variants
- Use semantic color names that work in both modes
- Test all UI in both appearances
4. Accessibility First
- Add
.accessibilityLabel()to all interactive elements - Support Dynamic Type with proper font scaling
- Ensure color contrast meets WCAG AA standards
- Use
.accessibilityElement(children:)for complex components
Figma to SwiftUI Mapping Rules
Color Mapping
Figma Color Variables → SwiftUI Color Extensions
// Figma: Primary/500 (#0066FF)
// Asset Catalog: Brand/Primary (Light: #0066FF, Dark: #4D94FF)
extension Color {
static let brandPrimary = Color("Brand/Primary")
}
Color Categories
- Brand Colors: Primary, secondary, tertiary brand colors
- UI Colors: Backgrounds, surfaces, dividers, borders
- Content Colors: Primary, secondary, tertiary text/icons
- Status Colors: Success, warning, error, info
- Interactive Colors: Default, hover, pressed, disabled states
Typography Mapping
Figma Text Styles → SwiftUI Font Extensions
// Figma: Title/Large (SF Pro Display, 22pt, Regular)
extension Font {
static let titleLarge = Font.system(size: 22, weight: .regular, design: .default)
}
Typography Scale (iOS Material 3 Inspired)
| Category | Large | Medium | Small |
|---|---|---|---|
| Display | 57pt | 45pt | 36pt |
| Headline | 32pt | 28pt | 24pt |
| Title | 22pt | 16pt | 14pt |
| Body | 16pt | 14pt | 12pt |
| Label | 14pt | 12pt | 11pt |
Font Weight Mapping
- Figma "Regular" →
.regular - Figma "Medium" →
.medium - Figma "Semibold" →
.semibold - Figma "Bold" →
.bold
Spacing Mapping
Figma Auto Layout Gap → SwiftUI Spacing
// Figma: Auto Layout with 16px gap
VStack(spacing: .spacingM) {
// children
}
// Figma: 16px padding on all sides
.padding(.spacingM)
Spacing Scale
extension CGFloat {
static let spacingXXS: CGFloat = 2 // Figma: 2px
static let spacingXS: CGFloat = 4 // Figma: 4px
static let spacingS: CGFloat = 8 // Figma: 8px
static let spacingM: CGFloat = 16 // Figma: 16px
static let spacingL: CGFloat = 24 // Figma: 24px
static let spacingXL: CGFloat = 32 // Figma: 32px
static let spacingXXL: CGFloat = 48 // Figma: 48px
static let spacingXXXL: CGFloat = 64 // Figma: 64px
}
Layout Mapping
Auto Layout → SwiftUI Stacks
Vertical Auto Layout:
// Figma: Vertical Auto Layout, 16px gap, center aligned
VStack(alignment: .center, spacing: .spacingM) {
// children
}
Horizontal Auto Layout:
// Figma: Horizontal Auto Layout, 8px gap, top aligned
HStack(alignment: .top, spacing: .spacingS) {
// children
}
Hug Contents:
- Figma "Hug" → No
.frame()modifier (let content determine size)
Fill Container:
- Figma "Fill" horizontal →
Spacer()or.frame(maxWidth: .infinity) - Figma "Fill" vertical →
.frame(maxHeight: .infinity)
Fixed Size:
- Figma: Width 200px, Height 100px
- SwiftUI:
.frame(width: 200, height: 100)
Constraints → Frame Modifiers
// Figma: Min Width 100px, Max Width 300px
.frame(minWidth: 100, maxWidth: 300)
// Figma: Aspect Ratio 16:9
.aspectRatio(16/9, contentMode: .fit)
Effects Mapping
Shadows
// Figma: Drop Shadow (X: 0, Y: 2, Blur: 8, Color: #000000 at 8% opacity)
.shadow(color: Color.black.opacity(0.08), radius: 8, x: 0, y: 2)
// Create reusable shadow tokens
extension View {
func shadowSmall() -> some View {
shadow(color: Color.black.opacity(0.04), radius: 4, x: 0, y: 1)
}
func shadowMedium() -> some View {
shadow(color: Color.black.opacity(0.08), radius: 8, x: 0, y: 2)
}
func shadowLarge() -> some View {
shadow(color: Color.black.opacity(0.12), radius: 16, x: 0, y: 4)
}
}
Corner Radius
// Figma: Corner Radius 12px
.cornerRadius(12)
// Or create tokens
extension CGFloat {
static let radiusSmall: CGFloat = 4
static let radiusMedium: CGFloat = 8
static let radiusLarge: CGFloat = 12
static let radiusXLarge: CGFloat = 16
static let radiusRound: CGFloat = 999 // Fully rounded
}
Borders
// Figma: Stroke 1px, Color #E0E0E0
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.uiDivider, lineWidth: 1)
)
Component Patterns
Buttons
// Figma: Primary Button Component
struct PrimaryButton: View {
let title: String
let action: () -> Void
@State private var isPressed = false
var body: some View {
Button(action: action) {
Text(title)
.textStyle(.labelLarge)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, .spacingM)
.background(isPressed ? Color.brandPrimary.opacity(0.8) : Color.brandPrimary)
.cornerRadius(.radiusMedium)
}
.buttonStyle(PlainButtonStyle())
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged { _ in isPressed = true }
.onEnded { _ in isPressed = false }
)
.accessibilityLabel(title)
.accessibilityAddTraits(.isButton)
}
}
Cards
// Figma: Content Card Component
struct ContentCard: View {
let title: String
let description: String
let imageURL: URL?
var body: some View {
VStack(alignment: .leading, spacing: .spacingM) {
// Image
if let imageURL {
AsyncImage(url: imageURL) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
} placeholder: {
Color.uiSurface
}
.frame(height: 200)
.clipped()
}
// Content
VStack(alignment: .leading, spacing: .spacingS) {
Text(title)
.textStyle(.titleMedium)
.lineLimit(2)
Text(description)
.textStyle(.bodySmall)
.foregroundColor(.contentSecondary)
.lineLimit(3)
}
.padding(.horizontal, .spacingM)
.padding(.bottom, .spacingM)
}
.background(Color.uiSurface)
.cornerRadius(.radiusLarge)
.shadowMedium()
.accessibilityElement(children: .combine)
}
}
Text Fields
// Figma: Input Field Component
struct CustomTextField: View {
let placeholder: String
@Binding var text: String
let isSecure: Bool
var body: some View {
Group {
if isSecure {
SecureField(placeholder, text: $text)
} else {
TextField(placeholder, text: $text)
}
}
.textStyle(.bodyMedium)
.padding(.spacingM)
.background(Color.uiSurface)
.overlay(
RoundedRectangle(cornerRadius: .radiusMedium)
.stroke(Color.uiDivider, lineWidth: 1)
)
.accessibilityLabel(placeholder)
}
}
Design System Architecture
File Structure
YourApp/
├── DesignSystem/
│ ├── Tokens/
│ │ ├── ColorTokens.swift
│ │ ├── TypographyTokens.swift
│ │ ├── SpacingTokens.swift
│ │ ├── ShadowTokens.swift
│ │ └── RadiusTokens.swift
│ ├── Components/
│ │ ├── Buttons/
│ │ │ ├── PrimaryButton.swift
│ │ │ ├── SecondaryButton.swift
│ │ │ └── TextButton.swift
│ │ ├── Cards/
│ │ │ ├── ContentCard.swift
│ │ │ └── ProfileCard.swift
│ │ └── Inputs/
│ │ ├── CustomTextField.swift
│ │ └── CustomTextEditor.swift
│ └── Modifiers/
│ ├── TextStyleModifier.swift
│ ├── ShadowModifiers.swift
│ └── CardStyleModifier.swift
├── Features/
│ ├── Authentication/
│ │ ├── Views/
│ │ │ ├── LoginView.swift
│ │ │ └── SignUpView.swift
│ │ └── ViewModels/
│ │ └── AuthenticationViewModel.swift
│ └── Home/
│ ├── Views/
│ │ ├── HomeView.swift
│ │ └── Components/
│ │ ├── HomeHeader.swift
│ │ └── HomeCard.swift
│ └── ViewModels/
│ └── HomeViewModel.swift
└── Resources/
└── Assets.xcassets/
├── Brand/
│ ├── Primary.colorset
│ └── Secondary.colorset
├── UI/
│ ├── Background.colorset
│ ├── Surface.colorset
│ └── Divider.colorset
└── Content/
├── Primary.colorset
├── Secondary.colorset
└── Tertiary.colorset
Implementation Order
- Foundation First: Create all token files (colors, typography, spacing)
- Asset Catalog: Set up all color assets with dark mode variants
- Base Components: Build reusable primitives (buttons, cards, inputs)
- Feature Views: Compose features using design system components
- Refinement: Add custom modifiers and polish interactions
Edge Cases & Solutions
Issue: Figma Uses Absolute Positioning
Problem: Figma design has elements positioned with X/Y coordinates Solution: Convert to flexible layout using stacks and spacers
// Instead of absolute positioning
.position(x: 100, y: 200) // ❌ Not responsive
// Use flexible layout
HStack {
Spacer()
content
.padding(.leading, .spacingL)
}
Issue: Complex Vector Graphics
Problem: Figma has custom vector illustrations Solution Options:
- Export as SVG and use as Image asset
- Replace with SF Symbol if semantically similar
- Use Shape API for simple vectors
// Option 2: SF Symbol replacement
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.brandPrimary)
.font(.system(size: 24))
Issue: Missing Design Tokens
Problem: Figma file has no variables/styles defined Solution: Analyze raw values and create semantic groupings
// Observed values: #0066FF, #0052CC, #004099
// Create semantic system:
extension Color {
static let brandPrimary = Color(hex: "0066FF") // Default state
static let brandPrimaryHover = Color(hex: "0052CC") // Hover state
static let brandPrimaryPressed = Color(hex: "004099") // Pressed state
}
Issue: Component Variants in Figma
Problem: Figma component has multiple variants (Primary, Secondary, Small, Large) Solution: Use enum-based configuration
enum ButtonStyle {
case primary, secondary, tertiary
}
enum ButtonSize {
case small, medium, large
var padding: CGFloat {
switch self {
case .small: return .spacingS
case .medium: return .spacingM
case .large: return .spacingL
}
}
}
struct ConfigurableButton: View {
let title: String
let style: ButtonStyle
let size: ButtonSize
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.padding(.vertical, size.padding)
.frame(maxWidth: .infinity)
.background(backgroundColor)
.foregroundColor(textColor)
}
}
private var backgroundColor: Color {
switch style {
case .primary: return .brandPrimary
case .secondary: return .brandSecondary
case .tertiary: return .clear
}
}
private var textColor: Color {
switch style {
case .primary, .secondary: return .white
case .tertiary: return .brandPrimary
}
}
}
Quality Checklist
Before completing a conversion, verify:
- Colors: All from Asset Catalog, no hex values in code
- Typography: Uses font extensions, no inline
.font(.system(...))calls - Spacing: Uses spacing tokens, no magic numbers
- Dark Mode: All colors support dark appearance automatically
- Accessibility: All interactive elements have accessibility labels
- Dynamic Type: Text scales properly with system settings
- Previews: Multiple preview states provided
- Documentation: Complex logic has inline comments
- Naming: Semantic names (not appearance-based)
- Organization: Files in correct feature/component directories
- Reusability: Common patterns extracted to shared components
- Performance: No expensive operations in view body
Common Mistakes to Avoid
❌ Hardcoding Colors
Text("Hello")
.foregroundColor(Color(red: 0, green: 0.4, blue: 1.0)) // BAD
✅ Use Token
Text("Hello")
.foregroundColor(.brandPrimary) // GOOD
❌ Magic Numbers
.padding(17) // BAD - arbitrary value
✅ Semantic Spacing
.padding(.spacingM) // GOOD - from spacing scale
❌ Appearance-Based Naming
static let blue = Color("Blue") // BAD
✅ Semantic Naming
static let brandPrimary = Color("Brand/Primary") // GOOD
❌ Missing Accessibility
Button(action: {}) {
Image(systemName: "heart")
} // BAD - no label
✅ Proper Accessibility
Button(action: {}) {
Image(systemName: "heart")
}
.accessibilityLabel("Favorite") // GOOD
Resources & References
- Apple Human Interface Guidelines: https://developer.apple.com/design/human-interface-guidelines/
- SwiftUI Documentation: https://developer.apple.com/documentation/swiftui
- Figma API: https://www.figma.com/developers/api
- Material Design 3: https://m3.material.io/ (for typography scale inspiration)
- WCAG Accessibility: https://www.w3.org/WAI/WCAG21/quickref/
Version History
v1.0.0 - Initial release with comprehensive Figma to SwiftUI mapping rules
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon