← ALL NOTES
METHOD·2026.09·6 MIN READ
Figma Variables: How I Set Up Variables in a Design System
figma-apidesign-tokens

This article records the setup I currently use for Figma Variables in a design system, mainly so I can reference it later when extending components, themes, or tokens.

I split variables into several independent collections. This makes them easier to find in the right-side panel and keeps values for different purposes from mixing together.

Variable Collections
CollectionTypeScopePurposeMain binding location
PrimitivesCOLOR—Raw color palette, e.g. ocean/50–900Not bound directly to components; referenced by Semantic
SemanticCOLORTEXTFILL / SHAPEFILL / FRAMEFILL / STROKECOLOR etc.Semantic colors, e.g. text/, icon/, bg/, border/Text, fill, background, stroke
TextSTRINGALL_SCOPESText content tokensText characters
RadiusFLOATCORNER_RADIUSCorner radiusCorner radius
SpacingFLOATWIDTH_HEIGHT / GAPSpacing, paddingPadding, item spacing, gap
BorderFLOATSTROKE_FLOATStroke weightStroke weight
OpacityFLOATOPACITYLayer opacityAppearance → Opacity
SizeFLOATWIDTH_HEIGHTComponent height / widthminHeight / minWidth and similar size properties

I split collections by purpose even when several of them share the same underlying type. Radius, Spacing, Border, and Opacity are all FLOAT, but keeping them in separate collections makes variable names and binding locations easier to reason about later.

Setting Scope

Scope directly affects where a variable is visible in the right-side panel.

For example, even though CORNER_RADIUS, GAP, and STROKE_FLOAT are all FLOAT values, each one only appears in its matching field: corner radius, spacing, or stroke weight respectively.

A precise Scope keeps the variable panel much cleaner. Once a design system grows, having radius, spacing, size, and stroke all show up in the same variable picker makes it hard to find the right one quickly.

The plugin API's setBoundVariable can bypass Scope and force a binding anyway. I avoid doing this. The binding can succeed technically, but the Figma UI may not display or manage it correctly afterward, which makes later review and maintenance harder.

I treat Scope as the actual intended usage range, so designers only see relevant variables in each property field.

Numeric Scales

Both Spacing and Radius use a non-linear scale: denser at the small end, spreading out at the large end.

Spacing. Currently:

0, 2, 4, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 80

Naming roughly follows size order: None → 2XSmall → XSmall → Small → Medium → Regular → Large → XLarge → 2XLarge, and so on.

Small spacing values are used far more often in the UI, so steps like 2, 4, 6, 8, 10, and 12 stay fine-grained. Beyond 24, the steps can spread out more.

Radius. Currently:

0, 1, 2, 4, 6, 8, 10, 12, 16, 20, 24, 32, 9999

9999 is used as Full, suitable for pill buttons, circular tags, and other fully rounded shapes. Naming: None → XSmall → Small → Regular → Medium → Large → XLarge → Huge → Full.

Border. Only a few commonly used values are kept:

0   = None
1   = Thin
1.5 = Regular
2   = Thick

If the product has no real use case for 3px or 4px, I don't build out a complete scale just to have one.

Opacity. Uses plain percentage integers:

5, 8, 10, 20, 30, 40, 50, 60, 70, 80, 90

Naming matches directly: 5% → 8% → 10% → 20%, and so on. Figma's Appearance panel already uses percentages for Opacity, so the variable value is written as 10 or 20, not 0.1 or 0.2.

Size. Built around real component dimensions. Currently mainly used for button height:

24 = XXS
28 = XS
32 = SM
36 = MD
40 = LG
48 = XL

Size does not need to cover every possible value up front. New fixed sizes can be added when a component genuinely needs one.

Color: Primitives and Semantic

Color is currently split into two layers:

Primitives → Semantic → Component

Primitives store raw color values, for example ocean/50 through ocean/900. This layer only represents the palette itself.

Semantic is named by usage, for example text/primary, text/secondary, icon/primary, bg/default, bg/brand, border/default. Semantic variables reference Primitives, and components bind to Semantic.

For example, a brand-color button binds to bg/brand in the component, and bg/brand currently points to ocean/600. If the brand color changes later, only the Semantic reference needs to change.

Light / Dark mode can use the same approach. Components keep using semantic variables such as text/primary and bg/default, without needing to change colors component by component.

So at the component layer, I try to avoid binding directly to a raw color value like ocean/600.

Layer Opacity vs. Color Alpha

Both make something transparent, but they apply in different places.

Layer OpacityPaint Opacity / Color Alpha
LocationAppearance sectionInside the Fill / Stroke color value
Scope of effectThe whole layer and all its childrenOnly the current Fill / Stroke
Common useDisabled states, fade in/out, reducing a whole component's visual weightSemi-transparent backgrounds, masks, overlays
Variable formFLOAT in the Opacity collectionThe Alpha channel of a COLOR variable

I generally decide this way: for a temporary faded state such as Disabled affecting the whole component, use Layer Opacity; for a color that is inherently and permanently semi-transparent, such as an overlay or translucent background, put the alpha inside the color variable itself.

This distinction matters especially with nested components. Layer Opacity affects child elements too, so if the intent was only to make a background semi-transparent, text and icons inside it can end up unintentionally faded as well, stacking opacity where it wasn't wanted.

Things I Keep in Mind With This Setup
  • Split variable collections by purpose to make them easier to find and maintain.
  • Keep Scope aligned with actual usage as closely as possible.
  • Use a scale for Spacing and Radius that is dense at small values and sparser at large ones.
  • Keep Border limited to values that are actually used.
  • Grow Size incrementally as specific components need it.
  • Use the Primitives → Semantic → Component binding relationship for color.
  • Have components consume Semantic variables rather than raw color steps.
  • Use Layer Opacity for whole-layer states, and color alpha for Fill / Stroke transparency itself.
  • Avoid forcing a binding through the Plugin API when the Figma UI can't display or manage it conveniently afterward.

This structure already covers most of my variable usage in the design system. As new component types or themes are added, I extend the collections and values as the actual need comes up.

CONTINUE READING
Translating Figma Component Properties into Code Props2026.09Engineering a Figma Design System into Production Assets2026.09