A tale of two parameter architectures—and how we unified them
Today, Figma offers a suite of dynamic features: responsive design, typography, animations, state machines, and more. These features all rely on the concept of parameters: values that can be set in one place and will propagate automatically throughout the project, ensuring quick, consistent updates.
When a parameter value changes, such as a color variable’s value, the parameter system ensures that the bound layer’s property immediately reflects this change.
Component properties and variables are both examples of “parameter systems,” or ways of achieving a behavior in Figma that’s set-once, apply-across. But while they’re similar, these systems evolved differently, to solve different core limitations our users faced—and the underlying architecture was not the same.
By 2024, it became clear that having separate architectures for these two systems was creating more problems than it solved, with unexpected behavior that confused users and made these features harder to learn and use reliably. For example, it was possible for a variable and component property to bind to the same layer property, leading to inconsistent rendering in the editor. Maintaining separate architectures also made it nearly impossible to bring parametrization to other Figma products without inheriting these technical burdens.
In this article, we’ll discuss how we built a single, unified architecture that now powers both component properties and variables under the hood. This has eliminated user-facing inconsistencies, improved developer velocity, and created a scalable foundation for parametrization across all current and future Figma products.
Deep dive: Component properties and variables
In 2022, we launched component properties to bridge an important gap: Designs couldn’t be easily translated into code, limiting both developer handoff and direct code generation features.
Here, we author a boolean component property on a given button component set that can subsequently be bound to layer properties within the button.
Scoped parametrization describes a system that defines parameters for specific layers and restricts their use to those layers only.
Component properties brought scoped parametrization to Figma, allowing components to define parameters that apply to internal layers. Design system authors could use this feature to create a contract for how a component should be customized, preventing downstream users from breaking the original design. But the real breakthrough was that component properties mirrored properties in code. For the first time, designers and developers could customize components using the exact same mental model. This helped us bring code capabilities into the canvas in products like Figma Sites, Figma Make, and Code Connect.
Variables took on an even broader set of types (color) and could define context-dependent values under different modes (light and dark).
Global parametrization describes a system that defines parameters at the library level. These parameters are available to bind to layers within any file that has access to that library.
In contrast, variables (launched in 2023) brought global parametrization to design, allowing any file with access to the variable to apply the variable to its design layers. Before variables, styles existed, but users lacked a way to parametrize more granular properties within elements like typography and gradients. This made it difficult to build a scalable design system with comprehensive product consistency. Variables became the new shared primitive that parametrized a wide range of design properties and powered a suite of advanced prototyping animations and interactions.
Styles launched in 2018 and consisted of parametrized bundles of values. For example, a given text style specifies a font family, font style, font size, etc., that are applied together to layers.
Before variables, prototyping interactions and styles offered some dynamic controls, but they couldn’t support more sophisticated behaviors like expressions, conditional logic, or context-aware values that would enable truly dynamic designs. In many cases, changing a design’s properties required manual manipulation. Variables unlocked powerful dynamic behaviors that brought static designs to life.
Variables can be used within advanced prototyping to represent global state (sprinkleVisibility). This state can then be composed as arguments into an expression to compute further state (cakeComplete).
Component properties and variables serve different primary use cases, but at their core, they are the same: Each defines parameters that bind to granular design properties. When a parameter’s resolved value changes, the bound property should update accordingly. Therefore, our unification strategy focused on two levels:
- Data model unification: All parameter systems should reference shared definitions of parameter data types and property bindings.
- Runtime unification: All parameter systems should execute on this blueprint to power dynamic canvas behavior consistently and performantly.
Unifying the data model
The main difference between component properties and variables is where the source of truth lives. Component properties live directly on a component or instance layer, while variables live at the library level. Despite this key difference, we were able to unify our architecture so that now these parameter systems share two critical aspects at the data model level: the space of parameter value types and the binding of parameters.
At the data model level, component properties and variables maintained completely separate type definitions—think VariableType.BOOLEAN
versus ComponentPropType.BOOLEAN
. This duplication meant that adding support for a new type required implementing it twice: once for variables and once for component properties. Beyond the obvious maintenance problem, this separation created an even more fundamental issue: How do you bind parameters across systems when their type definitions don’t match?
Our goal was to consolidate these fragmented typespaces into a unified one for all parameter systems. This meant establishing a single source of truth for types like boolean, string, and number rather than maintaining parallel definitions that could drift apart over time.
A single typespace automatically unlocked interoperability between parameter systems. Previously, you couldn’t bind a component property to a number variable because there was no such thing as a number component property; that type simply just didn’t exist within the system’s vocabulary. With a shared typespace, these cross-system bindings became more natural and immediate.
This unification was the key enabler to our component property to variable binding feature, eliminating the type mismatches that would have otherwise made such bindings impossible.
Unifying parameter bindings
Our guiding principle was simple: The same set of properties should be bindable across parameter systems, and each layer property should be bound to at most one parameter.
However, in the previous model, each parameter system defined its own way of storing parameter bindings. This meant that a layer could bind a property to multiple parameters from different systems simultaneously, resulting in an unclear source of truth for that property’s value. And, introducing a third parameter system would require a third way to store bindings for that system’s parameters, compounding data model redundancy.
To address this, we updated the data model to have a single way of storing parameter bindings, regardless of the parameter system from which they originated. This consolidation meant that a given property could only be bound to one parameter, and any future parameter system could leverage existing binding infrastructure without building its own.
While the data model defines how bindings and parameter values are stored, the runtime powers the dynamic behavior of parameter bindings. The runtime manages when, where, and how parametrized properties are recomputed and updated in response to changes—bringing parameters to life on the canvas.
Consider a simple example: When a layer binds its fill color to a variable, changing the variable’s color should automatically update the layer’s appearance on the canvas. This appears to happen instantly, but several steps occur under the hood in the runtime:
- Parameter usage tracking: Determine what layers use the changed variable.
- Invalidation: When the color of the variable changes, gather the affected properties across all layers in the design.
- Resolution: Re-resolve these affected properties to determine the final color values.
- Updates: Re-render the layers to reflect the new resolved color.
Before unification, each parameter system handled these steps differently. This prevented a single contract for how parameter systems worked, slowing down product developers who were building on top of these systems.
A variable alias occurs when a variable’s value references another variable. For example, a brand-primary color variable may alias a blue-500 color variable.
To solve this, we standardized all parameter systems to use the same flow:
Transitive resolution is the process of resolving a parameter through multiple levels of references. For example, when button-primary-bg references brand-primary, which in turn references blue-500, we must resolve the entire chain to determine the final color value of the original button-primary-bg.
- Parameter usage tracking: When a parameter changes, we need to know which layers will be affected so we can invalidate them. To do this, we track parameter usage at the most granular level, so that for any parameter, we know what layer properties are bound to it.
- Invalidation: Many user actions can trigger changes to parametrized layer properties: updating a parameter’s value, binding a parameter to another parameter, or switching variable modes on a layer. When this happens, the runtime finds all affected layers to determine which ones need to be re-resolved by leveraging our unified tracking system. Under the unified flow, we also optimized invalidation granularity, ensuring that invalidation occurs at the property level across all parameter systems, rather than at the layer level.
- Resolution: For each affected property, the runtime re-resolves its value. In the simplest case, resolution is a forward lookup on the value of the directly bound parameter. However, this lookup can also involve multiple hops in transitive cases (like variable aliasing) or even cross parameter system boundaries (as with component property to variable bindings). With our newly unified parameter typespace, we built a single path to perform resolution across all parameter systems, which provided automatic handling for more complex lookup patterns like these.
- Updates: After determining their newly resolved values, the runtime updates layer properties visually on the canvas. This should work the same way regardless of which parameter system triggers the change—we’re simply replacing one property value with another. To achieve this, we consolidated the two parameter update flows into one that handles re-rendering consistently across all parameter systems.
Paving the way forward
This generalized parametrization architecture has significantly improved the consistency and reliability of component properties and variables. It’s also created a stable foundation for the upcoming content management system—a prospective third parameter system announced at this past Config—and will unlock more exciting product offerings within Figma Sites and Figma Buzz, where parametrization will play a greater role than ever before.
A preview of the upcoming Figma Sites CMS, a third parameter system.
Figma Sites code layers and code component instances use parametrization for customization.
This effort has additionally resulted in performance wins to variable authoring flows due to stricter and more granular runtime invalidation. We’ve substantially reduced the total time it takes to add, delete, and duplicate variable modes, as well as change variable values.
Thanks to the countless Figmates who contributed to this effort: Stephanie Cheuk, Randall Koutnik, Peter Hayes, Jon Kaplan, Naomi Jung, Isaac Goldberg, Georgia Rust, Jacob Miller, Brian Lam, and many others involved in rearchitecting design systems at Figma.