Friday, November 15, 2024

Demystifying Figma’s Variable Mode Inheritance

The Grammarly Design team uses Figma to design the product experiences that help teams communicate and do their best work. Our design system team uses features like variables to build components that reflect what’s available in code. This allows us to enable a smooth handoff between design and development and scale consistent design across the product.

Before variables, we only had styles in Figma to ensure consistency across design files. However, each style is limited to storing a single value, while a variable can be associated with multiple color, string, number, or Boolean values. These values can then be assigned to different variable modes to easily change the look of any design.

Switching between light and dark modes has become fairly common in user interfaces, so variable modes might seem relatively straightforward at first glance. But variable modes behave differently from everything else in Figma, particularly in the context of parent frames and the components inside them. We usually think of components as being entirely encapsulated—separated from what’s going on in the rest of the frame—but when it comes to variables, that assumption doesn’t hold. Instead, there’s inheritance at play.

Our design system team confronted the complexities of variable mode inheritance when designing the component. A popover is a graphic user interface element that pops up and shows extra information or choices when a user triggers it. We wanted the popover’s border to disappear in light mode and be visible in dark mode. This small design requirement taught us three things about how inheritance works with variable modes in Figma.

A popover introduces how to use genAI prompts in combination with a piece of text

Lesson 1: Child elements inherit the mode of the parent container

Typically, a variable mode is set at the parent frame level and controls how all elements within the parent frame display. For example, if a page should display in light mode, that variable mode only needs to be applied to the outermost frame. All components and elements within the parent frame inherit the light mode setting, causing the visual aesthetic to change appropriately.

The popover inherits light mode colors because the parent element is scoped to light mode.The popover inherits light mode colors because the parent element is scoped to light mode.

Lesson 2: Child elements can be scoped to a mode independent of the parent container

The popover in our design system is different from other components because it uses dark mode colors regardless of whether the parent frame is scoped to light or dark mode. We achieve this by applying the dark variable mode directly to the component, instead of allowing the parent frame’s variable mode to determine its style. That means the component will maintain a dark background even if placed in a parent frame that’s scoped to light mode.

The popover component is scoped to dark mode, which prevents it from inheriting the variable mode of the parent frame.

How can we change a component’s behavior based on its parent’s variable mode?

We now had a popover component that remained in dark mode regardless of the mode of its parent.

Here’s where we ran into an interesting problem. We wanted the popover to look a bit different depending on the mode of its parent. Specifically, when the parent frame is set to dark mode, we wanted to add a border around the popover to ensure enough contrast for accessibility reasons and to prevent it from blending into the background. When the popover is placed in a parent frame scoped to light mode, the border is an unnecessary design detail, and we wanted to remove it. The challenge became changing the visibility of the border without relying on designers to do it manually, which led us to explore a few options.

Option 1: Use component variants

The simplest solution seemed to be using component variants with a Boolean property to remove the border. However, the popover component already had 24 variants to accommodate different content layouts and pointer positions. Doubling the number of variants isn’t ideal from a maintenance standpoint. It would also mean that designers would have to hide the border manually. This option was quickly dismissed.

Using a variant property requires designers to manually toggle the visibility of the border depending on the mode where the popover is displayed.

Option 2: Use a unique border variable

Next, we created a unique variable to control the border by setting the opacity to 0% in light mode. When building components, we try to minimize complexity by using nested frames only when necessary. For the popover, the border and background colors are applied to one parent frame wrapping the component. But because we’re setting the component to use dark mode, any light mode values, including the unique border variable, aren’t inherited. This became evident when the popover was placed into a parent frame set to light mode, and the border didn’t disappear. While this option failed, it did lead to the solution of using variable mode inheritance by properly structuring the component frames.

A special token set to 0% opacity was created to hide the border in the coded implementation of the popover. This was replicated in Figma using variables.A special token set to 0% opacity was created to hide the border in the coded implementation of the popover. This was replicated in Figma using variables.

Lesson 3: Use nested frames to “hack” variable mode inheritance

We’ve already established that child elements will inherit the variable mode of the parent frame if a mode isn’t scoped at the component level. This is how CSS inheritance works, and we can apply the same concept in Figma, too. So, we had the idea that we could automatically control the visibility of the border by adding another external frame around the component that would purely handle the border. This new frame would have no variable mode applied to it.

The external frame has no variable mode assigned to it, and the border is visible because it inherits the dark mode setting from the parent frame.

The popover is now divided into two frames: an external parent frame responsible for the border, and an internal child frame representing the rest of the popover component. We can now scope the internal frame to dark mode, making the background color, text, buttons, and icons display as expected using dark mode colors, with no impact on the border visibility.

The external frame containing the border isn’t impacted by the variable mode set on the internal frame.

We keep the special border variable set to 0% opacity from option 2, which will hide the border in light mode. We don’t need to make any manual changes to the external frame that’s responsible for the border, because it will inherit the parent frame’s variable mode automatically.

The border is hidden as it inherits the 0% opacity set in light mode.

Understanding the inheritance of variable modes gives us another way to build components that are easy to use and behave like their counterparts in code. Visit the Figma documentation for more information about applying variables and modes to your designs.

This article originated as a presentation at Friends of Figma San Francisco along with Anthony DiSpezio, Designer Advocate at Figma. If this piece was interesting to you, you might like the kind of work that we do on Grammarly’s design team. Check out our open roles here.

Related Articles

Latest Articles