Cây node Figma phong phú nhưng nhiều tạp âm. Frame, Group, Component, Instance, Text, Rectangle, Ellipse, Vector — mỗi loại có hàng chục trường tùy chọn, một số trong đó mâu thuẫn nhau. Một agent làm việc trực tiếp từ phản hồi Figma API thô sẽ tốn ngân sách nhận thức để hiểu schema thay vì viết code.
figmascope chuẩn hóa cây thành bốn loại node: stack, overlay, absolute và leaf. Mọi node trong mọi file screens/*.json đều là một trong bốn loại này. Không có gì khác.
Bốn loại
stack
Stack là node có Figma Auto Layout — layoutMode: "VERTICAL" hoặc layoutMode: "HORIZONTAL". Các children của nó được định vị theo luồng dọc theo trục. Khoảng cách và padding là rõ ràng.
{
"kind": "stack",
"id": "123:456",
"name": "ContentColumn",
"axis": "vertical",
"gap": 16,
"paddingTop": 24,
"paddingBottom": 24,
"paddingLeft": 20,
"paddingRight": 20,
"primaryAxisAlignItems": "SPACE_BETWEEN",
"counterAxisAlignItems": "CENTER",
"width": 390,
"height": 844,
"fills": [{ "type": "SOLID", "color": "#1a1a2e" }],
"children": [ ... ]
}
Ánh xạ sang Jetpack Compose như Column (trục dọc) hoặc Row (trục ngang). Các trường primaryAxisAlignItems và counterAxisAlignItems ánh xạ lần lượt thành Arrangement và Alignment. gap trở thành Arrangement.spacedBy().
overlay
Overlay là node mà các children của nó có vị trí tuyệt đối bên trong nó. Figma biểu diễn điều này như một Frame với layoutMode: "NONE" nơi các children có tọa độ absoluteBoundingBox. Loại overlay nắm bắt điều này mà không yêu cầu agent lý luận về tọa độ thô để hiểu cấu trúc.
{
"kind": "overlay",
"id": "123:789",
"name": "CardOverlay",
"width": 358,
"height": 200,
"fills": [{ "type": "SOLID", "color": "#ffffff" }],
"children": [
{
"kind": "absolute",
"offset": { "x": 16, "y": 16 },
"child": { ... }
}
]
}
Ánh xạ sang Compose như Box. Các children được định vị bằng Modifier.offset() hoặc Modifier.align() tùy thuộc vào cách vị trí của chúng liên quan đến ranh giới parent.
absolute
Node absolute là một wrapper mang một child duy nhất ở offset (x, y) cụ thể trong overlay parent của nó. Đây là node cấu trúc mỏng — nó tồn tại để giữ nguyên quan hệ không gian từ Figma mà không gộp vị trí với nội dung.
{
"kind": "absolute",
"offset": { "x": 24, "y": 140 },
"child": {
"kind": "leaf",
"name": "BadgeLabel",
"text": "NEW",
"stringRef": "badge.new",
...
}
}
Trong Compose, điều này thường trở thành child của Box với Modifier.offset(x.dp, y.dp). Các giá trị offset là ứng viên cho việc thay thế token nếu spacing token tồn tại trong phạm vi.
leaf
Leaf là node cuối — không có children. Nó có styling (fills, strokes, corner radius) và tùy chọn nội dung văn bản. Các leaf văn bản có trường text (chuỗi ký tự) và trường stringRef (khóa tài nguyên i18n từ strings.json).
{
"kind": "leaf",
"id": "123:101",
"name": "SpeedLabel",
"text": "Speed Test",
"stringRef": "speed.test",
"fontSize": 18,
"fontWeight": 600,
"fills": [{ "type": "SOLID", "color": "#ffffff" }],
"width": 160,
"height": 28
}
Ánh xạ sang Compose như composable Text() cho nội dung văn bản, hoặc như Surface, Box hay Image cho các leaf không phải văn bản tùy thuộc vào loại fill.
Bốn loại ánh xạ 1:1 với mô hình thành phần của bất kỳ framework UI nào. Stack là bố cục luồng. Overlay là container định vị z. Absolute mang metadata không gian. Leaf là nội dung. Chỉ vậy thôi. Taxonomy node Figma có hơn 12 loại node — IR rút gọn xuống còn bốn.
Cách xác định loại
Logic phân loại:
- Nếu
layoutModelà"VERTICAL"hoặc"HORIZONTAL"→stack - Nếu
layoutModelà"NONE"và node có children →overlay(children được bao bởiabsolute) - Nếu node là child trực tiếp của overlay và mang vị trí →
absolute - Nếu node không có children và không phải wrapper absolute →
leaf
Cảnh báo layout-mode-none-inferred trong _meta.json kích hoạt khi Frame với layoutMode: "NONE" có children với bounding box chồng chéo không tầm thường. figmascope xử lý nó như overlay, nhưng ghi chú suy luận vì một số frame layoutMode: "NONE" trong thực tế chỉ là container cho một child duy nhất (không chồng chéo) và có thể được đơn giản hóa. Cảnh báo để agent quyết định cách xử lý.
absoluteBoundingBox — tại sao nó được giữ nguyên
Mỗi node trong IR giữ lại absoluteBoundingBox từ Figma, ngay cả khi parent là stack (nơi vị trí tuyệt đối về mặt lý thuyết không liên quan đến bố cục):
{
"kind": "stack",
"absoluteBoundingBox": { "x": 0, "y": 88, "width": 390, "height": 756 },
...
}
Điều này dành cho các agent cần lý luận về quan hệ không gian giữa các node không chia sẻ quan hệ parent-child trực tiếp. Chồng chéo hai phần tử từ các nhánh khác nhau của cây yêu cầu biết vị trí tuyệt đối của chúng, không chỉ các offset tương đối. Hệ tọa độ là canvas Figma — gốc trên-trái, y tăng xuống dưới.
Nó cũng giúp với việc tham chiếu chéo PNG. _meta.json bao gồm trường pngCount và các PNG được xuất được đặt tên theo slug màn hình. Nếu bạn đang so sánh IR với screenshot, absoluteBoundingBox cho phép bạn định vị các node cụ thể trong ảnh. Để xem điều này trên thiết kế của bạn, xuất bundle từ figmascope.dev.
Fill và stroke trên các loại container
Một điểm nhầm lẫn phổ biến: các node stack và overlay có thể có fill, không chỉ leaf. Một column với màu nền vẫn là stack — nó chỉ cũng có mảng fills. figmascope giữ nguyên điều này:
{
"kind": "stack",
"axis": "vertical",
"fills": [{ "type": "SOLID", "color": "#f6f2ea" }],
"cornerRadius": 12,
...
}
Trong Compose, điều này thường trở thành Column bên trong Surface hoặc Card, với màu fill và corner radius được áp dụng cho container. IR không thu gọn fill vào các children — nó giữ trên node nơi Figma đặt nó.
Gradient fill trên các loại container phát ra cảnh báo background-gradient-not-supported:<name>. Node vẫn hiện diện trong IR với các trường khác nguyên vẹn. Agent nên xử lý fill như TODO và xấp xỉ với màu solid hoặc tạo lệnh vẽ tùy chỉnh, theo ghi chú phạm vi CONTEXT.md.
Quan hệ stringRef + text
Các node leaf văn bản mang cả hai:
text: giá trị chuỗi ký tự từ Figma (ví dụ:"Speed Test")stringRef: khóa tài nguyên từstrings.json(ví dụ:"speed.test")
Nếu nội dung của node văn bản bị loại khỏi strings.json do va chạm hoặc bộ lọc (chỉ số, rỗng, quá ngắn), leaf vẫn có text nhưng stringRef sẽ vắng mặt. Agent nên fallback về literal trong trường hợp đó. Xem strings.json để biết toàn bộ logic xử lý va chạm.
Khi cả hai đều có mặt, ràng buộc CONTEXT.md nói sử dụng stringRef. Trường text ở đó để lý luận agent (để nó biết chuỗi nói gì mà không cần mở strings.json) và là fallback.
Instance component trong IR
Khi một node Figma là INSTANCE của component, node IR mang hai trường bổ sung:
{
"kind": "stack",
"componentId": "789:012",
"componentName": "PrimaryButton",
...
}
Điều này liên kết trở lại với components/inventory.json. Agent biết node này là instance của PrimaryButton thay vì bố cục tùy chỉnh, và nên tham chiếu component hiện có thay vì tạo code trùng lặp. Phạm vi đầy đủ của điều này có trong Component Inventory.
Ví dụ màn hình thực tế
Một ví dụ đơn giản hóa nhưng chính xác về mặt cấu trúc của màn hình với header, content column và nút nổi:
{
"screen": "home",
"root": {
"kind": "overlay",
"name": "HomeScreen",
"width": 390,
"height": 844,
"fills": [{ "type": "SOLID", "color": "#0d0d1a" }],
"children": [
{
"kind": "absolute",
"offset": { "x": 0, "y": 0 },
"child": {
"kind": "stack",
"name": "ContentArea",
"axis": "vertical",
"gap": 24,
"paddingTop": 56,
"paddingLeft": 20,
"paddingRight": 20,
"children": [
{
"kind": "leaf",
"name": "Title",
"text": "Speed Test",
"stringRef": "speed.test",
"fontSize": 28,
"fontWeight": 700
}
]
}
},
{
"kind": "absolute",
"offset": { "x": 111, "y": 752 },
"child": {
"kind": "stack",
"name": "StartButton",
"componentId": "321:654",
"componentName": "PrimaryButton",
"axis": "horizontal",
"gap": 8,
"paddingTop": 16,
"paddingBottom": 16,
"paddingLeft": 32,
"paddingRight": 32,
"cornerRadius": 24,
"fills": [{ "type": "SOLID", "color": "#7f5cfe" }]
}
}
]
}
}
Từ đây agent có thể tạo chính xác màn hình Compose Box với Column định vị tuyệt đối cho nội dung và component PrimaryButton định vị tuyệt đối ở dưới cùng. Mọi quyết định bố cục đều có thể suy ra từ IR mà không cần đoán.
Để biết cách token áp dụng cho các giá trị khoảng cách và màu sắc trong cấu trúc này, xem tokens.json Giải Thích. Để biết quy trình agent đầy đủ sử dụng IR này với Cursor hoặc Claude Code, xem Jetpack Compose từ Figma. Hãy thử bằng cách dán URL Figma của bạn vào figmascope.