Skip to main content

Widgets

Widgets Overview

Build interactive canvas objects with the Figma Widget API — a React-like framework for Figma.

Widgets are interactive objects that live directly on the Figma or FigJam canvas. Unlike plugins (which run as modal tools), widgets are persistent, collaborative objects that multiple users can interact with simultaneously.

What Makes Widgets Special

Canvas-Native

Widgets render as first-class canvas objects. Users can move, resize, and interact with them like any other design element.

Collaborative

Widget state syncs across all users in real-time. Multiple people can interact with the same widget simultaneously.

React-Like API

Build widgets using a familiar JSX syntax with hooks like useSyncedState, usePropertyMenu, and useEffect.

Persistent State

Widget data persists in the file. State survives page reloads and is included when the file is duplicated or versioned.

Widget Lifecycle

A widget goes through several phases:

  1. Registration — The widget function is registered with widget.register()
  2. Render — Figma calls your widget function to get the UI tree
  3. Interaction — User clicks trigger state updates
  4. Re-render — State changes trigger a new render cycle

Building a Widget

Widgets use a React-like API with special Figma primitives:

const { widget } = figma;
const { Text, AutoLayout, useSyncedState, usePropertyMenu } = widget;

function CounterWidget() {
  const [count, setCount] = useSyncedState("count", 0);

  usePropertyMenu(
    [
      {
        itemType: "action",
        propertyName: "reset",
        tooltip: "Reset counter",
      },
    ],
    ({ propertyName }) => {
      if (propertyName === "reset") setCount(0);
    }
  );

  return (
    <AutoLayout
      direction="horizontal"
      spacing={12}
      padding={16}
      cornerRadius={12}
      fill="#FFFFFF"
      stroke="#E5E5E5"
    >
      <AutoLayout
        onClick={() => setCount(count - 1)}
        padding={{ horizontal: 12, vertical: 8 }}
        cornerRadius={8}
        fill="#F3F0FF"
        hoverStyle={{ fill: "#E8E0F0" }}
      >
        <Text fontSize={16} fontWeight={600}>−</Text>
      </AutoLayout>

      <Text fontSize={24} fontWeight={700} width={60} horizontalAlignText="center">
        {String(count)}
      </Text>

      <AutoLayout
        onClick={() => setCount(count + 1)}
        padding={{ horizontal: 12, vertical: 8 }}
        cornerRadius={8}
        fill="#A259FF"
        hoverStyle={{ fill: "#B370FF" }}
      >
        <Text fontSize={16} fontWeight={600} fill="#FFFFFF">+</Text>
      </AutoLayout>
    </AutoLayout>
  );
}

widget.register(CounterWidget);

Available Primitives

PrimitiveDescription
AutoLayoutFlexible container with auto-layout (like CSS flexbox)
FrameFixed-size container
TextText node with font, size, and color controls
SVGInline SVG rendering
ImageImage display from URL or bytes
InputText input field
EllipseEllipse shape
RectangleRectangle shape
LineLine shape

Key Hooks

HookPurpose
useSyncedState(key, default)State that syncs across all users
useSyncedMap(key)Key-value map that syncs across users
usePropertyMenu(items, handler)Right-click property menu
useEffect(fn)Side effects on mount
useWidgetNodeId()Get the widget’s node ID
const [value, setValue] = useSyncedState("myKey", "default");

Widgets have a maximum render size of 2048x2048 pixels. For larger content, use scrollable containers or pagination within your widget.

Test widgets with multiple Figma accounts to verify that synced state works correctly across users. Use the Figma Desktop App for the best development experience.