figma-lens: crea UI que coincida con Figma
figma-lens es un plugin de Figma más un puente local. El usuario selecciona frames en Figma desktop y hace clic en Exportar a Claude. El puente escribe una carpeta por frame:
design/
INDEX.md ← lista de todos los frames exportados (empieza aquí)
<frame>/
spec.md ← LÉELO PRIMERO: árbol de capas compacto con valores exactos
spec.json ← datos completos (+ CSS por capa desde Figma) cuando spec.md no es suficiente
tokens.json ← variables (todos los modos), estilos, recuentos de uso de valores sin procesar
[email protected] ← render completo (2x por defecto)
sections/NN-nombre.png ← recortes de los bloques de nivel superior a resolución completa
assets/*.svg ← iconos/arte vectorial, listos para usar
images/* ← mapas de bits originales usados como rellenos de imagen
compare/ ← escrito por `figma-lens check` (report.md, side-by-side.png, diff.png)
figma-lens.map.json ← (raíz del proyecto) componente/token de Figma → código real en este proyecto
El comando figma-lens es la CLI del repositorio figma-lens (npm link ahí lo hace global; si no, ejecuta node <figma-lens>/bin/figma-lens.js …). Ejecuta figma-lens help para todos los comandos.
Si aún no hay exportación
- Inicia el puente para el proyecto del usuario y mantenlo ejecutándose (en segundo plano):
figma-lens bridge --out <proyecto>/design. - Pide al usuario que seleccione los frames en Figma desktop → Plugins → Development → Figma Lens → Exportar a Claude.
- Lee
design/INDEX.mdpara confirmar que llegó.
Nunca adivines un diseño a partir de una descripción cuando sea posible una exportación. Pide la exportación.
Flujo de trabajo
- Lee
spec.mdcompletamente (es pequeño). Anota el tamaño del frame, el árbol de diseño, las variables y estilos, y los recursos. - Mira las imágenes. Lee
sections/*.pnguno por uno para ver los detalles. Usa[email protected]solo para la vista general, porque las imágenes grandes se reducen al visualizarlas. Los valores siempre provienen de la especificación, nunca de medir píxeles. - Usa el mapa de componentes (ver "Mapa de componentes" más abajo). Cada
⇒ USA …en spec.md es obligatorio: usa ese widget/componente y su mapeo de props, y usa los tokens mapeados ((color/primary ⇒ AppColors.primary)) en lugar de valores sin procesar. Para los elementos marcados como(no en el mapa), busca en el código base el tema y widgets existentes (FlutterThemeData,AppColors,TextTheme, constantes de espaciado,lib/widgets; web variables CSS, configuración de Tailwind,src/components). Usa valores sin procesar solo cuando no exista ningún token, e informa al usuario cuáles añadiste. - Reutiliza componentes. Una capa marcada como
component Button {Type=Primary}debe usar el widget o componente Button existente del proyecto con las props coincidentes. Construye uno nuevo solo si no existe ninguno, hazlo reutilizable y agrégalo al mapa. - Construye de arriba a abajo, siguiendo el árbol. El auto layout se asigna a Row/Column/flex (tablas más abajo). Solo las capas
@x,y(sin auto layout, oabsolute) necesitan Stack/Positioned o posicionamiento absoluto. - Usa los archivos exportados. Coloca
assets/*.svgeimages/*en la carpeta de recursos del proyecto y referéncialos. Nunca vuelvas a dibujar iconos ni sustituyas un conjunto de iconos diferente. - Copia el texto exactamente de
"…"en spec.md, incluyendo mayúsculas, puntuación y cadenas árabes/RTL. El texto con varias líneasrunnecesita texto enriquecido (TextSpan / spans anidados). - Verifica con el bucle de comparación y corrección (más abajo). No declares una pantalla terminada solo por leer el código; pruébalo con una comparación de capturas de pantalla.
Bucle de comparación y corrección
Después de construir una pantalla, compara el resultado real con Figma y corrige las diferencias:
- Ejecuta la compilación que muestra esa pantalla:
- Web: inicia el servidor de desarrollo y abre la ruta.
- Flutter web:
flutter run -d web-server --web-port 8080, luego usa esa URL. - Flutter Android: ejecuta la aplicación en un emulador/dispositivo y navega a la pantalla.
- Verifícala:
- Web / Flutter web:
figma-lens check --design design/<frame> --url http://localhost:<puerto>/<ruta>(el viewport se ajusta automáticamente al tamaño del frame y la escala). - Android:
figma-lens check --design design/<frame> --adb --crop-top <barra de estado px> [--crop-bottom <barra de navegación px>](la barra de estado ≈ 24–48 dp × densidad del dispositivo; recórtala para que solo se compare el contenido de la aplicación).
- Web / Flutter web:
- Lee
design/<frame>/compare/report.md(porcentaje de desajuste, las áreas que más difieren y los ids de capa de Figma en cada una) y miracompare/side-by-side.png(Figma | compilación | diff, rojo = diferente). - Corrige de arriba a abajo: primero las diferencias de altura o espaciado (desplazan todo lo de abajo), luego tamaños, colores, tipografía, radios, sombras. Busca cada id de capa listado en spec.md para los valores exactos.
- Vuelve a ejecutar la verificación. El informe muestra la puntuación anterior, para que puedas ver el progreso.
- Detente cuando el desajuste sea ≤ 3% (el anti-aliasing de texto nunca llega a 0) o cuando dos ejecuciones consecutivas dejen de mejorar. Luego informa la puntuación final y cualquier cosa que no hayas podido igualar, y por qué (fuente faltante, renderizado de plataforma, datos dinámicos).
Reglas: corrige el código real, nunca manipules la comparación (sin capturas de pantalla ni imágenes del diseño dentro de la aplicación). Usa los mismos datos/texto que el diseño al verificar (datos simulados si es necesario), de lo contrario las diferencias de texto dominarán la puntuación.
Mapa de componentes
figma-lens.map.json (raíz del proyecto) asigna componentes y tokens de Figma al código de este proyecto:
{
"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/<nombre>.svg')"
},
"tokens": { "color/primary": { "flutter": "AppColors.primary", "web": "var(--color-primary)" } }
}
- ¿Aún no hay mapa? → Ejecuta
figma-lens map --design design. Añade cada componente y token de las exportaciones con valores vacíos (""). - Rellénalo tú mismo: busca en el código base cada componente (por nombre, por aspecto, por props) y cada valor de token; escribe
ruta#Símbolo(o una expresión). Deja""solo cuando aún no exista nada."Prefijo/*"coincide con toda una familia (iconos). - Después de editar el mapa, ejecuta
figma-lens render design/<frame>para que spec.md muestre las flechas⇒ USA. El puente también aplica el mapa en cada nueva exportación. - Cuando crees un nuevo widget reutilizable para un componente no mapeado, agrégalo al mapa.
Lectura de spec.md
- Las unidades son px de diseño, que equivalen a px de CSS y px lógicos de Flutter. Nunca multipliques por la escala de la captura de pantalla.
row|column gap G pad s,d,i,z main:X cross:Yes auto layout.pad 16significa que los cuatro lados son 16.gap autosignifica espacio entre.w:FILL h:HUGda el tamaño en cada eje: FIXED (tamaño exacto mostrado), HUG (ajustar al contenido) o FILL (ocupar el espacio disponible).@x,yes la posición dentro del padre.absolutesignifica un hijo con posicionamiento absoluto dentro de un auto layout.pin H/Vda restricciones (MIN = izquierda/arriba, MAX = derecha/abajo, STRETCH = ambos, CENTER, SCALE).bg A + Blista las capas de pintura de abajo a arriba.(nombre)es una variable de Figma y[nombre de estilo]es un estilo de Figma.- Los colores son
#RRGGBBo#RRGGBBAA(alfa al final). - El texto usa el formato
"caracteres" · Familia Estilo (peso) tamaño/alturaLínea color. alturaLínea es px, % o auto.lses espaciado entre letras en px, o en % del tamaño de fuente. radius 12establece cada esquina.radius izq,sup,der,infestablece cada esquina.smoothinges suavizado de esquinas estilo iOS; apruxímalo, o ignóralo en Flutter a menos que se use un paquete squircle.drop-shadow x,y difuminado B propagación S color,inner-shadow,layer-blur R,background-blur R.→ assets/x.svgsignifica que la capa es un icono. Usa el SVG al tamaño indicado.vars{itemSpacing=space/md}significa que la propiedad de la capa está vinculada a esa variable. Usa el token.
Mapeo Flutter
| Figma | Flutter |
|---|---|
| columna / fila | Column / Row (usa spacing: G en Flutter ≥ 3.27, de lo contrario SizedBox como separadores) |
| wrap, rowGap R | Wrap(spacing: G, runSpacing: R) |
| pad s,d,i,z | Padding(padding: EdgeInsets.fromLTRB(i, s, z, d)). Cuidado con el orden. |
| main MIN / CENTER / MAX / SPACE_BETWEEN | MainAxisAlignment.start / center / end / spaceBetween |
| cross MIN / CENTER / MAX / BASELINE | CrossAxisAlignment.start / center / end / baseline (baseline necesita textBaseline) |
| HUG en el eje principal | mainAxisSize: MainAxisSize.min |
| FILL en el eje principal del padre | Expanded (o Flexible); grow N establece flex: N |
| FILL en el eje transversal del padre | CrossAxisAlignment.stretch en el padre, o SizedBox(width: double.infinity) |
| FIXED a×h | SizedBox(width: a, height: h) |
@x,y / absolute | Stack + Positioned(left: x, top: y); usa pin para derecha/abajo |
| bg sólido | BoxDecoration(color: Color(0xAARRGGBB)). Mueve el alfa al frente: #11223380 → 0x80112233. |
| gradiente-lineal desde/hasta | LinearGradient(begin: Alignment(fx*2-1, fy*2-1), end: Alignment(tx*2-1, ty*2-1), colors, stops) usando from/to en spec.json |
| radius | BorderRadius.circular(r) o BorderRadius.only(...) |
| borde W color interior | Border.all(width: W, color: …) (interior es el valor predeterminado de Flutter; centro/exterior necesitan strokeAlign) |
| drop-shadow x,y difuminado B propagación 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)) |
| opacidad | Opacity (o alfa de color para pinturas) |
| text size/lineHeight | TextStyle(fontSize: tamaño, height: alturaLínea / tamaño). Para %: height: pct/100. Establece leadingDistribution: TextLeadingDistribution.even para coincidir con el centrado vertical de Figma. |
| peso 600 | FontWeight.w600 |
| ls px / ls % | letterSpacing: px / letterSpacing: tamaño * pct / 100 |
| ellipsis maxLines N | maxLines: N, overflow: TextOverflow.ellipsis |
| align CENTER | textAlign: TextAlign.center. Para pantallas RTL, usa TextAlign.start y EdgeInsetsDirectional. |
| assets/x.svg | SvgPicture.asset('assets/…/x.svg', width:, height:) (flutter_svg) |
| image fill FILL / FIT | Image.asset(fit: BoxFit.cover / BoxFit.contain) |
Mapeo web (CSS / Tailwind / React)
| Figma | CSS |
|---|---|
| columna / fila | display:flex; flex-direction: column / row; gap: Gpx |
| wrap, rowGap R | flex-wrap: wrap; column-gap: G; row-gap: R |
| pad s,d,i,z | padding: s d i z (mismo orden que 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 (o simplemente auto) |
| FILL (eje principal) | flex: 1 1 0; min-width: 0 |
| FILL (eje transversal) | align-self: stretch |
| FIXED | width: Wpx; height: Hpx; flex-shrink: 0 |
@x,y / absolute | padre position: relative; hijo position:absolute; left:x; top:y (usa pin para derecha/abajo) |
colores #RRGGBBAA | CSS válido tal cual |
gradiente-lineal Ndeg | linear-gradient(Ndeg, c1 p1%, c2 p2%) |
| drop-shadow | box-shadow: x y B S color (inner-shadow añade inset) |
| layer-blur / background-blur R | filter: blur(R/2 px) / backdrop-filter: blur(R/2 px) |
| text | font: peso tamaño/alturaLínea familia; 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> o SVG en línea; mantén el tamaño exportado |
spec.json también tiene el CSS generado por Figma (css) para la mayoría de las capas. Úsalo para verificar los valores CSS; los tokens y la estructura del diseño aún provienen del árbol anterior.
Reglas
- Los valores de la especificación tienen prioridad sobre lo que ves en la imagen. Cuando entren en conflicto, confía en la especificación y menciona el conflicto.
- No redondees ni "mejores" los valores (14 sigue siendo 14, no 16) a menos que el usuario solicite normalizarlos a tokens.
- Las capas ocultas no se exportan. No inventes estados (hover, error, vacío) que no estén en la exportación; pide esos frames.
- Señala las fuentes faltantes. Si la familia de fuentes no está incluida en el proyecto, dilo e indica al usuario qué añadir (fuentes de pubspec / @font-face).
- Para varios puntos de ruptura o estados, pide al usuario que exporte cada frame. Cada uno se convierte en su propia carpeta.