If you haven’t heard about Figma, it is a UX/UI design application just like Adobe XD or Sketch. It describes itself as “the collaborative interface design tool”. I’ve been using it for a while now, mostly for creating web tips like the one below:
But previously I also used it for it’s main purpose: designing user interfaces. Can you guess what takes up the most time when creating a card similar to the one above? — If you guess highlighting the syntax, you were right. Since I’m using the same colors for the same tokens over and over again, this step is redundant and not automating it is just wasting your time for no reason.
Fortunately, Figma has plugins which could potentially solve this problem. Unfortunately, I haven’t found one so I had to create my own.
First we need to find out whether it is possible to create such thing, so I went over to the API documentations on Figma and looked into the TextNode object since I wanted to work with text. It looked like you can color a piece of text using the
setRangeFills method, so I came to the following conclusion:
setRangeFills expects a start (inclusive) and end (exclusive) index to know what part of the text should be styled. So first we need to get the selected text, get the tokens from it through regex and find out each token’s start and end position then apply the appropriate style to it.
Note that implementing sytnax highlight with regex is not the preferred way. You can’t parse HTML with regex because HTML’s grammar is much more complex than what regex can handle. In order to cope with the task, you would need a full lexer and parser to identifying each token and to make it more robust and act like a real syntax highlighter. However I wanted to get things done with the least amount of effort for a small set of tokens.
Setting up the plugin
In order to start working on plugins, you need to have the desktop application as it can’t be done from the web app. You can download the desktop app here.
Once you have it installed, open it up and go the the “Plugins” option on the left hand side then click on the plus sign on the right hand side next to “Development”. It will present you with the following popup:
Add a name for your plugin and click on Continue. For the template, we are going to use the default: run once. You can also create an empty project or one with a user interface. It will present you with the following folder structure:
Everything will be done inside the
If you don’t have TypeScript installed already, you can install it globally with
npm i -g typescript. Using Visual Studio Code, run the “Run Build Task” menu item (
b on Windows) and select
tsc: watch — tsconfig.json to compile the project on each save.
If you’re getting the following error even after installing TypeScript globally:
tsc is not a recognized as an internal or external command...
Try to add the following into your system environment variables:
code.ts holds a sample plugin, you can delete everything in the file as we are going to build ours from the ground up.
To do some precautions, we will need to check if there is an active selection and if the selection is a text. We can do this with a simple if statement:
figma object is exposed by the API. Once we’re sure we have a selection we can get the whole text by accessing the
To make sure we don’t get type errors we need to cast the current selection as a
TextNode. For styling the text, we can call
setRangeFills on it which accepts three params:
- The start index (inclusive)
- The end index (exclusive)
- The color definition which has a type and a color object.
Making the whole text white can be done with:
As you can see the value of colors can be between 0 and 1 instead of 0 and 255. To make it easier to work with, let’s set up a function for normalizing values.
To cap values between 0 and 1, all we have to do is divide each value by 255:
Using this function, I also set up some predefined colors so they can be easily referenced:
This way, we can simply pass
colours.x to the
setRangeFills function without having to normalize and write out each color. Still, writing out the whole
setRangeFills call can be tedious and we may have to color multiple parts of the text with the same color. To make it a little bit more dynamic, let’s also create a function for the
Make it dynamic
Since we may have multiple blocks which require the same coloring, we want our function to accept an array of indexes, more specifically a multidimensional array of indexes where each sub-array holds a start and end index like so:
[[0, 10], [23, 42]]
We might also want to pass the color here. This leaves us with the following function:
We loop through the
ranges array and set the start index to
index and the end index to
index. To try it out, let’s apply a base color to the text with the following function call:
We can essentially, do the same call for everything else. The only problem which prevents us from doing so is to get the proper index for each block of text. We might want a function that returns a multidimensional array, similar to the one above, that holds each and every start and end index.
Getting text ranges
For getting the indexes, I defined the following function, which takes in a text and a regex pattern which should be run against it:
text.matchAll returns a
RegExpStringIterator, which can be transformed into an array using the spread operator. For each result, we get the index of the match — this will be the start index — and the length of the result, which will be the end index.
Adding some regex magic
We’ve left with the regex. This is what will get us the indexes for each text block. To keep everything in one place, I added a regex object at the top of the file which is responsible for holding regex for every possible token and language:
As you can see, most of the regex patterns are ending with a lookahead which helps us to match certain pattern but avoid the match in the end result. Unfortunately, lookbehinds are not supported yet so we might have matches where we also select unnecessary parts of the string. For example, the
attributeValue will include an equal sign and a quotation mark at the beginning, we don’t want to color them the same way as we do for the attribute value, so we will need to offset the index for some of these patterns.
The way to get around this is to modify the
getTextRange function a little bit:
We can introduce a 3rd param called
padding. Instead of using
textIndex for the start index, we can define a variable that adds
textIndex and use that as the start index for the text range. I also added a safe check to avoid going into negative values as we might get errors for that.
Putting everything together
Putting everything together, we can apply different styles with the combination of two function calls:
getTextRange to the
applyStyles function with the text and the required regex. For styling attribute values, we can supply an offset of 2, so
=" at the beginning of each attribute won’t be styles with the same color.
Lastly, don’t forget to call
figma.closePlugin() as the last thing to terminate the plugin.
To try out the plugin, right click on your text, select Plugins — Development, and there you will find it. Now the code block will come alive with the click of a button: