O alvo de export padrão do figmascope é Jetpack Compose. Isso não é arbitrário — o modelo de layout do Compose (Column, Row, Box, Modifier) mapeia de perto para os tipos de nó do IR (stack, overlay, absolute, leaf). Um stack vertical no Figma é um Column no Compose. A tradução é mecânica, o que a torna adequada ao codegen conduzido por agentes.
Este passo a passo cobre o mapeamento IR-para-Compose em detalhe, mostra um fragmento JSON real com o Composable correspondente e explica a camada de mapeamento de tokens.
Por que Compose é o alvo padrão
Três razões estruturais:
- Auto-layout ↔ Column/Row. Os frames de auto-layout do Figma (a grande maioria dos designs modernos do Figma) exportam como nós
kind: "stack". Nós stack têm uma propriedadeaxis—verticalmapeia paraColumn,horizontalmapeia paraRow. Este é um mapeamento 1:1 sem etapa de interpretação. - Tokens de espaçamento ↔ valores dp. O Compose usa
Dppara todas as dimensões de layout. Valores de token emtokens.jsonsão inteiros sem unidade (ex.:spacing.16 = 16) que mapeiam diretamente para16.dp. Sem conversão, sem escala. - Tokens de cor ↔ Color composable. Valores hex do Figma em
tokens.jsonmapeiam paraColor(0xFFrrggbb)com uma única transformação. A chave do token se torna um nome de variável semântico no seu tema.
Os tipos de nó do IR e seus mapeamentos em Compose
| Tipo IR | Propriedades | Primitivo Compose |
|---|---|---|
stack | axis: "vertical" | Column |
stack | axis: "horizontal" | Row |
overlay | filhos em camadas | Box |
absolute | x, y, width, height | Box com Modifier.offset(x.dp, y.dp) |
leaf | type: "text" | Text com TextStyle |
leaf | type: "rectangle" com fill | Box(Modifier.background(Color(...))) |
Todos os tipos de nó podem carregar uma propriedade spacing (gap entre filhos) e um objeto padding (top/right/bottom/left). Ambos referenciam chaves de token.
Mapeamento de tokens em detalhe
Um arquivo tokens.json parece assim:
{
"spacing": {
"4": 4, "8": 8, "12": 12, "16": 16,
"20": 20, "24": 24, "32": 32, "48": 48
},
"radius": {
"4": 4, "8": 8, "12": 12, "16": 16, "full": 9999
},
"color": {
"7f5cfe": "#7F5CFE",
"1a1a2e": "#1A1A2E",
"f6f2ea": "#F6F2EA",
"ffffff": "#FFFFFF",
"e53935": "#E53935"
},
"typography": {
"heading.24": { "size": 24, "weight": 700, "lineHeight": 1.2 },
"body.14": { "size": 14, "weight": 400, "lineHeight": 1.5 },
"label.12": { "size": 12, "weight": 500, "lineHeight": 1.4 }
}
}
As regras de mapeamento:
spacing.16→16.dpradius.12→RoundedCornerShape(12.dp)color.7f5cfe→Color(0xFF7F5CFE)(prefixe0xFFpara alpha completo)typography.heading.24→TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold, lineHeight = 28.8.sp)
As chaves de token são intencionalmente opacas (strings hex para cor, strings numéricas para espaçamento) para não enviesar o modelo em direção a nenhuma convenção de nomenclatura específica. Seu tema Compose pode criar aliases com nomes semânticos — colorPrimary, spacingMd — independentemente do IR.
Um exemplo real: JSON da tela home para Composable
Aqui está um IR simplificado mas estruturalmente correto de uma tela home. Um stack vertical com um leaf de cabeçalho e uma lista de cards:
{
"name": "home",
"kind": "stack",
"axis": "vertical",
"spacing": "spacing.24",
"padding": { "top": "spacing.16", "right": "spacing.16",
"bottom": "spacing.16", "left": "spacing.16" },
"fill": "color.f6f2ea",
"children": [
{
"kind": "leaf",
"type": "text",
"stringRef": "home.title",
"typography": "typography.heading.24",
"fill": "color.1a1a2e"
},
{
"kind": "stack",
"axis": "vertical",
"spacing": "spacing.12",
"children": [
{
"kind": "overlay",
"radius": "radius.12",
"fill": "color.ffffff",
"padding": { "top": "spacing.16", "right": "spacing.16",
"bottom": "spacing.16", "left": "spacing.16" },
"children": [
{
"kind": "leaf",
"type": "text",
"stringRef": "home.card.label",
"typography": "typography.label.12",
"fill": "color.7f5cfe"
}
]
}
]
}
]
}
O Composable correspondente — o que um agente deve produzir a partir deste IR:
@Composable
fun HomeScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF6F2EA))
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Text(
text = stringResource(R.string.home_title),
style = TextStyle(
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
lineHeight = 28.8.sp,
color = Color(0xFF1A1A2E)
)
)
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFFFFFFF))
.padding(16.dp)
) {
Text(
text = stringResource(R.string.home_card_label),
style = TextStyle(
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
lineHeight = 16.8.sp,
color = Color(0xFF7F5CFE)
)
)
}
}
}
}
Cada valor no Composable é rastreável a uma chave de token no IR. Nada é hardcoded — 16.dp vem de spacing.16, 24.dp de spacing.24, Color(0xFF7F5CFE) de color.7f5cfe.
String refs — mapeamento para stringResource
Todo nó de texto no IR carrega um stringRef com uma chave em notação de ponto. O arquivo strings.json mapeia chaves para valores de exibição e fallbacks:
{
"home.title": { "value": "Bom dia", "fallback": "Bom dia" },
"home.card.label": { "value": "Resumo de hoje", "fallback": "Resumo" }
}
A notação de ponto mapeia para IDs de recursos de string do Android com pontos substituídos por underscores: home.title → R.string.home_title. O campo fallback é o que você hardcodifica como string literal se o recurso ainda não existir em strings.xml:
text = stringResource(R.string.home_title, "Bom dia")
Diga ao agente para sempre usar o campo fallback — não uma string vazia — para que a tela seja legível durante o desenvolvimento antes que strings.xml seja populado.
Posicionamento absoluto
Nós com kind: "absolute" usam coordenadas do Figma diretamente. Aparecem em designs com elementos sobrepostos ou ancorados em posições específicas. O mapeamento em Compose usa Box como pai e Modifier.offset nos filhos:
// IR: { "kind": "absolute", "x": 24, "y": 80, "width": 120, "height": 40 }
Box(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.offset(x = 24.dp, y = 80.dp)
.size(width = 120.dp, height = 40.dp)
) {
// filhos
}
}
Posicionamento absoluto é incomum em arquivos Figma bem estruturados (a maioria dos arquivos modernos usa auto-layout). Quando você o vê, verifique se a intenção do design é realmente absoluta ou se o designer simplesmente não aplicou constraints de auto-layout — o IR não consegue inferir a intenção.
Lacunas honestas
O bundle cobre o caso comum bem. Algumas coisas que ele não cobre:
- Fills com gradiente. O IR exporta um aviso quando encontra um gradiente. O fill de background do nó é omitido. Deixe um
// TODO: gradientee implemente manualmente. - Efeitos complexos. Sombras drop e blurs não são representados no IR. Eles aparecem nos avisos de
_meta.jsonse presentes. - Ícones vetoriais. O IR armazena um ID de referência para nós de ícone, não dados de path. Você precisará resolver o ícone para um drawable ou ícone Compose real separadamente.
- Componentes aninhados. O IR inclui
componentIdem nós de instância.components/inventory.jsonmapeia IDs para nomes. Implemente o componente separadamente e referencie-o pelo nome no Composable pai.
Essas lacunas são explícitas — aparecem nos avisos de _meta.json e no CONTEXT.md. O agente não adivinhou silenciosamente através delas.
Exporte o bundle de contexto no app principal do figmascope, depois use-o com Claude Code ou Cursor para implementar Composables diretamente do IR. Sem adivinhação por screenshot, sem valores hardcoded.