{"id":15497988,"url":"https://github.com/rodydavis/figma_flutter_plugin","last_synced_at":"2025-07-12T12:43:26.214Z","repository":{"id":164583495,"uuid":"640046887","full_name":"rodydavis/figma_flutter_plugin","owner":"rodydavis","description":"Example of how to use Flutter Web in a Figma Pluigin","archived":false,"fork":false,"pushed_at":"2023-06-01T23:22:59.000Z","size":10428,"stargazers_count":11,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-18T17:30:34.110Z","etag":null,"topics":["figjam","figjam-plugin","figma","figma-plugin","flutter"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rodydavis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-12T21:09:32.000Z","updated_at":"2024-10-18T15:38:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"7a1d6caf-8ec7-4162-91cf-c0d55683f2d4","html_url":"https://github.com/rodydavis/figma_flutter_plugin","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodydavis%2Ffigma_flutter_plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodydavis%2Ffigma_flutter_plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodydavis%2Ffigma_flutter_plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rodydavis%2Ffigma_flutter_plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rodydavis","download_url":"https://codeload.github.com/rodydavis/figma_flutter_plugin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250337847,"owners_count":21414099,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["figjam","figjam-plugin","figma","figma-plugin","flutter"],"created_at":"2024-10-02T08:41:35.909Z","updated_at":"2025-04-22T22:44:27.104Z","avatar_url":"https://github.com/rodydavis.png","language":"Dart","funding_links":[],"categories":["Dart"],"sub_categories":[],"readme":"# figma_flutter_plugin\n\nThis is an example of how to build a Figma plugin using Flutter web and inline all the assets in the single html file as required by the Figma plugin API.\n\n## Features\n\n- Tree shaking for Material Icons\n- Inline assets / fonts\n- Offline support in Figma (Not using remote views)\n- Two way communication between Flutter and Figma\n- Figma and FigJam support\n- Build scripts for generating figma project `/build/figma`\n- Typings for Figma API\n- Light and Dark mode support\n\n## Prerequisites\n\n### Plugin ID\n\nYou will need to get a new Figma plugin ID by opening Figma Desktop App and creating a new plugin. Then copy the ID to the `figma/manifest.json` and also update the name key.\n\n### Typings\n\nMake sure to check out the repo with submodules if you want the Figma typings.\n\n```bash\ngit submodule update --init --recursive\n```\n\n### Flutter\n\nYou need to update the name and description in `pubspec.yaml`.\n\n### Figma\n\nTo use the plugin you need to import the manifest from the `build/figma` folder, not the top level `figma` folder.\n\nRun the build script:\n\n```bash\ndart scripts/build.dart\n```\n\nThen open figma and import the manifest.json from the `build/figma` folder.\n\n## Screenshots\n\n### Figma\n\n![](/screenshots/figma-preview.png)\n\n### FigJam\n\n![](/screenshots/figjam-preview.png)\n\n## Example\n\n### UI\n\n```dart\nimport 'package:flutter/material.dart';\n\nimport 'figma.dart';\n\nclass Example extends StatefulWidget {\n  const Example({\n    super.key,\n    required this.title,\n    required this.seedColor,\n    required this.isFigma,\n  });\n\n  final String title;\n  final Color seedColor;\n  final bool isFigma;\n\n  @override\n  State\u003cExample\u003e createState() =\u003e _ExampleState();\n}\n\nclass _ExampleState extends State\u003cExample\u003e {\n  final controller = TextEditingController(text: '5');\n  final formKey = GlobalKey\u003cFormState\u003e();\n  final api = FigmaApi();\n\n  late int red = widget.seedColor.red;\n  late int green = widget.seedColor.green;\n  late int blue = widget.seedColor.blue;\n\n  @override\n  void initState() {\n    if (widget.isFigma) {\n      api.init().then((value) =\u003e setState(() {}));\n    } else {\n      api.initialized = true;\n    }\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    final colors = theme.colorScheme;\n    final color = Color.fromARGB(255, red, green, blue);\n    final onColor = color.onColor();\n    return Theme(\n      data: theme.copyWith(\n        colorScheme: ColorScheme.fromSeed(\n          seedColor: color,\n          brightness: colors.brightness,\n        ),\n      ),\n      child: Scaffold(\n        appBar: AppBar(\n          title: Text(widget.title),\n          actions: [\n            Builder(builder: (context) {\n              return IconButton(\n                icon: const Icon(Icons.settings_outlined),\n                onPressed: () async {\n                  showBottomSheet(\n                    context: context,\n                    builder: (context) {\n                      return Material(\n                        child: Column(\n                          children: [\n                            ListTile(\n                              leading: Icon(Icons.notifications),\n                              title: Text('Show notification'),\n                              onTap: () =\u003e api.notify('Hello from Flutter!'),\n                            ),\n                            ListTile(\n                              leading: Icon(Icons.close),\n                              title: Text('Close Plugin'),\n                              textColor: colors.error,\n                              iconColor: colors.error,\n                              onTap: () =\u003e api.closePlugin(),\n                            ),\n                          ],\n                        ),\n                      );\n                    },\n                  );\n                },\n              );\n            }),\n          ],\n        ),\n        body: !api.initialized\n            ? const CircularProgressIndicator()\n            : Form(\n                key: formKey,\n                autovalidateMode: AutovalidateMode.onUserInteraction,\n                child: ListView(\n                  padding: const EdgeInsets.all(16),\n                  children: [\n                    ListTile(\n                      leading: const Icon(Icons.color_lens, color: Colors.red),\n                      title: Text('Red'),\n                      subtitle: Slider(\n                        value: red.toDouble(),\n                        min: 0,\n                        max: 255,\n                        divisions: 255,\n                        onChanged: (value) =\u003e\n                            setState(() =\u003e red = value.toInt()),\n                      ),\n                    ),\n                    const SizedBox(height: 16),\n                    ListTile(\n                      leading:\n                          const Icon(Icons.color_lens, color: Colors.green),\n                      title: Text('Green'),\n                      subtitle: Slider(\n                        value: green.toDouble(),\n                        min: 0,\n                        max: 255,\n                        divisions: 255,\n                        onChanged: (value) =\u003e\n                            setState(() =\u003e green = value.toInt()),\n                      ),\n                    ),\n                    const SizedBox(height: 16),\n                    ListTile(\n                      leading: const Icon(Icons.color_lens, color: Colors.blue),\n                      title: Text('Blue'),\n                      subtitle: Slider(\n                        value: blue.toDouble(),\n                        min: 0,\n                        max: 255,\n                        divisions: 255,\n                        onChanged: (value) =\u003e\n                            setState(() =\u003e blue = value.toInt()),\n                      ),\n                    ),\n                    const SizedBox(height: 32),\n                    TextFormField(\n                      controller: controller,\n                      decoration: const InputDecoration(\n                        border: OutlineInputBorder(),\n                        labelText: 'Number of rectangles',\n                      ),\n                      validator: (value) {\n                        if (value == null || value.isEmpty) {\n                          return 'Please enter a number';\n                        }\n                        if (int.tryParse(value) == null) {\n                          return 'Please enter a valid number';\n                        }\n                        return null;\n                      },\n                    ),\n                    const SizedBox(height: 16),\n                    ElevatedButton.icon(\n                      style: ElevatedButton.styleFrom(\n                        backgroundColor: color,\n                        foregroundColor: onColor,\n                      ),\n                      onPressed: () {\n                        if (!formKey.currentState!.validate()) {\n                          return;\n                        }\n                        formKey.currentState!.save();\n                        api.createShapes(int.parse(controller.text), color);\n                      },\n                      label: const Text('Create rectangles'),\n                      icon: const Icon(Icons.add, size: 18),\n                    ),\n                  ],\n                ),\n              ),\n      ),\n    );\n  }\n}\n\nextension on FigmaApi {\n  Future\u003cvoid\u003e createShapes(int count, Color color) async {\n    final ids = \u003cString\u003e[];\n    for (var i = 0; i \u003c count; i++) {\n      if (type == FigmaEditorType.figma) {\n        final res = await execMethod(\n          'createRectangle',\n          attributes: {\n            'x': i * 150,\n            'fills': [\n              {\n                'type': 'SOLID',\n                'color': color.toFigma(),\n              },\n            ],\n          },\n          keys: ['id', 'name'],\n        );\n        final result = res['result'] as Map;\n        final id = result['id']?.toString();\n        if (id != null) ids.add(id);\n      } else {\n        final res = await execMethod(\n          'createShapeWithText',\n          attributes: {\n            'shapeType': FigJamShapeType.ROUNDED_RECTANGLE.name,\n            'fills': [\n              {\n                'type': 'SOLID',\n                'color': color.toFigma(),\n              },\n            ],\n          },\n          keys: ['id', 'name', 'width'],\n        );\n        print(res);\n        final result = res['result'] as Map;\n        final id = result['id']?.toString();\n        if (id != null) {\n          ids.add(id);\n          final width = num.parse(result['width'].toString());\n          await nodeOptions(id, attributes: {\n            'x': i * (width + 200),\n          });\n        }\n      }\n    }\n\n    if (type == FigmaEditorType.figJam) {\n      for (var i = 0; i \u003c ids.length - 1; i++) {\n        await execMethod(\n          'createConnector',\n          attributes: {\n            'strokeWeight': 8,\n            'connectorStart': {\n              'endpointNodeId': ids[i],\n              'magnet': 'AUTO',\n            },\n            'connectorEnd': {\n              'endpointNodeId': ids[i + 1],\n              'magnet': 'AUTO',\n            },\n          },\n        );\n      }\n    }\n\n    await appendToCurrentPage(ids);\n    await setSelection(ids);\n    await scrollAndZoomIntoView(ids);\n  }\n}\n\n```\n\n### Figma\n\n```dart\nimport 'dart:async';\nimport 'dart:html' as html;\n\nimport 'package:flutter/material.dart';\n\nclass FigmaApi {\n  FigmaEditorType type = FigmaEditorType.figma;\n  String command = '';\n  String pluginId = '';\n  bool initialized = false;\n\n  Future\u003cvoid\u003e init() async {\n    final info = await _result('init');\n    final result = info['result'] as Map;\n    final editorType = (result['editorType'] ?? 'figma').toString();\n    if (editorType == 'figma') {\n      type = FigmaEditorType.figma;\n    } else {\n      type = FigmaEditorType.figJam;\n    }\n    command = result['command']?.toString() ?? '';\n    pluginId = result['id']?.toString() ?? '';\n    initialized = true;\n    print('Editor: $type');\n    print('Command: \"$command\"');\n    print('Plugin ID: $pluginId');\n  }\n\n  Future\u003cFigmaJson\u003e execMethod(\n    String name, {\n    List\u003cString\u003e args = const [],\n    FigmaJson attributes = const {},\n    List\u003cString\u003e? keys,\n  }) async {\n    return _result('function', {\n      'name': name,\n      'attrs': attributes,\n      'args': args,\n      if (keys != null) 'keys': keys,\n    });\n  }\n\n  Future\u003cFigmaJson\u003e notify(\n    String message, {\n    int? timeout,\n    bool? error,\n  }) async {\n    return execMethod(\n      'notify',\n      args: [message],\n      attributes: {\n        if (timeout != null) 'timeout': timeout,\n        if (error != null) 'error': error,\n      },\n    );\n  }\n\n  Future\u003cFigmaJson\u003e execCallback(\n    String name, {\n    FigmaJson attributes = const {},\n  }) async {\n    return _result(name, attributes);\n  }\n\n  Future\u003cFigmaJson\u003e nodeOptions(\n    String nodeId, {\n    FigmaJson attributes = const {},\n    List\u003cString\u003e? keys,\n  }) async {\n    return _result('node-options', {\n      'node_id': nodeId,\n      'attrs': attributes,\n      if (keys != null) 'keys': keys,\n    });\n  }\n\n  Future\u003cvoid\u003e appendToCurrentPage(List\u003cString\u003e ids) async {\n    await execCallback('append-to-current-page', attributes: {'ids': ids});\n  }\n\n  Future\u003cvoid\u003e setSelection(List\u003cString\u003e ids) async {\n    await execCallback('set-selection', attributes: {'ids': ids});\n  }\n\n  Future\u003cvoid\u003e scrollAndZoomIntoView(List\u003cString\u003e ids) async {\n    await execCallback('scroll-and-zoom-into-view', attributes: {'ids': ids});\n  }\n\n  Future\u003cvoid\u003e closePlugin([String? message]) async {\n    await execMethod('closePlugin', args: [\n      if (message != null) message,\n    ]);\n  }\n\n  void _send(\n    String type, [\n    FigmaJson data = const {},\n  ]) {\n    final parent = html.window.parent!;\n    final message = {\n      'pluginMessage': {'msg_type': type, ...data}\n    };\n    parent.postMessage(message, '*');\n  }\n\n  void _receive(ValueChanged\u003cFigmaJson\u003e callback) {\n    final parent = html.document.getElementById('output')!;\n    parent.addEventListener('figma', (event) {\n      final customEvent = event as html.CustomEvent;\n      final detail = customEvent.detail;\n      final result = detail;\n      callback(result);\n    });\n  }\n\n  Future\u003cFigmaJson\u003e _result(\n    String type, [\n    FigmaJson data = const {},\n  ]) async {\n    final completer = Completer\u003cFigmaJson\u003e();\n    final id = DateTime.now().millisecondsSinceEpoch.toString();\n    _receive((event) {\n      if (event['id'] == id) {\n        completer.complete(event);\n      }\n    });\n    _send(type, {'id': id, ...data});\n    return completer.future;\n  }\n}\n\nextension FigmaColorUtils on Color {\n  /// RGB values between 0 and 1\n  Map\u003cString, double\u003e toFigma() {\n    return {\n      'r': red / 255,\n      'g': green / 255,\n      'b': blue / 255,\n    };\n  }\n\n  String toHex() {\n    final r = red.toRadixString(16).padLeft(2, '0');\n    final g = green.toRadixString(16).padLeft(2, '0');\n    final b = blue.toRadixString(16).padLeft(2, '0');\n    return '#$r$g$b';\n  }\n\n  Color onColor() {\n    return computeLuminance() \u003e 0.5 ? Colors.black : Colors.white;\n  }\n}\n\ntypedef FigmaJson = Map\u003cString, Object?\u003e;\n\nenum FigmaEditorType {\n  figma,\n  figJam,\n}\n\nenum FigJamShapeType {\n  SQUARE,\n  ELLIPSE,\n  ROUNDED_RECTANGLE,\n  DIAMOND,\n  TRIANGLE_UP,\n  TRIANGLE_DOWN,\n  PARALLELOGRAM_RIGHT,\n  PARALLELOGRAM_LEFT,\n}\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodydavis%2Ffigma_flutter_plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frodydavis%2Ffigma_flutter_plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frodydavis%2Ffigma_flutter_plugin/lists"}