Development

Tailwind Export - A Figma plugin that can export element styles to Tailwind

It only took me 3 days to develop a Figma plug-in with the help of GPT4, and I didn't know any Figma plug-in development knowledge before.

2023年3月27日
Tailwind Export - A Figma plugin that can export element styles to Tailwind
本文共有1776字,预计阅读时间8分钟
I always wanted to develop a Figma plug-in to practice, but I was afraid of the development documentation and didn't know what to do, so I didn't do it.
It may be because I have been using Tailwind CSS recently, and I came up with an idea in the first two weeks:
Frame in Figma already supports many settings similar to CSS layout. Can these settings be converted into Tailwind output?
Searched on Figma, there are similar plugins, but there is no plugin specifically for exporting Tailwind. This is a gap that can be done.
Looking through the development documents of Figma, the styles of the elements in the design documents are easy to get. Just set up the most basic framework, get any style, convert it into Tailwind and put it on the plug-in interface. The rest of the work is for support Just adjust the output one by one.
The question now is how to put this basic framework together.

Introducing GPT-4

First of all, I tested the effect of ChatGPT and found that the answers given were not reliable.
Will GPT-4 be more reliable?
But I can't use GPT-4, because I mainly use paid APIs, and I don't want to buy another plus.
Fortunately, with Poe, there is an opportunity to use GPT-4 once a day. That means I have to craft my prompts carefully so I don't waste a once-a-day opportunity.
Then I asked the first question for GPT-4:
I want to write a Figma plugin, the function is to convert the CSS style generated by the selected element into the tailwind class name and display it on the plugin panel. The plug-in panel has no elements by default, only a prompt "Please select an element". After selecting an element, the tailwind class name corresponding to its style will be displayed on the plug-in panel. There will be a copy button below. If you click on another element, continue to generate a new tailwind class name. This plug-in only needs to consider width, height and background color for the time being, and other styles can be ignored. You need to tell me how to implement this function in code.ts, and give the complete code without typescript errors.

AI's Answer

Here is its answer:
First, you need to have a basic Figma plugin project structure. You can refer to the Figma plugin development documentation to learn how to create a new plugin.
Here's a simple example of how to implement the functionality you describe in code.ts. Note that this example only works for handling width, height and background-color and converting them to Tailwind CSS classnames. You may need to adjust it to your needs.
1// code.ts
2const figmaUIApi = `
3  <html>
4  <style>
5    body {
6      font-family: -apple-system, sans-serif;
7      margin: 0;
8      padding: 24px;
9    }
10
11    #info {
12      margin-bottom: 16px;
13    }
14
15    button {
16      background-color: #1669ef;
17      border: none;
18      border-radius: 4px;
19      color: white;
20      cursor: pointer;
21      font-size: 14px;
22      padding: 8px 16px;
23    }
24
25    button:hover {
26      background-color: #0d4edb;
27    }
28  </style>
29  <body>
30    <div id="info">请选择元素</div>
31    <button id="copy">复制</button>
32    <script>
33      const info = document.querySelector('#info');
34      const copyButton = document.querySelector('#copy');
35
36      copyButton.addEventListener('click', () => {
37        const tailwindClasses = info.textContent;
38        if (tailwindClasses) {
39          navigator.clipboard.writeText(tailwindClasses);
40        }
41      });
42
43      onmessage = (event) => {
44        const tailwindClasses = event.data.pluginMessage;
45        if (tailwindClasses) {
46          info.textContent = tailwindClasses;
47        } else {
48          info.textContent = '请选择元素';
49        }
50      };
51    </script>
52  </body>
53  </html>
54`;
55
56figma.showUI(figmaUIApi, { width: 300, height: 150 });
57
58function convertToTailwind(width: number, height: number, color: string): string {
59  // 宽度和高度转换
60  const widthClass = `w-${Math.round(width / 4)}`;
61  const heightClass = `h-${Math.round(height / 4)}`;
62
63  // 颜色转换
64  const colorClass = `bg-${color.replace('#', '')}`;
65
66  return `${widthClass} ${heightClass} ${colorClass}`;
67}
68
69figma.on("selectionchange", () => {
70  const node = figma.currentPage.selection[0];
71
72  if (node && node.type === "RECTANGLE") {
73    const width = node.width;
74    const height = node.height;
75    const color = node.fills[0].color;
76    const hexColor = `#${Math.round(color.r * 255).toString(16)}${Math.round(color.g * 255).toString(16)}${Math.round(color.b * 255).toString(16)}`;
77
78    const tailwindClasses = convertToTailwind(width, height, hexColor);
79    figma.ui.postMessage(tailwindClasses);
80  } else {
81    figma.ui.postMessage(null);
82  }
83});
Note that the figmaUIApi code in this example is a simple HTML and JavaScript code used to create the plugin's UI. It receives messages sent from code.ts and updates the displayed Tailwind class name when a message is received.
The convertToTailwind function in this example converts the width, height, and color to the corresponding Tailwind class names. You can extend this function to support more CSS properties as needed.
Note that this example only works with rectangle nodes. If you need to support other types of nodes, you can adjust the code as needed.
Continue
This code can actually run, but there will be some bugs. In fact, you should regard AI as Copilot, and use it to provide you with inspiration, it will be a good tool.
Then let's analyze this code.

Interface

