The shared language of props

Component properties differ across design files and coding frameworks. How can we translate and align them to help designers and developers work better together?
False cognates are words that look similar but have different meanings; heteronyms are words that are spelled the same but are pronounced differently.
Language can be both a barrier to and a catalyst for communication. The right word can precisely describe a feeling and drive alignment around an idea. At the same time, words that sound similar might have vastly different meanings across languages, or different pronunciations and meanings within the same language—despite being spelled identically. And, of course, communicating effectively isn’t just about the words we use, but also the perspectives we bring and the contexts we share (or don’t).
The same is true for the languages of design and engineering. As an engineer, I use many of the same terms as my design counterpart, but our different environments and contexts often lead to differences in how we interpret these terms. This is a good thing—we should be leveraging each other’s unique expertise. The problem is when we think we’re talking about the same thing, but we’re not.
When a component is used or implemented, it is considered to be an instance of the component. These instances are where that expression takes place, by modifying the values of component properties.
Before we go too deep, let's start with some loose definitions for components and their properties. First up: components. Whether you’re designing in Figma or bringing designs to life in a codebase, you’re familiar with the concept. Components make the process of building and maintaining more efficient, and allow you to be consistent at scale. More specifically, they are reusable elements with rules describing how they can be expressed. These rules are defined in the component as properties (or “props,” as they’re affectionately called). Props describe the various ways a component might behave and look, such as a variation or interactive state.
“Boolean” (true/false values) is a great example of how designers and developers approach shared concepts from different perspectives. It’s an engineering concept that developers learn when they just start out; often designers are introduced to the idea by using Figma with their developer counterpart.
It is with this unique framing that I ask sincerely: What is a property? Designers and developers are familiar with props, but view them through their unique lenses. For designers in Figma, props are used almost exclusively to mark visual differences. Figma props come in different types, like variant, boolean, instance swap, and text. Developers share a lot of these concepts, but might use other terminology to describe them; they might use the same language to describe something subtly different. Developers also have properties that are non-visual, like event handlers, or data-related properties. In this sense, they see the purpose of props very differently, even when there’s a shared understanding of them as a concept. Maybe that’s obvious, maybe that’s frustrating, but it’s the truth! Let’s talk about why.
A case study on buttons
It’s all too easy to conflate language across environments—especially when describing a shared concept, like a button. We see buttons all the time. They seem pretty straightforward. But in practice, a button in Figma isn’t the same thing as the button component in a codebase. While we share a lot of language about the visual appearance of buttons, designers are thinking about visual consistency and how they will be implemented across their files, and developers are thinking about interactivity and how they will be rendered with code. Same concept, different considerations.
Material UI is an open source project based on Google's Material Design.
Before joining Figma, I worked as a software engineer on a React and TypeScript web application, where I onboarded right as they were migrating to a front-end that would be compatible with a design system. They were building on top of Material UI (MUI); while MUI leveled up the front-end, its conventions often conflicted with those of the purpose-built design system. Migrations like these surface these differences in language and understanding and present an opportunity for closing those gaps with effective communication. They also demonstrate just how complex the idea of something as simple as a button can be.
The driving force behind this effort was to build components that were easy to implement by full-stack engineers whose primary focus was connecting the front to the back-end. We wanted our engineers to find the component library intuitive and lightweight. Our goal was to create an accessible and well-designed front-end that reduced the amount of considerations they needed to think through about style and front-end concerns.
We had two button components in Figma: Button, and IconButton. Those Figma components didn’t share a primitive component like they did in the codebase. Component inheritance doesn’t exist in Figma in that way. You might even argue that a single Figma button component would suffice, and I wouldn’t disagree. In our case, they were separate because it was the best way to model our idea from a design perspective. There was redundancy in some of the props (like size and color variants), but we were fine with that. It was easy enough to stay in sync, and it didn’t threaten consistency.
In addition to those two button components in Figma, we had five in the codebase. Five. The Button and IconButton components in Figma shared a name and concept with the same-named components in the codebase, but they didn’t share the same purpose or contain a third of the props as their counterpart in the codebase. Implementing components as a designer in Figma differs from implementing as a developer in a codebase. When you optimize for the developer or designer experience with a component, it is tailor-fit for that specific purpose—even if it shares a name with a component in another environment. If its purpose is different, so too are its patterns. We can’t reduce a component to just a name. Context is crucial.

Implementing components as a designer in Figma differs from implementing as a developer in a codebase. When you optimize for the developer or designer experience with a component, it is tailor-fit for that specific purpose—even if it shares a name with a component in another environment.
You may be familiar with the idea of JavaScript frameworks, or you may be framework-agnostic (I see and envy you!). You may build for a platform other than the web. You may also have no idea what I’m talking about (👋 you belong here, too). In any case, hang with me for a sec. Roughly speaking, this component library had the following component inheritance for our concept of buttons in the codebase:

