DEV Community

loading...

How to develop Figma plugin with React + TS

seya profile image kazuya ・2 min read

TL;DR

git clone https://github.com/kazuyaseki/react-figma-plugin-boilerplate.git <your project name>
yarn or npm install
yarn webpack:watch  or  npm run webpack:watch 

Developing Figma plugin is exciting, but when it comes to develop one with some stateful UI, it's a pain to develop in imperative way.

Thus I created a boilerplate to develop with React and here I introduce it.

The content of boilerplate is as follows.
https://github.com/kazuyaseki/react-figma-plugin-boilerplate

How to render Figma plugin UI with React

There is nothing special, you just have to do ReactDOM.render to ui.html which is specified from manifest.json.

<div id="app"></div>
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './App';

ReactDOM.render(<App />, document.getElementById('app'));

Sample Code

I paste a sample code of plugin made with React below.

My plugin is to enable incremental search for components and create an instance from it.(actually Figma itself has this feature but I thought it would be nice when keyboard shortcut functionality is added to plugin)

It works as the following gif(sorry my tweet is in Japanese)

Before you start building your plugin, I recommend you to read How Plugins Run document.

You have to beware that main thread which can reference Figma object is different from plugin UI thread.

So you need to use message object to pass data between the threads, my sample code has codes for both directions, so please refer to it.

import { subscribeOnMessages } from 'react-figma';

figma.showUI(__html__);

const componentNodes = figma.root.findAll((node) => node.type === 'COMPONENT');
const conmponentsData = componentNodes.map((node) => ({
  id: node.id,
  name: node.name,
}));
figma.ui.postMessage(conmponentsData);

figma.ui.onmessage = (message) => {
  subscribeOnMessages(message);

  if (message.type === 'create-instance') {
    const component = figma.root.findOne(
      (node) => node.id === message.id
    ) as ComponentNode;
    component.createInstance();
  }
};
import * as React from 'react';

type ComponentItemType = {
  id: string;
  name: string;
};

export const App = () => {
  const [query, setQuery] = React.useState('');
  const [components, setComponents] = React.useState<ComponentItemType[]>([]);

  React.useEffect(() => {
    onmessage = (event) => {
      setComponents(event.data.pluginMessage as ComponentItemType[]);
    };
  }, []);

  const create = (id: string) => {
    parent.postMessage({ pluginMessage: { type: 'create-instance', id } }, '*');
  };

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <div style={{ display: 'flex', flexDirection: 'column' }}>
        {components
          .filter((component) => {
            if (query.length === 0) {
              return true;
            }
            return component.name.includes(query);
          })
          .map((component) => (
            <button onClick={() => create(component.id)}>
              {component.name}
            </button>
          ))}
      </div>
    </div>
  );
};

Discussion (0)

pic
Editor guide