Il target di esportazione predefinito di figmascope è Jetpack Compose. Non è arbitrario — il modello di layout di Compose (Column, Row, Box, Modifier) si mappa strettamente ai tipi di nodi IR (stack, overlay, absolute, leaf). Uno stack verticale in Figma è una Column in Compose. La traduzione è meccanica, il che la rende adatta alla codegen guidata da agenti.

Questa guida copre in dettaglio il mapping IR-to-Compose, mostra un frammento JSON reale con il Composable corrispondente e spiega il layer di mapping dei token.

Perché Compose è il target predefinito

Tre ragioni strutturali:

  1. Auto-layout ↔ Column/Row. I frame auto-layout di Figma (la stragrande maggioranza dei design Figma moderni) vengono esportati come nodi kind: "stack". I nodi stack hanno una proprietà axisvertical si mappa a Column, horizontal si mappa a Row. Questo è un mapping 1:1 senza nessun passaggio di interpretazione.
  2. Token di spaziatura ↔ valori dp. Compose usa Dp per tutte le dimensioni del layout. I valori dei token in tokens.json sono interi senza unità (ad es., spacing.16 = 16) che si mappano direttamente a 16.dp. Nessuna conversione, nessun ridimensionamento.
  3. Token di colore ↔ composable Color. I valori hex di Figma in tokens.json si mappano a Color(0xFFrrggbb) con una singola trasformazione. La chiave del token diventa un nome di variabile semantica nel tuo tema.

I tipi di nodi IR e i loro mapping Compose

Tipo IRProprietàPrimitiva Compose
stackaxis: "vertical"Column
stackaxis: "horizontal"Row
overlayfigli sovrappostiBox
absolutex, y, width, heightBox con Modifier.offset(x.dp, y.dp)
leaftype: "text"Text con TextStyle
leaftype: "rectangle" con riempimentoBox(Modifier.background(Color(...)))

Tutti i tipi di nodi possono portare una proprietà spacing (gap tra i figli) e un oggetto padding (top/right/bottom/left). Entrambi fanno riferimento a chiavi di token.

Mapping dei token in dettaglio

Un file tokens.json appare così:

{
  "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 }
  }
}

Le regole di mapping:

Le chiavi dei token sono intenzionalmente opache (stringhe hex per il colore, stringhe numeriche per la spaziatura) in modo che non pregiudichino il modello verso alcuna convenzione di denominazione particolare. Il tuo tema Compose può creare alias con nomi semantici — colorPrimary, spacingMd — indipendentemente dall'IR.

Un esempio reale: da JSON della schermata home a Composable

Ecco un IR semplificato della schermata home. Uno stack verticale con un leaf header e una lista di card:

{
  "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"
            }
          ]
        }
      ]
    }
  ]
}

Il Composable corrispondente — quello che un agente dovrebbe produrre da questo 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)
                    )
                )
            }
        }
    }
}

Ogni valore nel Composable risale a una chiave token nell'IR. Nulla è hardcoded — 16.dp viene da spacing.16, 24.dp da spacing.24, Color(0xFF7F5CFE) da color.7f5cfe.

Riferimenti alle stringhe — mapping stringResource

Ogni nodo di testo nell'IR porta un stringRef con una chiave in notazione punto. Il file strings.json mappa le chiavi ai valori di visualizzazione e ai fallback:

{
  "home.title": { "value": "Buongiorno", "fallback": "Buongiorno" },
  "home.card.label": { "value": "Riepilogo di oggi", "fallback": "Riepilogo" }
}

La notazione punto si mappa agli ID delle risorse stringa Android con i punti sostituiti da underscore: home.titleR.string.home_title. Il campo fallback è quello che hardcodi come stringa letterale se la risorsa non esiste ancora in strings.xml:

text = stringResource(R.string.home_title, "Buongiorno")

Di' all'agente di usare sempre il campo fallback — non una stringa vuota — in modo che la schermata sia leggibile durante lo sviluppo prima che strings.xml venga popolato.

Posizionamento assoluto

I nodi con kind: "absolute" usano direttamente le coordinate Figma. Compaiono nei design con elementi sovrapposti o ancorati a posizioni specifiche. Il mapping Compose usa Box come genitore e Modifier.offset sui figli:

// 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)
    ) {
        // figli
    }
}

Il posizionamento assoluto è raro nei file Figma ben strutturati (la maggior parte dei file moderni usa auto-layout). Quando lo vedi, verifica se l'intento del design è veramente assoluto o se il designer semplicemente non ha applicato i vincoli auto-layout — l'IR non può inferire l'intento.

Lacune oneste

Il bundle copre bene il caso comune. Alcune cose che non copre:

Queste lacune sono esplicite — compaiono negli avvisi di _meta.json e in CONTEXT.md. L'agente non le attraversa silenziosamente indovinando.

Esporta il bundle di contesto dall'app figmascope principale, poi usalo con Claude Code o Cursor per implementare i Composable direttamente dall'IR. Nessun guessing da screenshot, nessun valore hardcoded.