One of these five components, MuiButtonBase, was an existing component that we utilized to get a lot of initial core property definitions that were super helpful under the hood. These are things that are not easy to justify building an entire methodology around when you can just grab one out of a box. ButtonPrimitive was our custom base component that sat as an ideological layer, protecting the components that engineers implemented (Button) from all the hurdle-hopping we were doing to customize the core MUI base component.

A eureka moment here for me was that I could bake accessibility requirements into these prop definitions. For example, best practice for aria-label and buttons is to only provide these labels when the button content is not descriptive of the action. Buttons that have no text and only an icon are not descriptive, so we enforced aria-label as a required prop when button.iconOnly was true. Instances where it was false had aria-label as optional. We added comments and documentation about nuances of when to add an aria-label for this optional scenario, so that developers would have that information pop up in their editor as they implemented buttons. This primitive layer allowed us to enforce and educate about accessibility at the component library level—a huge win.

Three other components—Button, IconButton, and SpecialtyButton—were limited expressions of the ButtonPrimitive, whose express purpose was easy implementation. They might share some ButtonPrimitive property definitions (the size variant, color variant, onClick, etc), but have certain props hardcoded. For example, when ButtonPrimitive was wrapped by the Button component, that iconOnly logic was hardcoded as iconOnly={false} behind the scenes, and the IconButton component was hardcoded as iconOnly={true}. Implementation engineers didn’t need to know any of this! The types just worked and these components could be implemented with the least amount of overhead for the developer implementing a button in the course of their feature development work. All they knew was that aria-label and icon were required props when they went to implement an IconButton.

Components in a codebase serve a very specific purpose, beyond the visual layer. There can be many components for something as simple as a button. Yes, there are components in Figma and components in the codebase. Yes, in the case of design systems they can describe the same pieces of user interface. No, that does not mean they are the same component and no, they do not need to use the exact same language. It’s not that we need a unified language in all contexts; rather, we just need enough context to get everyone on the same page.
It’s not that we need a unified language in all contexts; rather, we just need enough context to get everyone on the same page.
Our shared vocabulary
In practice, when should we use different words—one in design, and one in development—and when can we use the same language? Variant component properties like size: "small" | "medium" | "large" or variant: "primary" | "secondary" | "basic" | "danger" | "success" are the easiest things to align on and often comprise a majority of what a developer needs to know when implementing a component in a design. It is funny, though, how often differences in word casing creep in as an unnecessary hurdle here. One quick win is to agree to name your props and variant options the same thing. Many of the designers I’ve worked with are good with naming, and developers often have requirements for format (and the answer is almost always camelCase). Play to each other’s strengths.

Even this “simple” case for aligning language is not without caveats. One road bump is that the only way to model interactive default, hover, focus, and pressed “states” of a component in Figma is with variants. State is not a variant property in the world of web; it’s a style within the theme variant (“primary,” “danger,” etc). What do we do here? We can add a prefix like “:state” or “*state” to the Figma property definition signifying that this property isn’t a property in the codebase.

Another thing we can do is align the terminology of these states to reflect the corresponding terminology from the platform. In the case of the web, we might prefer “initial, focus-visible, active” over “default, focus, pressed.” The difference between :focus and :focus-visible in CSS is far more meaningful than at this design variant level. How far you go into aligning these is, of course, for you to decide. Bringing that nuance of CSS implementation into the component language might do more muddying than good. There are opportunities to align in both directions, and more clearly understanding your counterpart’s needs will help you make those concessions together.

Oh, and what about when a button is disabled? A designer might add “disabled” to the aforementioned “:state” variant since it is mutually exclusive with the other states. That makes a lot of sense. In code, however, disabled is often its own prop in addition to a separate style. In this case it might be best to leave it a state variant option in Figma, but also express it in the codebase as a disabled boolean property. There are times where there will just be differences in how you describe components with properties. That is ok too. You can still align on the casing of the word “disabled.”

At Figma, we’re constantly evolving our product to meet folks where they are, which will always require a certain amount of adopting implementation patterns. But we also need to think about designer ergonomics in the same way engineers think about developer ergonomics. One of our goals is to speed up the product development process as much as possible, and doing so requires fully embracing the design experience in a design tool in the same way that we embrace the developer experience in development tools.
With that will come differences in language, due to the difference in environment. Today, what’s most important is that we learn to recognize these differences and focus on how we can work together to describe what we are building with the language defined by the tools in front of us. Otherwise we end up trying to force a conformity that will inevitably leave certain needs unmet and always be trailing behind.
Today, what’s most important is that we learn to recognize these differences and focus on how we can work together to describe what we are building with the language defined by the tools in front of us.
Specifics will change based on the products you’re building, but let’s agree to align where possible and have conversations when that alignment causes friction. The best path forward is always going to be some combination of priorities relative to your unique scenario. So if we can’t use the exact same language all the time, how do we better translate across contexts and frameworks?
Behind the scenes of the Component Inspector plugin
I built a plugin to answer that exact question. When developers explore design files that have UI components in them, in addition to the component name, they’re primarily concerned about the values of a few specific properties. I wanted to generate code for components that focused on the language of the properties instead of the visual style. This Component Inspector plugin does just that, surfacing code for component definitions and instances in different component frameworks—Angular, React, Vue, and Web Components. It actively ignores some property language on the Figma side, and some property language on the code side that don’t overlap, with the goal of generating just enough code to help translate visual styles in precise language for developers. Creating this plugin was the most approachable way for me to embrace this idea of a shared language—to reflect what I see as the most useful version of this in practice.