The big thing following the const figmaUIApi is the interface of the plug-in, and the ones in <style> are temporarily ignored.
The most important part of the interface-related code is copyButton.addEventListener and onmessage.
copyButton.addEventListener adds a listening function to the copy button, saves the content in info to tailwindClasses, and writes it to the clipboard if it exists;
The function of onmessage is to judge whether there is a selection element, and if there is no selection, a prompt copy will be displayed.

Plugin Logic

figma.showUI is a method of Figma used to call out the interface of the plugin.
The function convertToTailwind is the simplest conversion function generated by AI for us, such as the following:
1const widthClass = `w-${Math.round(width / 4)}`;
Its effect is to obtain the width of the element, divide it by 4, round it up, and add a prefix in front.
If the width is 24, w-6 will be output here.
Of course, the width of Tailwind cannot be rounded so simply, but we have the framework, and it is only a matter of time to refine it, and there is no problem.
The same is true for the remaining color functions, which cannot be used directly, but you can know the logic.
The role of figma.on is to register a callback function for the specified event, and execute the function when the event occurs.
AI generated for us a processing for rectangular elements when the selection element changes:
1figma.on("selectionchange", () => {
2  const node = figma.currentPage.selection[0];
3
4  if (node && node.type === "RECTANGLE") {
5    const width = node.width;
6    const height = node.height;
7    const color = node.fills[0].color;
8    const hexColor = `#${Math.round(color.r * 255).toString(16)}${Math.round(color.g * 255).toString(16)}${Math.round(color.b * 255).toString(16)}`;
9
10    const tailwindClasses = convertToTailwind(width, height, hexColor);
11    figma.ui.postMessage(tailwindClasses);
12  } else {
13    figma.ui.postMessage(null);
14  }
15});
When a Figma element is selected, this function is triggered. If the node type is a rectangle, the attribute is extracted, passed to the convertToTailwind() processing function, and finally returns a string of Tailwind class name strings, which are then displayed on the interface.
In fact, there are still some small bugs that need to be dealt with, so I won’t talk about them here.

Add Features

The basic framework has been laid, and there is no help from GPT-4 for the time being. The rest can only be done by ourselves and GPT-3.5.
The next job is to read the Figma Plugin documentation, find out the supported attributes, and then consider how to convert them into the corresponding Tailwind class names.
For example for text type:
1export default function Text(node: SceneNode): string {
2  const textNode = node as TextNode;
3
4  let fontSize = 'text-base';
5  let fontWeight = 'font-normal';
6  let fontColor = 'text-black';
7
8  const textAlign = ConvertTextAlign(textNode);
9
10  if (textNode.fontSize !== figma.mixed) {
11    fontSize = ConvertFontSize(textNode.fontSize);
12  }
13
14  if (textNode.fontWeight !== figma.mixed) {
15    fontWeight = ConvertFontWeight(textNode.fontWeight);
16  }
17
18  const opacity = ConvertOpacity(textNode.opacity);
19
20  if (textNode.fills !== figma.mixed) {
21    if (textNode.fills[0].type === "SOLID") {
22      fontColor = `text-${ConvertColors(textNode.fills[0].color)}`
23    }
24  }
25  
26  return `${textAlign} ${fontSize} ${fontWeight} ${opacity} ${fontColor}`;
27}
First extract the attributes of the text one by one, and then pass them into the corresponding processing functions.
Take the font size conversion function as an example:
1import getNumber from "./getNumber";
2
3const Sizes = {
4  'text-xs': 12,
5  'text-sm': 14,
6  'text-base': 16,
7  'text-lg': 18,
8  'text-xl': 20,
9  'text-2xl': 24,
10  'text-3xl': 30,
11  'text-4xl': 36,
12  'text-5xl': 48,
13  'text-6xl': 60,
14  'text-7xl': 72,
15  'text-8xl': 96,
16  'text-9xl': 128,
17}
18
19export default function ConvertFontSize(fontSize: number): string {
20  const fontSizeClass = getNumber(fontSize, Sizes);
21
22  return fontSizeClass;
23}
After this function receives the font size, it will call another function. The function of this getNumber() is to find the name closest to the first parameter in the second parameter. For example, if the font size passed in is 17, the string text-base will be returned.
The rest of the processing logic is also similar. They all find the closest parameter to the input parameter in a preset parameter and return the class name.
Of course, there are some more troublesome ones, such as color. How to find the one closest to the specified color among a bunch of preset colors?
About how to compare the colors, it is better to have another introduction in the future. Let's move on first.

Finish

Next, you need to continue to increase the width and height, corner radius, transparency, padding, and even layout conversion. The methods are similar, and the layout is additionally mentioned.
People who are familiar with CSS will definitely find that the design of Autolayout is very similar to the Flexbox layout in CSS, and align-items, justify-content, and gap can all be found. So I also specially generated layout-related class names.
The final effect of the plug-in is like this, and the detailed code can be viewed on Github.
https://darmau-image-1256887306.cos.accelerate.myqcloud.com/figma_98b02722bf.jpg
Figma Plugin

What else can I do

After experiencing the whole process, I think AI can't let people with zero foundation realize development, but it can make people with a little knowledge realize their ideas faster.
You may have seen a lot of sayings that GPT-4 can completely help you write code, but I still recommend you not to skip the basic learning. At the beginning, you may be happy to run the demo successfully, but as the implemented functions become more and more complex, you still need to write some logic yourself. If you don't understand it at all, you will have no way to start, or don't know how to ask questions.
For me, with the blessing of AI, I finally dare to develop some ideas that were only in my mind before. The next step may be an OnlyFans original image download plug-in to save some artistic photos.

评论