
요즘 AI 코딩 에이전트로 UI 코드를 만들 때 가장 많이 하는 실수가 있다.
디자인 이미지를 하나 던지고 이렇게 말하는 것이다.
이 화면 똑같이 SwiftUI로 만들어줘.
또는 이렇게 시킨다.
이 Figma 화면 보고 Android Compose 코드로 바꿔줘.
처음 결과는 꽤 그럴듯하다.
카드도 있고, 버튼도 있고, 텍스트도 얼추 맞는다.
그런데 실제 디자이너 리뷰에 들어가면 바로 문제가 나온다.
위 여백이 4px 커요.
타이틀 line-height가 달라요.
버튼 높이가 다릅니다.
카드 radius가 안 맞아요.
아이콘과 텍스트 간격이 이상해요.
Android에서는 전체가 조금 내려가 보여요.
iOS에서는 Dynamic Type 대응이 깨져요.
AI가 UI를 못 짜서가 아니다.
입력이 부족했기 때문이다.
UI 코드는 단순히 “화면을 비슷하게 그리는 작업”이 아니다.
좌표
여백
크기
폰트
line-height
색상
radius
shadow
asset
component state
safe area
responsive rule
이런 수치와 규칙을 코드로 옮기는 작업이다.
그래서 Figma 기반 UI 구현에서 AI를 제대로 쓰려면 접근을 바꿔야 한다.
이미지 한 장을 보고 감으로 만들게 하지 말고, Figma의 수치 정보를 구조화해서 넘겨야 한다.
이 글에서는 iOS SwiftUI와 Android Jetpack Compose에서 Codex와 Claude를 함께 사용해 Figma UI를 더 정확하게 구현하는 워크플로를 정리해본다.
핵심은 이거다.
Claude
→ Figma 구조와 수치 분석
Codex
→ 실제 프로젝트 코드 구현
Claude
→ 디자인 기준으로 리뷰
Codex
→ 제한된 수정
Screenshot Test
→ 실제 화면 검증
AI 하나에게 처음부터 끝까지 맡기는 것이 아니라, 역할을 나눠야 한다.
Figma 화면을 이미지로만 보면 AI는 많은 것을 추측해야 한다.
예를 들어 카드 하나를 봐도 AI는 이런 값을 정확히 알 수 없다.
카드 width가 343인지 344인지
padding이 16인지 20인지
corner radius가 12인지 16인지
텍스트 line-height가 22인지 24인지
shadow opacity가 8%인지 12%인지
아이콘 크기가 20인지 24인지
사람이 봐도 확대하지 않으면 헷갈리는 값이다.
AI는 더 쉽게 틀린다.
특히 다음 요소는 이미지 기반 추측으로는 자주 틀린다.
- line-height
- letter spacing
- Auto Layout gap
- Hug / Fill / Fixed 설정
- component variant
- opacity가 섞인 color
- shadow blur와 y offset
- safe area 기준
- scroll 영역과 fixed 영역 구분
- iOS point와 Android dp 변환
디자인을 코드로 옮기는 데 필요한 것은 단순 스크린샷이 아니다.
정확한 설계 데이터다.
Figma에는 이미 그 정보가 있다.
문제는 AI에게 그 정보를 어떻게 먹이느냐다.
나쁜 요청은 이런 식이다.
이 이미지 보고 똑같이 만들어줘.
이 요청은 AI에게 거의 모든 것을 추측하라고 시키는 것이다.
조금 나은 요청은 이렇다.
이 Figma 프레임의 spacing, typography, color, radius를 분석해서 SwiftUI로 만들어줘.
하지만 이것도 충분하지 않다.
실무에서는 더 구체적인 작업 패킷이 필요하다.
Figma node URL
대상 플랫폼
기준 디바이스 크기
디자인 토큰
사용할 기존 컴포넌트
수정 가능한 파일
수정 금지 파일
허용 오차
스크린샷 검증 방식
이걸 하나의 파일 묶음으로 만들면 좋다.
나는 이걸 UI Implementation Pack이라고 부른다.
프로젝트 안에 이런 폴더를 만든다.
.ai/ui-tasks/
└── checkout-summary-card/
├── task.md
├── figma.md
├── layout-contract.json
├── design-tokens.json
├── component-map.md
├── platform-rules.md
├── validation.md
└── done.md
각 파일의 역할은 단순하다.
task.md
→ 이번에 구현할 화면과 범위
figma.md
→ Figma 링크, frame 정보, 기준 디바이스
layout-contract.json
→ 핵심 수치, 간격, 크기, 제약
design-tokens.json
→ color, typography, radius, shadow
component-map.md
→ Figma 컴포넌트와 실제 코드 컴포넌트 매핑
platform-rules.md
→ iOS / Android 구현 규칙
validation.md
→ 스크린샷 검증과 허용 오차
done.md
→ 완료 보고 형식
AI에게 이미지만 던지는 것이 아니라 이 폴더를 읽고 작업하게 만든다.
# Task
Figma의 `CheckoutSummaryCard` 프레임을 iOS SwiftUI와 Android Compose 컴포넌트로 구현한다.
## Goal
결제 요약 카드 UI를 기존 DesignSystem 컴포넌트를 사용해 구현한다.
## Target
- iOS: `CheckoutSummaryCardView.swift`
- Android: `CheckoutSummaryCard.kt`
## Not Goal
- 결제 로직 변경
- API 모델 변경
- DesignSystem 컴포넌트 수정
- 전체 Checkout 화면 리팩토링
- 새로운 폰트 추가
- 새로운 색상 토큰 임의 생성
## Expected Result
- Figma 수치 기준으로 spacing, typography, radius, color가 맞아야 한다.
- iOS와 Android 모두 같은 디자인 토큰을 사용해야 한다.
- 하드코딩 숫자는 `layout-contract.json` 기준으로만 사용한다.
- 기존 컴포넌트가 있으면 새로 만들지 않는다.
UI 작업에서 Not Goal이 매우 중요하다.
AI는 UI를 만들다가 DesignSystem까지 고치려고 할 수 있다.
이번 작업은 카드 컴포넌트 하나라면 카드 하나에서 끝나야 한다.
# Figma
## Source
Figma file:
https://figma.com/file/...
Frame:
Checkout / Summary Card / Default
Node:
123:456
## Target Platforms
- iOS SwiftUI
- Android Jetpack Compose
## Reference Device
- iOS: iPhone 15 Pro logical width 393pt
- Android: Pixel 8 logical width 412dp
## Important
Use Figma values as logical layout units.
- iOS: map layout values to pt
- Android: map layout values to dp
- Text size on Android should use sp
- Do not infer spacing from screenshot if Figma layout data exists
여기서 포인트는 “이미지 추측 금지”다.
Figma MCP나 Dev Mode에서 가져온 실제 layout 값을 우선한다.
스크린샷은 검증용이지 원본 수치의 대체물이 아니다.
AI가 UI를 만들 때 가장 잘 틀리는 부분은 간격이다.
그래서 핵심 layout 값을 JSON으로 고정한다.
{
"component": "CheckoutSummaryCard",
"frame": {
"width": 343,
"height": "hug"
},
"container": {
"padding": {
"top": 20,
"leading": 20,
"bottom": 20,
"trailing": 20
},
"cornerRadius": 16,
"background": "color.surface.card"
},
"layout": {
"direction": "vertical",
"spacing": 16
},
"header": {
"height": "hug",
"spacing": 8,
"icon": {
"size": 24
},
"title": {
"textStyle": "typography.title.medium"
}
},
"rows": {
"spacing": 12,
"labelStyle": "typography.body.medium",
"valueStyle": "typography.body.semibold"
},
"divider": {
"height": 1,
"color": "color.border.subtle"
},
"totalRow": {
"topPadding": 4,
"labelStyle": "typography.title.small",
"valueStyle": "typography.title.medium"
}
}
이 파일이 있으면 AI가 임의로 padding(18) 같은 값을 넣을 가능성이 줄어든다.
중요한 수치는 프롬프트가 아니라 계약 파일로 만든다.
{
"colors": {
"color.surface.card": {
"light": "#FFFFFF",
"dark": "#1C1C1E"
},
"color.text.primary": {
"light": "#111111",
"dark": "#FFFFFF"
},
"color.text.secondary": {
"light": "#6B7280",
"dark": "#A1A1AA"
},
"color.border.subtle": {
"light": "#E5E7EB",
"dark": "#2C2C2E"
}
},
"typography": {
"typography.title.medium": {
"fontFamily": "Pretendard",
"fontSize": 18,
"lineHeight": 24,
"fontWeight": 700
},
"typography.title.small": {
"fontFamily": "Pretendard",
"fontSize": 16,
"lineHeight": 22,
"fontWeight": 700
},
"typography.body.medium": {
"fontFamily": "Pretendard",
"fontSize": 14,
"lineHeight": 20,
"fontWeight": 400
},
"typography.body.semibold": {
"fontFamily": "Pretendard",
"fontSize": 14,
"lineHeight": 20,
"fontWeight": 600
}
},
"radius": {
"radius.card": 16
}
}
AI에게 “폰트 비슷하게”라고 하면 결과가 흔들린다.
폰트 크기, line-height, weight는 반드시 token으로 준다.
AI가 기존 컴포넌트를 무시하고 새로 만드는 것도 자주 생기는 문제다.
그래서 매핑 파일을 둔다.
# Component Map
## Figma → iOS
- `Button/Primary`
→ `DSPrimaryButton`
- `Icon/Receipt`
→ `Image(.receipt24)`
- `Color/Surface/Card`
→ `Color.dsSurfaceCard`
- `Text/TitleMedium`
→ `.dsTitleMedium()`
## Figma → Android
- `Button/Primary`
→ `DSPrimaryButton`
- `Icon/Receipt`
→ `R.drawable.ic_receipt_24`
- `Color/Surface/Card`
→ `AppTheme.colors.surfaceCard`
- `Text/TitleMedium`
→ `AppTheme.typography.titleMedium`
## Rules
- Do not create new DesignSystem components.
- If a mapped component does not exist, stop and report.
- Do not invent new color names.
- Do not add new font files.
이 파일 하나로 AI가 새 컴포넌트를 남발하는 것을 막을 수 있다.
둘 다 같은 일을 시키면 결과가 중복된다.
역할을 나누는 편이 좋다.
Claude
→ Figma 구조 분석
→ 수치 추출
→ layout-contract 작성
→ 구현 결과 리뷰
Codex
→ 실제 저장소 코드 수정
→ SwiftUI / Compose 구현
→ 테스트와 빌드 실행
→ 리뷰 지적 사항 반영
이렇게 나누면 장점이 있다.
Claude는 디자인 의도와 구조를 정리한다.
Codex는 실제 코드베이스 안에서 수정한다.
Claude는 다시 diff와 screenshot을 보고 리뷰한다.
Codex는 필요한 부분만 고친다.
구현자와 리뷰어가 분리된다.
UI 작업에서도 이 분리가 중요하다.
Claude에는 이런 식으로 시킨다.
Figma MCP에서 아래 node를 읽고 UI Implementation Pack을 만들어줘.
대상:
- iOS SwiftUI
- Android Jetpack Compose
출력:
- layout-contract.json
- design-tokens.json
- component-map.md 초안
- ambiguity-report.md
주의:
- 이미지를 보고 추측하지 말고 Figma layout data를 우선해.
- Auto Layout direction, gap, padding, fixed/hug/fill 값을 분리해.
- iOS pt, Android dp/sp로 매핑할 때 애매한 부분은 ambiguity-report.md에 적어.
- 실제 코드는 아직 작성하지 마.
중요한 것은 “아직 코드 작성하지 마”다.
먼저 수치를 정리해야 한다.
수치가 틀리면 그 다음 구현은 계속 흔들린다.
Figma 데이터가 있어도 애매한 부분은 반드시 생긴다.
예를 들어 이런 것들이다.
- Text line-height가 코드 토큰에 없음
- Figma에는 17px인데 DesignSystem에는 16 또는 18만 있음
- 카드 shadow가 iOS 기본 shadow와 정확히 매칭되지 않음
- Android Compose에서 font rendering이 iOS와 다르게 보임
- 이미지 asset이 export되지 않음
- Hug height가 실제 데이터 길이에 따라 달라짐
이런 건 AI가 조용히 결정하면 안 된다.
보고서로 남겨야 한다.
# Ambiguity Report
## 1. Typography mismatch
Figma:
- fontSize: 17
- lineHeight: 24
- weight: 600
Current iOS tokens:
- bodyMedium: 16 / 22 / 400
- titleSmall: 16 / 22 / 700
- titleMedium: 18 / 24 / 700
Recommendation:
Use `titleMedium` only if design owner approves.
Otherwise add a new token through DesignSystem process.
## 2. Shadow mismatch
Figma:
- y: 4
- blur: 16
- opacity: 0.08
Current DesignSystem:
- `shadow.cardSmall`
- `shadow.cardMedium`
Recommendation:
Use `shadow.cardSmall`.
Do not create a new shadow token in this task.
UI 구현에서 애매함을 숨기면 디자이너 리뷰에서 터진다.
애매한 부분은 구현 전에 노출해야 한다.
이제 Codex에는 실제 코드 작업을 맡긴다.
.ai/ui-tasks/checkout-summary-card/ 문서를 읽고 iOS SwiftUI 구현을 진행해줘.
반드시 읽을 파일:
- task.md
- layout-contract.json
- design-tokens.json
- component-map.md
- platform-rules.md
- validation.md
수정 가능 파일:
- CheckoutSummaryCardView.swift
- CheckoutSummaryCardViewModel.swift
- CheckoutSummaryCardPreview.swift
수정 금지:
- DesignSystem/**
- Network/**
- Payment/**
- Package.swift
- project.yml
규칙:
- layout-contract.json의 수치를 우선한다.
- 존재하는 DesignSystem token을 사용한다.
- 새 색상, 새 폰트, 새 컴포넌트를 만들지 않는다.
- 애매한 부분은 구현하지 말고 report에 남긴다.
- 완료 후 변경 파일, 실행 명령, 테스트 결과를 보고한다.
Codex가 repo 안에서 실제 파일을 수정하는 역할이다.
Claude는 이 단계에서 구현자가 아니라 reviewer로 남겨둔다.
예를 들어 CheckoutSummaryCardView는 이런 식으로 구현할 수 있다.
import SwiftUI
struct CheckoutSummaryCardView: View {
let model: CheckoutSummaryCardModel
var body: some View {
VStack(alignment: .leading, spacing: Layout.containerSpacing) {
header
Divider()
.background(Color.dsBorderSubtle)
rows
totalRow
.padding(.top, Layout.totalTopPadding)
}
.padding(.top, Layout.paddingTop)
.padding(.leading, Layout.paddingLeading)
.padding(.bottom, Layout.paddingBottom)
.padding(.trailing, Layout.paddingTrailing)
.background(Color.dsSurfaceCard)
.clipShape(
RoundedRectangle(
cornerRadius: Layout.cornerRadius,
style: .continuous
)
)
}
private var header: some View {
HStack(spacing: Layout.headerSpacing) {
Image(.receipt24)
.resizable()
.frame(
width: Layout.iconSize,
height: Layout.iconSize
)
Text(model.title)
.font(.dsTitleMedium)
.foregroundStyle(Color.dsTextPrimary)
}
}
private var rows: some View {
VStack(spacing: Layout.rowSpacing) {
ForEach(model.rows) { row in
HStack {
Text(row.label)
.font(.dsBodyMedium)
.foregroundStyle(Color.dsTextSecondary)
Spacer(minLength: 12)
Text(row.value)
.font(.dsBodySemibold)
.foregroundStyle(Color.dsTextPrimary)
}
}
}
}
private var totalRow: some View {
HStack {
Text(model.totalLabel)
.font(.dsTitleSmall)
.foregroundStyle(Color.dsTextPrimary)
Spacer(minLength: 12)
Text(model.totalValue)
.font(.dsTitleMedium)
.foregroundStyle(Color.dsTextPrimary)
}
}
}
private enum Layout {
static let paddingTop: CGFloat = 20
static let paddingLeading: CGFloat = 20
static let paddingBottom: CGFloat = 20
static let paddingTrailing: CGFloat = 20
static let containerSpacing: CGFloat = 16
static let headerSpacing: CGFloat = 8
static let rowSpacing: CGFloat = 12
static let totalTopPadding: CGFloat = 4
static let iconSize: CGFloat = 24
static let cornerRadius: CGFloat = 16
}
이 코드의 핵심은 화려한 SwiftUI가 아니다.
수치의 출처가 분명하다는 점이다.
Layout 값
→ layout-contract.json 기준
Color / Font
→ design-tokens.json과 DesignSystem 기준
Component
→ component-map.md 기준
AI가 숫자를 감으로 만든 것이 아니라 계약 파일에 따라 구현한 구조다.
SwiftUI 구현에서 AI가 자주 틀리는 지점은 다음이다.
- VStack spacing과 내부 padding을 혼동한다.
- frame height를 불필요하게 고정한다.
- Text lineLimit을 임의로 넣는다.
- Spacer를 잘못 넣어 Figma의 Fill 동작과 달라진다.
- cornerRadius만 넣고 continuous style을 놓친다.
- shadow를 임의 값으로 만든다.
- asset 크기를 원본 크기와 다르게 잡는다.
- Dynamic Type 대응을 고려하지 않는다.
그래서 platform-rules.md에 iOS 규칙을 둔다.
# iOS Platform Rules
## SwiftUI
- Prefer VStack/HStack based on Figma Auto Layout.
- Do not use absolute position unless the Figma layer is explicitly absolute.
- Do not fix height when Figma uses hug content.
- Use DesignSystem font tokens.
- Use DesignSystem color tokens.
- Use `.frame(width:height:)` only for fixed-size icons and assets.
- Do not invent shadows.
- Do not add `.lineLimit(1)` unless Figma or product requirement says so.
- Keep layout values in a private `Layout` enum.
이 규칙이 있으면 Codex가 SwiftUI를 훨씬 안정적으로 작성한다.
Android도 같은 방식으로 간다.
@Composable
fun CheckoutSummaryCard(
model: CheckoutSummaryCardModel,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.background(
color = AppTheme.colors.surfaceCard,
shape = RoundedCornerShape(Layout.CornerRadius)
)
.padding(
start = Layout.PaddingHorizontal,
top = Layout.PaddingVertical,
end = Layout.PaddingHorizontal,
bottom = Layout.PaddingVertical
),
verticalArrangement = Arrangement.spacedBy(Layout.ContainerSpacing)
) {
Header(title = model.title)
HorizontalDivider(
color = AppTheme.colors.borderSubtle,
thickness = 1.dp
)
Column(
verticalArrangement = Arrangement.spacedBy(Layout.RowSpacing)
) {
model.rows.forEach { row ->
SummaryRow(row)
}
}
TotalRow(
label = model.totalLabel,
value = model.totalValue,
modifier = Modifier.padding(top = Layout.TotalTopPadding)
)
}
}
@Composable
private fun Header(title: String) {
Row(
horizontalArrangement = Arrangement.spacedBy(Layout.HeaderSpacing),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(R.drawable.ic_receipt_24),
contentDescription = null,
modifier = Modifier.size(Layout.IconSize),
tint = AppTheme.colors.textPrimary
)
Text(
text = title,
style = AppTheme.typography.titleMedium,
color = AppTheme.colors.textPrimary
)
}
}
@Composable
private fun SummaryRow(row: CheckoutSummaryRow) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = row.label,
style = AppTheme.typography.bodyMedium,
color = AppTheme.colors.textSecondary
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = row.value,
style = AppTheme.typography.bodySemibold,
color = AppTheme.colors.textPrimary,
modifier = Modifier.weight(1f),
textAlign = TextAlign.End
)
}
}
@Composable
private fun TotalRow(
label: String,
value: String,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = label,
style = AppTheme.typography.titleSmall,
color = AppTheme.colors.textPrimary
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = value,
style = AppTheme.typography.titleMedium,
color = AppTheme.colors.textPrimary,
modifier = Modifier.weight(1f),
textAlign = TextAlign.End
)
}
}
private object Layout {
val PaddingHorizontal = 20.dp
val PaddingVertical = 20.dp
val ContainerSpacing = 16.dp
val HeaderSpacing = 8.dp
val RowSpacing = 12.dp
val TotalTopPadding = 4.dp
val IconSize = 24.dp
val CornerRadius = 16.dp
}
Compose도 SwiftUI와 마찬가지로 핵심은 수치 출처다.
Figma px
→ Android dp
Figma text size
→ Android sp
Figma Auto Layout
→ Column / Row / Arrangement.spacedBy
Figma constraints
→ Modifier.weight, fillMaxWidth, wrapContentHeight
단, Android에서는 텍스트 크기는 sp, layout 간격은 dp를 사용한다.
이 차이를 AI에게 명확히 알려줘야 한다.
Compose에서 AI가 자주 틀리는 부분도 있다.
- Row 안에서 weight 위치를 잘못 잡는다.
- dp와 sp를 섞는다.
- padding과 Arrangement.spacedBy를 중복 적용한다.
- fillMaxWidth를 남발한다.
- Figma의 Hug를 고정 height로 바꾼다.
- TextAlign.End 없이 값 텍스트가 어긋난다.
- Material 기본 padding이 섞여 Figma와 달라진다.
그래서 Android 규칙도 따로 둔다.
# Android Platform Rules
## Jetpack Compose
- Map Figma layout values to dp.
- Map font size to sp through typography tokens.
- Use Column and Row based on Figma Auto Layout.
- Prefer `Arrangement.spacedBy` for Auto Layout gap.
- Do not use fixed height for hug content.
- Do not use Material default padding if Figma specifies custom padding.
- Use existing theme tokens only.
- Do not create new colors or typography tokens.
- Use `Modifier.weight(1f)` only when Figma uses fill behavior.
여기서 중요한 말을 하나 해야 한다.
AI로 UI를 만들 때 목표를 “완벽한 pixel perfect”로 잡으면 실망하기 쉽다.
iOS와 Android는 렌더링 방식이 다르다.
폰트 렌더링
line-height 처리
shadow 표현
anti-aliasing
safe area
status bar
device density
dynamic type
font scale
이런 차이 때문에 실제 스크린샷은 완전히 같을 수 없다.
그래서 목표는 이렇게 잡는 편이 좋다.
나쁜 목표:
Figma 이미지와 100% 똑같이 보이게 만들어라.
좋은 목표:
Figma의 layout contract, token, component mapping을 지켜라.
허용 오차 안에서 screenshot diff를 통과시켜라.
즉, 수치 정확도는 “이미지와 감으로 비슷함”이 아니라 “계약된 수치를 지켰음”으로 봐야 한다.
# Validation
## Required
- Build must pass.
- Screenshot must be captured for reference state.
- Layout values must match `layout-contract.json`.
- No changes outside allowed files.
- No new design tokens.
- No new dependencies.
## Screenshot Tolerance
- Position tolerance: 2pt on iOS, 2dp on Android
- Text rendering differences are allowed within platform font rendering limits.
- Color must use existing DesignSystem token.
- Shadow may use closest existing token if exact match is unavailable.
## Manual Review Focus
- Header alignment
- Row spacing
- Total row emphasis
- Card padding
- Text wrapping
- Dark mode
- Long price value
UI 검증에서는 자동 테스트와 수동 리뷰 기준을 함께 둔다.
Figma 수치대로 구현해도 실제 화면은 다를 수 있다.
그래서 스크린샷이 필요하다.
흐름은 이렇게 잡는다.
1. Codex가 UI 코드 구현
2. iOS Preview 또는 UI Test로 screenshot 생성
3. Android Preview 또는 screenshot test로 이미지 생성
4. Figma reference와 비교
5. Claude가 screenshot + layout-contract 기준으로 리뷰
6. Codex가 허용 파일 안에서 수정
Claude 리뷰 프롬프트는 이렇게 줄 수 있다.
다음 자료를 기준으로 UI 구현을 리뷰해줘.
입력:
- Figma reference screenshot
- iOS rendered screenshot
- Android rendered screenshot
- layout-contract.json
- design-tokens.json
- diff.patch
확인할 것:
- padding
- spacing
- alignment
- typography
- radius
- color token
- component mapping
- platform-specific mismatch
출력:
- pass / needs_changes / blocked
- mismatch list
- severity
- suggested fix
- 수정해야 할 파일
이때 Claude에게 바로 코드를 수정하게 하지 않는다.
리뷰만 시킨다.
수정은 Codex가 한다.
Figma MCP 또는 Dev Mode 정보를 사용할 때는 이런 프롬프트가 좋다.
선택한 Figma node에서 UI 구현에 필요한 수치를 추출해줘.
반드시 포함:
- frame size
- Auto Layout direction
- padding
- gap
- child order
- fixed / hug / fill
- text styles
- color variables
- radius
- shadow
- asset export names
- component variants
출력 형식:
- layout-contract.json
- design-tokens.json
- ambiguity-report.md
주의:
- 코드 작성 금지
- 추측 금지
- 누락된 값은 null로 두고 ambiguity-report에 작성
- 기존 DesignSystem과 매핑되지 않는 token은 새로 만들지 말고 보고
여기서 가장 중요한 단어는 추측 금지다.
AI는 빈칸을 잘 채운다.
UI 구현에서는 그게 오히려 위험하다.
UI Implementation Pack을 기준으로 iOS SwiftUI 코드를 구현해줘.
작업 순서:
1. task.md 읽기
2. layout-contract.json 읽기
3. design-tokens.json 읽기
4. component-map.md 읽기
5. platform-rules.md 읽기
6. 기존 DesignSystem token 확인
7. CheckoutSummaryCardView.swift 구현
8. Preview 추가
9. 빌드 가능 여부 확인
10. 결과 보고
제약:
- layout-contract.json에 없는 수치 임의 사용 금지
- 새 DesignSystem token 추가 금지
- DesignSystem 파일 수정 금지
- ViewModel 또는 API 로직 수정 금지
- 애매한 값은 구현하지 말고 ambiguity-report에 추가
Android 구현은 이렇게 바꾼다.
UI Implementation Pack을 기준으로 Android Jetpack Compose 코드를 구현해줘.
제약:
- layout 값은 dp
- text size는 typography token의 sp
- Auto Layout gap은 Arrangement.spacedBy 사용
- Hug content는 fixed height로 바꾸지 않기
- Material 기본 padding이 Figma 수치와 충돌하면 custom layout 사용
- 새 color token 추가 금지
구현된 UI를 리뷰해줘.
입력:
- layout-contract.json
- design-tokens.json
- component-map.md
- platform-rules.md
- diff.patch
- rendered screenshot
리뷰 기준:
- 수치가 contract와 일치하는가
- 기존 DesignSystem token을 사용했는가
- 임의 수치가 들어갔는가
- Figma Auto Layout이 SwiftUI/Compose 구조로 올바르게 옮겨졌는가
- 수정 금지 파일을 건드렸는가
- iOS와 Android의 플랫폼 차이를 고려했는가
출력:
## Verdict
pass / needs_changes / blocked
## Mismatches
- file
- issue
- expected
- actual
- severity
## Required Fixes
## Human Review Needed
리뷰 결과가 needs_changes면 Codex에게 그 항목만 수정하게 한다.
저장소 루트에 AI용 규칙을 둔다.
# AGENTS.md
## UI Implementation Rules
- Do not implement UI from screenshots alone when Figma metadata is available.
- Use layout-contract.json as the source of truth for spacing and size.
- Use design-tokens.json and existing DesignSystem tokens for color and typography.
- Do not create new tokens without approval.
- Do not modify DesignSystem unless the task explicitly allows it.
- Do not use absolute positioning unless Figma explicitly uses absolute positioning.
- Prefer SwiftUI stacks and Compose Row/Column based on Figma Auto Layout.
- Do not claim visual match without screenshot evidence.
## Completion Report
Every UI task must report:
- Files changed
- New hardcoded layout values
- Token mapping
- Build result
- Screenshot validation result
- Remaining mismatches
- Human review focus
이 규칙이 있으면 다음 UI 작업에서도 재사용할 수 있다.
처음부터 완벽한 자동화를 만들 필요는 없다.
현실적인 순서는 이렇다.
Claude가 Figma node를 읽고 layout-contract.json을 만든다.
개발자는 그걸 확인한다.
전체 화면이 아니라 카드, 셀, 버튼 같은 단위부터 시작한다.
iOS와 Android 렌더링 이미지를 남긴다.
Claude가 Figma reference와 비교해 mismatch를 정리한다.
반복되는 색상, 폰트, radius, shadow를 token으로 정리한다.
중요 화면부터 자동 비교를 붙인다.
AI 코딩 에이전트는 UI 작업에서 꽤 강력하다.
하지만 UI는 코드가 컴파일된다고 끝나는 작업이 아니다.
수치가 맞아야 한다.
디자인 토큰이 맞아야 한다.
상태별 화면이 맞아야 한다.
플랫폼 차이를 고려해야 한다.
디자이너가 리뷰할 수 있어야 한다.
AI에게 이미지만 던지고 “똑같이 만들어줘”라고 하면 결과는 늘 애매하다.
앞으로 중요한 개발 역량은 이렇게 바뀐다.
Figma 데이터를 구조화하는 능력
디자인 토큰을 코드 토큰과 매핑하는 능력
Codex와 Claude 역할을 나누는 능력
수치 계약 파일을 만드는 능력
스크린샷 기반으로 검증하는 능력
AI가 추측하지 못하게 막는 능력
AI가 UI를 대신 짜주는 시대가 온다고 해도, 개발자가 할 일은 사라지지 않는다.
오히려 더 명확해진다.
디자인 의도를 코드로 옮길 수 있는 계약을 만드는 일
이 계약이 없으면 AI는 그림을 보고 감으로 코딩한다.
계약이 있으면 AI는 수치를 보고 구현한다.
Figma에서 전달받은 UI를 Codex와 Claude로 구현할 때 핵심은 모델 성능이 아니다.
입력의 품질이다.
나쁜 입력:
이미지 하나
좋은 입력:
Figma node
layout-contract.json
design-tokens.json
component-map.md
platform-rules.md
validation.md
그리고 역할 분리가 필요하다.
Claude
→ Figma 분석과 리뷰
Codex
→ 실제 코드 구현과 제한 수정
Screenshot Test
→ 렌더링 결과 검증
Human
→ 애매한 디자인 결정 승인
iOS SwiftUI든 Android Compose든 원칙은 같다.
Figma Auto Layout을 플랫폼 layout으로 옮긴다.
Figma token을 DesignSystem token으로 매핑한다.
임의 수치를 줄인다.
스크린샷으로 검증한다.
애매한 부분은 보고하고 멈춘다.
한 줄로 정리하면 이렇다.
AI에게 UI를 맡길 때 중요한 건
“비슷하게 만들어줘”가 아니라
“이 수치 계약을 지켜서 구현해줘”라고 말하는 것이다.
이제 UI 개발에서 AI를 잘 쓰는 개발자는 프롬프트를 잘 쓰는 사람이 아니다.
Figma와 코드 사이에 정확한 계약서를 만들 줄 아는 사람이다.