I shipped this plugin last year, before we launched Dev Mode, a dedicated workspace for developers in Figma. Now that Dev Mode is live, I’ve built a section to display code with plugins in Figma.
The first thing I had to do was specify that the plugin runs differently in Dev Mode. I wanted to keep the same functionality in Figma for the time being. I updated my manifest.json to tell Figma this also works in Dev Mode, and is codegen enabled. I could also specify which languages it generates here.

The relevant sections in the Component Inspector manifest.json for codegen. Check out the full file in the GitHub repository.
{
"name": "Component Inspector",
"editorType": ["dev", "figma"],
"capabilities": ["codegen"],
"codegenLanguages": [
{ "label": "Angular", "value": "angular" },
{ "label": "React", "value": "react" },
{ "label": "Vue: Composition API", "value": "vue-composition" },
{ "label": "Vue: Options API", "value": "vue-options" },
{ "label": "Web Components", "value": "web" },
{ "label": "JSON", "value": "json" }
],
"codegenPreferences": [
{
"itemType": "select",
"propertyName": "boolean",
"label": "Boolean properties on instances",
"options": [
{ "label": "Implicit", "value": "implicit", "isDefault": true },
{ "label": "Explicit", "value": "explicit" }
]
},
{
"itemType": "select",
"propertyName": "comments",
"label": "Comment generation in definitions",
"options": [
{ "label": "Disabled", "value": "disabled", "isDefault": true },
{ "label": "Enabled", "value": "enabled" }
]
},
{
"itemType": "select",
"propertyName": "defaults",
"label": "Default values on instances",
"options": [
{ "label": "Shown", "value": "shown", "isDefault": true },
{ "label": "Hidden", "value": "hidden" }
]
},
{
"itemType": "action",
"propertyName": "settings",
"label": "More Settings"
}
]
}Then in codegen mode, I returned the code whenever the codegen event fires.
Example codegen plugin “generate” event handler. Check out our plugin samples repository on GitHub for a sample codegen plugin.
if (figma.mode === "codegen") {
figma.codegen.on("generate", async (event) => {
const { node, language } = event;
if (language === "html") {
return [
{
title: `My HTML`,
code: `<p>${node.name}</p>`,
language: "HTML",
},
];
} else if (language === "css") {
return [
{
title: `My CSS`,
code: `.${node.name} { color: red; }`,
language: "CSS",
},
];
}
});
}The plugin has always embraced a few of the concepts we have covered here. You can select a property prefix to ignore (“:” in “:state” for example). This allows developers to only see code for relevant component properties by hiding the rest. It also lets you to configure text properties as “slots,” so you can even specify which element type you want to be rendered in that slot.


An “optional” element property is a frequent pattern in code frameworks. This occurs when you have something like “icon” and it can be absent. In Figma, you model this as two properties—“hasIcon” (boolean) controlling the visibility of an “icon” (instance swap). In code this would just be one optional icon property. The “has icon” is implied when the icon property is “undefined.” In order to transform the two Figma properties into one, the plugin has to detect that the instance swap (“icon”) property’s visibility has a reference to a boolean property (“hasIcon”). When that is the case, it knows to make icon optional and not generate code for the “hasIcon” property.
Another “optional” pattern is when a variant can be undefined. There is no good way to show this in Figma. So the plugin allows the user to specify a keyword to name the default variant value (something like “undefined”). The plugin will then detect that case, and treat that variant property as optional in that case. Plugins for codegen can surface user-specific settings like these in a pop-up window, or directly in the UI in a preferences menu. I used this menu for the Component Inspector settings: “implicit or explicit boolean props on instances” and “show or hide default values on instances.”
This plugin is not perfect for everyone, and that’s kind of the point. Developers have very specific needs. We know that. Plugins—especially private plugins for all you organizations out there—are the best way to tailor-fit the translation of design language to specific codebase-informed needs.
Check out these resources if you’re interested in building plugins, and feel free to send feedback on our APIs:
- Component inspector source code
- Plugin samples:
- Plugin docs
- Figma Engineer Sawyer Hood's Config talk about building plugins for Dev Mode
There’s no perfect translation between designers and developers, but when we embrace the differences in our environments and understand the nuances in how we talk about them, we can build better, together, and more efficiently. But to do that, we must amend our strict definitions of terms like variable, component, and property in ways that bring precision, not distortion.

Learn more about how Dev Mode enables designer-developer collaboration in this talk I gave with other members of Figma team.




