figmascope's default export target is Jetpack Compose. That's not arbitrary — Compose's layout model (Column, Row, Box, Modifier) maps closely to the IR node kinds (stack, overlay, absolute, leaf). A vertical stack in Figma is a Column in Compose. The translation is mechanical, which makes it well-suited to agent-driven codegen.

This walkthrough covers the IR-to-Compose mapping in detail, shows a real JSON fragment with the corresponding Composable, and explains the token mapping layer.

Why Compose is the default target

Three structural reasons:

  1. Auto-layout ↔ Column/Row. Figma's auto-layout frames (the vast majority of modern Figma designs) export as kind: "stack" nodes. Stack nodes have an axis property — vertical maps to Column, horizontal maps to Row. This is a 1:1 mapping with no interpretation step.
  2. Spacing tokens ↔ dp values. Compose uses Dp for all layout dimensions. Token values in tokens.json are unitless integers (e.g., spacing.16 = 16) that map directly to 16.dp. No conversion, no scaling.
  3. Color tokens ↔ Color composable. Figma hex values in tokens.json map to Color(0xFFrrggbb) with a single transformation. The token key becomes a semantic variable name in your theme.

The IR node kinds and their Compose mappings

IR kindPropertiesCompose primitive
stackaxis: "vertical"Column
stackaxis: "horizontal"Row
overlaylayered childrenBox
absolutex, y, width, heightBox with Modifier.offset(x.dp, y.dp)
leaftype: "text"Text with TextStyle
leaftype: "rectangle" with fillBox(Modifier.background(Color(...)))

All node kinds can carry a spacing property (gap between children) and a padding object (top/right/bottom/left). Both reference token keys.

Token mapping in detail

A tokens.json file looks like this:

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

The mapping rules:

The token keys are intentionally opaque (hex strings for color, numeric strings for spacing) so they don't bias the model toward any particular naming convention. Your Compose theme can alias them to semantic names — colorPrimary, spacingMd — independently of the IR.

A real example: home screen JSON to Composable

Here's a simplified home screen IR. A vertical stack with a header leaf and a card list:

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

The corresponding Composable — what an agent should produce from this 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)
                    )
                )
            }
        }
    }
}

Every value in the Composable traces back to a token key in the IR. Nothing is hardcoded — 16.dp comes from spacing.16, 24.dp from spacing.24, Color(0xFF7F5CFE) from color.7f5cfe.

String refs — stringResource mapping

Every text node in the IR carries a stringRef with a dot-notation key. The strings.json file maps keys to display values and fallbacks:

{
  "home.title": { "value": "Good morning", "fallback": "Good morning" },
  "home.card.label": { "value": "Today's summary", "fallback": "Summary" }
}

The dot-notation maps to Android string resource IDs with dots replaced by underscores: home.titleR.string.home_title. The fallback field is what you hardcode as the literal string if the resource doesn't exist yet in strings.xml:

text = stringResource(R.string.home_title, "Good morning")

Tell the agent to always use the fallback field — not an empty string — so the screen is readable during development before strings.xml is populated.

Absolute positioning

Nodes with kind: "absolute" use Figma coordinates directly. These appear in designs with overlapping elements or elements anchored to specific positions. The Compose mapping uses Box as the parent and Modifier.offset on children:

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

Absolute positioning is uncommon in well-structured Figma files (most modern files use auto-layout). When you see it, check whether the design intent is truly absolute or whether the designer just didn't apply auto-layout constraints — the IR can't infer intent.

Honest gaps

The bundle covers the common case well. A few things it doesn't:

These gaps are explicit — they show up in _meta.json warnings and CONTEXT.md. The agent doesn't guess through them silently.

Export the context bundle from the main figmascope app, then use it with Claude Code or Cursor to implement Composables directly from the IR. No screenshot guessing, no hardcoded values.