Figma Widgets: Moving Beyond Static Boards

Figma has always been strong on collaboration, with a rich library of community plugins for everything from 3D elements to abstract SVGs. Yet the core design canvas has historically been static. That changed when Figma introduced JavaScript-powered widgets, bringing logic-driven components directly into design and FigJam boards.

Unlike plugins, which often operate on your files from an external UI, widgets live on the canvas itself. They can be stateless or hold internal state, update in real time, and respond to user interaction. This opens the door to a wide range of uses beyond the visual design phase—project management tools, live data feeds, voting polls, or even a quick game of Tic-Tac-Toe when the team needs a break.

You can add existing widgets to your board from the Widgets menu (Shift+I), but the real power lies in building your own.

Setting Up a Widget Project

Developing widgets requires the Figma Desktop app for Windows or Mac. Linux users are out of luck unless they rely on a virtual machine. Once installed, you can start a new widget project directly from the app: open the Widgets menu (Shift+I), switch to the Development tab, and create a new item.

Figma will ask you to name the widget and choose whether it targets design boards or FigJam boards. The design option works fine for this exercise. You also get a choice of starting templates: a simple counter, an iFrame-enabled version, or an empty project. Even with the "Empty" option, you can later add access to network features.

After you save the project to a local directory, opening that folder in your terminal is the first step. Before running any commands, know that hitting some errors is part of the learning curve. The process of understanding those errors will teach you more about how widgets work.

Designing the Layout in Code

The widget we're building is a random design quote generator, visually inspired by Chris Coyier's design quotes website. Rather than recreating that site's full look, the key is to pull the colors, fonts, fonts weights, and font sizes from it—noting them with DevTools (Ctrl+Shift+C or Cmd+Shift+C)—and then translate those into widget styling.

In Figma, familiar design building blocks map to React-like components. Auto-layout frames become the <AutoLayout /> component. Text elements are <Text />, and custom shapes or icons use <SVG />. The API is compact: just eight layer-based node components. These few primitives are enough to build nearly any layout.

If the resulting JSX looks like a jumble of anonymous layers, clear that up by assigning a name property to each element. Naming things well makes the code readable at a glance.

<AutoLayout name="Root">
  <AutoLayout name="Header">
    <SVG name="QuoteIcon" />
  </AutoLayout>
  <Text name="QuoteText" />
</AutoLayout>

The <SVG /> component takes a src property containing the actual SVG markup. For quotation mark icons, that means pasting in the SVG source directly.

Live Preview and Hot Reloading

Figma provides a solid developer experience with hot reload: edits to your code are reflected on the canvas in real time. To preview your work, open the Widgets menu, go to the Development tab, and drag your widget onto the board. If it isn't listed, import its manifest.json file via the three-dot menu.

A common hurdle: nothing renders, and an error message appears. Following the console instructions—or navigating via the Figma logo → Widgets → Development—will reveal the issue. It's likely that your TypeScript hasn't been compiled to JavaScript. Run npm install and npm run watch (or the yarn equivalents) in your project folder to fix that.

If your widget goes stale and doesn't reflect recent code changes, force an update from the context menu: WidgetsRe-render widget.

Styling Through Props

Unlike React projects, there is no CSS when it comes to styling Figma widgets. All formatting happens via a set of well-documented props on the components. The naming convention aligns almost exactly with the controls you see in the Figma UI.

AutoLayout props in the Widgets API correspond to Figma's layout panels.

To make the widget design come alive, start by configuring the two root <AutoLayout /> components. Setting properties like direction, padding, horizontalAlignItems, and verticalAlignItems mirrors how you'd use the auto-layout features inside Figma itself.

The very first time, the widget will update instantly. But it's still bare. Add a fill background color to the root container. Next, shift focus to the <Text> components, using values from the style guide you inspected on the website: the font family, weight, and size props are spelled out in the API reference.

Adding Dynamic State

Right now, every instance of the widget shows the same quote. To randomize this, the widget needs state. Figma implements this with the useSyncedState hook. It's very close to React's useState but requires a unique key because the state syncs across everyone who's viewing the widget live in a shared Figma board.

