figma-lens: Figma와 일치하는 UI 구축하기
figma-lens는 Figma 플러그인과 로컬 브리지로 구성된 도구입니다. 사용자가 Figma 데스크톱에서 프레임을 선택하고 Export to Claude를 클릭하면 브리지가 프레임당 하나의 폴더를 생성합니다:
design/
INDEX.md ← 내보낸 모든 프레임 목록 (여기서 시작)
<frame>/
spec.md ← 먼저 읽기: 정확한 값이 포함된 간결한 레이어 트리
spec.json ← 전체 데이터 (spec.md로 부족할 때 Figma의 레이어별 CSS 포함)
tokens.json ← 변수 (모든 모드), 스타일, 원시 값 사용 횟수
[email protected] ← 전체 렌더링 (기본 2x)
sections/NN-name.png ← 최상위 블록의 전체 해상도 크롭
assets/*.svg ← 아이콘/벡터 아트, 바로 사용 가능
images/* ← 이미지 채움에 사용된 원본 비트맵
compare/ ← `figma-lens check` 에 의해 생성됨 (report.md, side-by-side.png, diff.png)
figma-lens.map.json ← (프로젝트 루트) Figma 컴포넌트/토큰 → 이 프로젝트의 실제 코드
figma-lens 명령어는 figma-lens 저장소의 CLI입니다 (거기서 npm link 하면 전역으로 사용 가능; 그렇지 않으면 node <figma-lens>/bin/figma-lens.js … 실행). 모든 명령어는 figma-lens help로 확인하세요.
내보내기가 아직 없는 경우
- 사용자 프로젝트의 브리지를 시작하고 백그라운드에서 계속 실행합니다:
figma-lens bridge --out <project>/design - 사용자에게 Figma 데스크톱에서 프레임을 선택하도록 요청합니다 → 플러그인 → 개발 → Figma Lens → Export to Claude
design/INDEX.md를 읽어 내보내기가 완료되었는지 확인합니다.
내보내기가 가능할 때 디자인을 설명으로 추측하지 마세요. 내보내기를 요청하세요.
워크플로우
spec.md를 완전히 읽습니다 (작습니다). 프레임 크기, 레이아웃 트리, 변수 및 스타일, 에셋을 확인합니다.- 이미지를 봅니다.
sections/*.png를 하나씩 읽어 세부 사항을 확인합니다.[email protected]는 전체 그림을 볼 때만 사용합니다. 큰 이미지는 볼 때 축소되기 때문입니다. 값은 항상 스펙에서 가져오며, 픽셀을 측정하지 않습니다. - 컴포넌트 맵을 사용합니다 (아래 "컴포넌트 맵" 참조). spec.md의 모든
⇒ USE ...는 필수입니다: 해당 위젯/컴포넌트와 프로퍼티 매핑을 사용하고, 매핑된 토큰((color/primary ⇒ AppColors.primary))을 원시 값 대신 사용합니다.(not in map)으로 표시된 항목은 코드베이스에서 기존 테마와 위젯(FlutterThemeData,AppColors,TextTheme, 간격 상수,lib/widgets; 웹 CSS 변수, Tailwind 설정,src/components)을 검색합니다. 토큰이 없는 경우에만 원시 값을 사용하고, 추가한 값을 사용자에게 알려줍니다. - 컴포넌트를 재사용합니다.
component Button {Type=Primary}로 표시된 레이어는 프로젝트의 기존 Button 위젯 또는 일치하는 props를 가진 컴포넌트를 사용해야 합니다. 기존 것이 없을 때만 새로 만들고, 재사용 가능하게 만든 후 맵에 추가합니다. - 트리를 따라 위에서 아래로 구축합니다. 자동 레이아웃은 Row/Column/flex로 매핑됩니다(아래 표 참조).
@x,y레이어(자동 레이아웃이 없거나absolute)만 Stack/Positioned 또는 절대 위치 지정이 필요합니다. - 내보낸 파일을 사용합니다.
assets/*.svg와images/*를 프로젝트의 에셋 폴더에 넣고 참조합니다. 아이콘을 다시 그리거나 다른 아이콘 세트로 대체하지 마세요. - 텍스트를 정확히 복사합니다 spec.md의
"…"에서 대소문자, 구두점 및 아랍어/RTL 문자열을 포함하여 복사합니다. 여러run줄이 있는 텍스트는 리치 텍스트(TextSpan/중첩 spans)가 필요합니다. - 비교-수정 루프로 검증합니다 (아래 참조). 코드를 읽고 완료되었다고 선언하지 마세요. 스크린샷 diff로 증명하세요.
비교-수정 루프
화면을 구축한 후, 실제 결과를 Figma와 비교하고 다른 점을 수정합니다:
- 빌드를 실행하여 해당 화면을 표시합니다:
- 웹: dev 서버를 시작하고 경로를 엽니다.
- Flutter 웹:
flutter run -d web-server --web-port 8080, 그런 다음 해당 URL을 사용합니다. - Flutter Android: 에뮬레이터/기기에서 앱을 실행하고 화면으로 이동합니다.
- 확인합니다:
- 웹 / Flutter 웹:
figma-lens check --design design/<frame> --url http://localhost:<port>/<route>(뷰포트는 프레임 크기와 배율로 자동 설정됩니다). - Android:
figma-lens check --design design/<frame> --adb --crop-top <상태 표시줄 px> [--crop-bottom <내비게이션 바 px>](상태 표시줄 ≈ 24–48 dp × 기기 밀도; 앱 콘텐츠만 비교되도록 자릅니다).
- 웹 / Flutter 웹:
design/<frame>/compare/report.md를 읽고 (불일치 %, 가장 큰 차이 영역 및 각 영역의 Figma 레이어 ID)compare/side-by-side.png를 봅니다 (Figma | 빌드 | diff, 빨간색 = 다름).- 위에서 아래로 수정합니다: 높이 또는 간격 차이를 먼저 수정합니다(아래 모든 것을 이동시킴). 그런 다음 크기, 색상, 타이포그래피, 반경, 그림자를 수정합니다. 나열된 각 레이어 ID를 spec.md에서 정확한 값을 찾습니다.
- 확인을 다시 실행합니다. 보고서에는 이전 점수가 표시되어 진행 상황을 볼 수 있습니다.
- 불일치가 ≤ 3%일 때 중지합니다 (텍스트 안티앨리어싱은 0에 도달하지 않음) 또는 두 번 연속 실행에서 더 이상 개선되지 않을 때 중지합니다. 그런 다음 최종 점수와 일치시킬 수 없었던 항목 및 그 이유(누락된 글꼴, 플랫폼 렌더링, 동적 데이터)를 보고합니다.
규칙: 실제 코드를 수정하고, diff를 속이지 마세요(앱 내에 디자인의 스크린샷이나 이미지 없음). 확인할 때 디자인과 동일한 데이터/텍스트를 사용합니다(필요시 모의 데이터). 그렇지 않으면 텍스트 차이가 점수를 지배합니다.
컴포넌트 맵
figma-lens.map.json (프로젝트 루트)은 Figma 컴포넌트와 토큰을 이 프로젝트의 코드에 매핑합니다:
{
"components": {
"Button": { "flutter": "lib/widgets/app_button.dart#AppButton", "web": "src/components/Button.tsx#Button", "props": { "Label": "label", "Type": "variant" } },
"Icon/*": "SvgPicture.asset('assets/icons/<name>.svg')"
},
"tokens": { "color/primary": { "flutter": "AppColors.primary", "web": "var(--color-primary)" } }
}
- 맵이 아직 없음 →
figma-lens map --design design을 실행합니다. 내보내기의 모든 컴포넌트와 토큰을 빈 값("")으로 추가합니다. - 직접 채웁니다: 각 컴포넌트(이름, 모양, props로)와 각 토큰 값을 코드베이스에서 검색하고
path#Symbol(또는 표현식)을 작성합니다. 아직 존재하지 않는 경우에만""를 남깁니다."Prefix/*"는 전체 패밀리(아이콘)와 일치합니다. - 맵을 편집한 후
figma-lens render design/<frame>을 실행하여 spec.md에⇒ USE화살표가 표시되도록 합니다. 브리지는 새 내보내기 시에도 맵을 적용합니다. - 매핑되지 않은 컴포넌트에 대해 새 재사용 가능 위젯을 만들면 맵에 추가합니다.
spec.md 읽기
- 단위는 디자인 px이며, CSS px 및 Flutter 논리적 px와 같습니다. 스크린샷 배율을 곱하지 마세요.
row|column gap G pad t,r,b,l main:X cross:Y는 자동 레이아웃입니다.pad 16은 네 면 모두 16입니다.gap auto는 space-between입니다.w:FILL h:HUG는 각 축의 크기 조정을 제공합니다: FIXED(표시된 정확한 크기), HUG(내용에 맞춤) 또는 FILL(사용 가능한 공간 차지).@x,y는 부모 내의 위치입니다.absolute는 자동 레이아웃 내의 절대 위치 자식입니다.pin H/V는 제약 조건을 제공합니다(MIN = 왼쪽/위쪽, MAX = 오른쪽/아래쪽, STRETCH = 둘 다, CENTER, SCALE).bg A + B는 페인트를 아래에서 위로 나열합니다.(name)은 Figma 변수이고[style name]은 Figma 스타일입니다.- 색상은
#RRGGBB또는#RRGGBBAA입니다(알파가 마지막). - 텍스트는
"문자" · Family Style (weight) size/lineHeight color형식을 사용합니다. lineHeight는 px, % 또는 auto입니다.ls는 글자 간격(px) 또는 글꼴 크기의 %입니다. radius 12는 모든 모서리를 설정합니다.radius tl,tr,br,bl은 각 모서리를 설정합니다.smoothing은 iOS 스타일 모서리 부드럽게 처리입니다. 근사하거나 Flutter에서 squircle 패키지를 사용하지 않는 한 무시합니다.drop-shadow x,y blur B spread S color,inner-shadow,layer-blur R,background-blur R.→ assets/x.svg는 레이어가 아이콘임을 의미합니다. 나열된 크기로 SVG를 사용하세요.vars{itemSpacing=space/md}는 레이어 속성이 해당 변수에 바인딩됨을 의미합니다. 토큰을 사용하세요.
Flutter 매핑
| Figma | Flutter |
|---|---|
| column / row | Column / Row (Flutter ≥ 3.27에서는 spacing: G 사용, 그렇지 않으면 SizedBox 간격) |
| wrap, rowGap R | Wrap(spacing: G, runSpacing: R) |
| pad t,r,b,l | Padding(padding: EdgeInsets.fromLTRB(l, t, r, b)). 순서에 주의하세요. |
| main MIN / CENTER / MAX / SPACE_BETWEEN | MainAxisAlignment.start / center / end / spaceBetween |
| cross MIN / CENTER / MAX / BASELINE | CrossAxisAlignment.start / center / end / baseline (baseline에는 textBaseline 필요) |
| HUG on main axis | mainAxisSize: MainAxisSize.min |
| FILL on parent's main axis | Expanded (또는 Flexible); grow N은 flex: N 설정 |
| FILL on parent's cross axis | 부모에서 CrossAxisAlignment.stretch, 또는 SizedBox(width: double.infinity) |
| FIXED w×h | SizedBox(width: w, height: h) |
@x,y / absolute | Stack + Positioned(left: x, top: y); 오른쪽/아래쪽에는 pin 사용 |
| bg solid | BoxDecoration(color: Color(0xAARRGGBB)). 알파를 앞으로 이동: #11223380 → 0x80112233 |
| gradient-linear from/to | LinearGradient(begin: Alignment(fx*2-1, fy*2-1), end: Alignment(tx*2-1, ty*2-1), colors, stops) spec.json의 from/to 사용 |
| radius | BorderRadius.circular(r) 또는 BorderRadius.only(...) |
| border W color inside | Border.all(width: W, color: …) (inside는 Flutter의 기본값; center/outside는 strokeAlign 필요) |
| drop-shadow x,y blur B spread S | BoxShadow(offset: Offset(x, y), blurRadius: B, spreadRadius: S, color: …) |
| background-blur R | ClipRRect + BackdropFilter(filter: ImageFilter.blur(sigmaX: R/2, sigmaY: R/2)) |
| opacity | Opacity (또는 페인트의 색상 알파) |
| text size/lineHeight | TextStyle(fontSize: size, height: lineHeight / size). %의 경우: height: pct/100. Figma의 수직 중앙 정렬과 일치시키려면 leadingDistribution: TextLeadingDistribution.even 설정 |
| weight 600 | FontWeight.w600 |
| ls px / ls % | letterSpacing: px / letterSpacing: size * pct / 100 |
| ellipsis maxLines N | maxLines: N, overflow: TextOverflow.ellipsis |
| align CENTER | textAlign: TextAlign.center. RTL 화면의 경우 TextAlign.start 및 EdgeInsetsDirectional 사용 |
| assets/x.svg | SvgPicture.asset('assets/…/x.svg', width:, height:) (flutter_svg) |
| image fill FILL / FIT | Image.asset(fit: BoxFit.cover / BoxFit.contain) |
웹 (CSS / Tailwind / React) 매핑
| Figma | CSS |
|---|---|
| column / row | display:flex; flex-direction: column / row; gap: Gpx |
| wrap, rowGap R | flex-wrap: wrap; column-gap: G; row-gap: R |
| pad t,r,b,l | padding: t r b l (CSS와 동일한 순서) |
| main MIN / CENTER / MAX / SPACE_BETWEEN | justify-content: flex-start / center / flex-end / space-between |
| cross MIN / CENTER / MAX / BASELINE | align-items: flex-start / center / flex-end / baseline |
| HUG | width/height: fit-content (또는 그냥 auto) |
| FILL (main axis) | flex: 1 1 0; min-width: 0 |
| FILL (cross axis) | align-self: stretch |
| FIXED | width: Wpx; height: Hpx; flex-shrink: 0 |
@x,y / absolute | 부모 position: relative; 자식 position:absolute; left:x; top:y (오른쪽/아래쪽에 pin 사용) |
colors #RRGGBBAA | 그대로 유효한 CSS |
gradient-linear Ndeg | linear-gradient(Ndeg, c1 p1%, c2 p2%) |
| drop-shadow | box-shadow: x y B S color (inner-shadow는 inset 추가) |
| layer-blur / background-blur R | filter: blur(R/2 px) / backdrop-filter: blur(R/2 px) |
| text | font: weight size/lineHeight family; letter-spacing (ls % → pct/100 em) |
| ellipsis maxLines N | display:-webkit-box; -webkit-line-clamp:N; -webkit-box-orient:vertical; overflow:hidden |
| assets/x.svg | <img src> 또는 인라인 SVG; 내보낸 크기 유지 |
spec.json에는 대부분의 레이어에 대해 Figma가 자체 생성한 CSS(css)도 있습니다. CSS 값을 교차 확인하는 데 사용하세요. 토큰 및 레이아웃 구조는 여전히 위의 트리에서 가져옵니다.
규칙
- 스펙 값은 이미지에서 보는 것보다 우선합니다. 충돌이 있을 경우 스펙을 신뢰하고 충돌을 언급하세요.
- 사용자가 토큰으로 정규화를 요청하지 않는 한 값을 반올림하거나 "개선"하지 마세요(14는 14로 유지, 16이 아님).
- 숨겨진 레이어는 내보내지지 않습니다. 내보내기에 없는 상태(호버, 오류, 빈 상태)를 만들지 마세요. 해당 프레임을 요청하세요.
- 누락된 글꼴을 표시하세요. 글꼴 패밀리가 프로젝트에 번들되지 않은 경우, 그렇게 말하고 사용자에게 추가할 사항(pubspec fonts / @font-face)을 알려주세요.
- 여러 중단점이나 상태의 경우 각 프레임을 내보내도록 사용자에게 요청하세요. 각각이 자체 폴더가 됩니다.