const { useSyncedState } = widget;

function QuotesWidget() {
  const [quote, setQuote] = useSyncedState("quote-text", "");
  const [author, setAuthor] = useSyncedState("quote-author", "");
}
const [quote, setQuote] = useSyncedState("quote", "Default quote text");

This code is the foundation to store the fetched quote. The next step is figuring out how to get data from the internet into that state variable.

Fetching External Data

You can't simply call fetch() in your widget code. The Figma widget runtime executes third-party JavaScript in a sandbox to protect against malicious actors—even though all widgets are human-reviewed, a single bad line of code could cause damage. That safe sandbox does away with browser APIs, and that's exactly what an iframe restores.

By writing plain HTML in a separate file, typically named ui.html, you get full access to the browser API inside your widget. A small, well-defined communication channel connects this iframe back to the widget's logic.

When you need data, the widget sends a message, like "networkRequest":

iframe.postMessage({ type: "networkRequest", url: "https://quotesondesign.com/wp-json/wp/v2/posts?orderby=rand" });

The ui.html page listens for that message, uses the standard fetch() API against that URL, and then posts widget-bound data back to the main thread.

The tricky part is knowing when to show the hidden iframe to trigger these messages. Calling showUI() directly inside the widget's component will spit out an error—you have to defer it. That's where the useEffect hook and the waitForTask function come in.

useEffect(() => {
  waitForTask(fetchData());
}, []);

Using useEffect like this triggers after state changes, which means in Figma's implementation that hook can fire repeatedly. To make sure we fetch data only when we need it, the trick is to check if our quote state is still empty (the default `` string) before initiating the network request inside the effect.

Some quotes returned by the API may contain odd elements, but in real use any HTML special characters that leak through can throw off the design. Formatting them on the JavaScript side before setting the state keeps the display clean.

A pro tip: if you hit a bizarre "memory access out of bounds" error—a known quirk during development—a simple Figma restart usually clears it.

Adding Interactive Menus

It's nice to get a random quote on creation, but users will want to refresh it without deleting the whole widget. The usePropertyMenu hook handles this beautifully: with a single function call you expose a context menu button that appears when your widget is selected.

Credit: Figma Docs.
const menu = usePropertyMenu([
  { item: "refresh", tooltip: "Refresh Quote", property: "action", icon: "RestartIcon" }
], onMenuAction);

Inside the handler, detecting that the clicked item is "refresh" triggers the same fetch routine again. That single hook creates immediate interactivity.

Going Public with Your Widget

For a widget to be genuinely useful, it needs to see the light of day. Organizations can publish widgets privately, but releasing to the world goes through Figma's review process. Expect it to take 5 to 10 business days.

Publishing starts in the same Development tab under the Widgets menu, selecting the three-dot menu on your widget, and pressing Publish. The modal requests standard metadata—title, description, tags—plus a 128×128 icon and a 1920×960 screenshot for the community page.

The screenshot can be generated directly in Figma: right-click the widget, hover over Copy/Paste as, and pick Copy as PNG. Paste that image into the publishing modal's designated area. Submitting the form clears the final hurdle; from there, Figma's staff contacts you regarding approval status. A rejection often means more changes, but the resubmission path is straightforward.

What's Next for Your Figma Widget

The widget you've built is just the starting point. Figma's widget API also supports click events, input forms, and image handling, which open up considerably more interactive possibilities. If you want to see the complete implementation from this walkthrough, the full source code is available in this GitHub repository.

The best way to push your Figma skills further is to explore the Widgets community. Pick apart widgets that catch your eye and use them as a jumping-off point for your own experiments. Keep shipping widgets, keep refining your React knowledge, and you'll quickly find yourself working with patterns far beyond what we covered here.

Helpful Documentation

Building this widget required digging through a fair amount of Figma's documentation. The following resources proved most valuable during development.

Widget Development Guides

API Reference

Widgets vs. Plugins